From 32e37660d8066b16fe18e6e70ab289ed409e5a0f Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Fri, 26 Apr 2024 22:38:56 -0500 Subject: [PATCH 01/97] Bot Rework --- common/classes.h | 2 + .../database_update_manifest_bots.cpp | 340 ++ common/database_schema.h | 1 + common/eqemu_logsys.h | 16 +- common/eqemu_logsys_log_aliases.h | 70 + .../base/base_bot_data_repository.h | 85 +- .../base/base_bot_settings_repository.h | 464 ++ common/repositories/bot_data_repository.h | 40 - common/repositories/bot_settings_repository.h | 50 + common/ruletypes.h | 113 + common/spdat.cpp | 795 ++- common/spdat.h | 100 +- common/version.h | 2 +- zone/aggro.cpp | 119 + zone/attack.cpp | 7 +- zone/bot.cpp | 4250 +++++++++++++---- zone/bot.h | 338 +- zone/bot_command.cpp | 229 +- zone/bot_command.h | 67 +- zone/bot_commands/actionable.cpp | 69 +- zone/bot_commands/aggressive.cpp | 26 +- zone/bot_commands/appearance.cpp | 2 +- zone/bot_commands/apply_poison.cpp | 1 + zone/bot_commands/attack.cpp | 9 +- zone/bot_commands/behind_mob.cpp | 173 + zone/bot_commands/bind_affinity.cpp | 1 + zone/bot_commands/bot.cpp | 373 +- zone/bot_commands/bot_settings.cpp | 43 + zone/bot_commands/cast.cpp | 301 ++ zone/bot_commands/caster_range.cpp | 9 +- zone/bot_commands/class_race_list.cpp | 113 + zone/bot_commands/click_item.cpp | 4 +- zone/bot_commands/copy_settings.cpp | 366 ++ zone/bot_commands/default_settings.cpp | 473 ++ zone/bot_commands/defensive.cpp | 11 +- zone/bot_commands/follow.cpp | 106 +- zone/bot_commands/guard.cpp | 6 +- zone/bot_commands/hold.cpp | 6 +- zone/bot_commands/illusion_block.cpp | 172 + zone/bot_commands/inventory.cpp | 21 +- zone/bot_commands/max_melee_range.cpp | 172 + zone/bot_commands/mesmerize.cpp | 44 +- zone/bot_commands/pet.cpp | 47 +- zone/bot_commands/pull.cpp | 18 +- zone/bot_commands/release.cpp | 2 +- zone/bot_commands/sit_hp_percent.cpp | 172 + zone/bot_commands/sit_in_combat.cpp | 172 + zone/bot_commands/sit_mana_percent.cpp | 172 + zone/bot_commands/spell.cpp | 4 +- zone/bot_commands/spell_aggro_checks.cpp | 236 + zone/bot_commands/spell_delays.cpp | 242 + zone/bot_commands/spell_engaged_priority.cpp | 240 + zone/bot_commands/spell_holds.cpp | 229 + zone/bot_commands/spell_idle_priority.cpp | 240 + zone/bot_commands/spell_max_hp_pct.cpp | 236 + zone/bot_commands/spell_max_mana_pct.cpp | 236 + zone/bot_commands/spell_max_thresholds.cpp | 243 + zone/bot_commands/spell_min_hp_pct.cpp | 236 + zone/bot_commands/spell_min_mana_pct.cpp | 236 + zone/bot_commands/spell_min_thresholds.cpp | 244 + zone/bot_commands/spell_pursue_priority.cpp | 240 + zone/bot_commands/spell_target_count.cpp | 236 + zone/bot_commands/suspend.cpp | 2 +- zone/bot_commands/taunt.cpp | 19 +- zone/bot_commands/timer.cpp | 9 +- zone/bot_database.cpp | 343 +- zone/bot_database.h | 26 +- zone/bot_structs.h | 5 + zone/botspellsai.cpp | 3785 +++++++-------- zone/client.cpp | 275 +- zone/client.h | 32 + zone/client_bot.cpp | 8 + zone/client_mods.cpp | 6 +- zone/client_packet.cpp | 13 +- zone/command.cpp | 10 + zone/command.h | 5 + zone/entity.cpp | 1 + zone/entity.h | 2 +- zone/exp.cpp | 1 + zone/gm_commands/illusion_block.cpp | 31 + zone/gm_commands/spell_delays.cpp | 215 + zone/gm_commands/spell_holds.cpp | 218 + zone/gm_commands/spell_max_thresholds.cpp | 216 + zone/gm_commands/spell_min_thresholds.cpp | 216 + zone/lua_bot.cpp | 6 - zone/lua_bot.h | 1 - zone/merc.cpp | 4 +- zone/mob.cpp | 825 +++- zone/mob.h | 103 + zone/perl_bot.cpp | 6 - zone/perl_client.cpp | 4 +- zone/perl_npc.cpp | 2 +- zone/questmgr.cpp | 2 +- zone/spell_effects.cpp | 10 +- zone/spells.cpp | 203 +- 95 files changed, 15921 insertions(+), 3953 deletions(-) create mode 100644 common/repositories/base/base_bot_settings_repository.h create mode 100644 common/repositories/bot_settings_repository.h create mode 100644 zone/bot_commands/behind_mob.cpp create mode 100644 zone/bot_commands/bot_settings.cpp create mode 100644 zone/bot_commands/cast.cpp create mode 100644 zone/bot_commands/class_race_list.cpp create mode 100644 zone/bot_commands/copy_settings.cpp create mode 100644 zone/bot_commands/default_settings.cpp create mode 100644 zone/bot_commands/illusion_block.cpp create mode 100644 zone/bot_commands/max_melee_range.cpp create mode 100644 zone/bot_commands/sit_hp_percent.cpp create mode 100644 zone/bot_commands/sit_in_combat.cpp create mode 100644 zone/bot_commands/sit_mana_percent.cpp create mode 100644 zone/bot_commands/spell_aggro_checks.cpp create mode 100644 zone/bot_commands/spell_delays.cpp create mode 100644 zone/bot_commands/spell_engaged_priority.cpp create mode 100644 zone/bot_commands/spell_holds.cpp create mode 100644 zone/bot_commands/spell_idle_priority.cpp create mode 100644 zone/bot_commands/spell_max_hp_pct.cpp create mode 100644 zone/bot_commands/spell_max_mana_pct.cpp create mode 100644 zone/bot_commands/spell_max_thresholds.cpp create mode 100644 zone/bot_commands/spell_min_hp_pct.cpp create mode 100644 zone/bot_commands/spell_min_mana_pct.cpp create mode 100644 zone/bot_commands/spell_min_thresholds.cpp create mode 100644 zone/bot_commands/spell_pursue_priority.cpp create mode 100644 zone/bot_commands/spell_target_count.cpp create mode 100644 zone/gm_commands/illusion_block.cpp create mode 100644 zone/gm_commands/spell_delays.cpp create mode 100644 zone/gm_commands/spell_holds.cpp create mode 100644 zone/gm_commands/spell_max_thresholds.cpp create mode 100644 zone/gm_commands/spell_min_thresholds.cpp diff --git a/common/classes.h b/common/classes.h index 96e5cad26..dc586ceeb 100644 --- a/common/classes.h +++ b/common/classes.h @@ -131,6 +131,8 @@ static std::map class_names = { #define ARMOR_TYPE_LAST ARMOR_TYPE_PLATE #define ARMOR_TYPE_COUNT 5 +#define BOT_CLASS_BASE_ID_PREFIX 3000 + const char* GetClassIDName(uint8 class_id, uint8 level = 0); diff --git a/common/database/database_update_manifest_bots.cpp b/common/database/database_update_manifest_bots.cpp index 48c9aa7f8..afaa9f23e 100644 --- a/common/database/database_update_manifest_bots.cpp +++ b/common/database/database_update_manifest_bots.cpp @@ -161,6 +161,346 @@ ADD COLUMN `extra_haste` mediumint(8) NOT NULL DEFAULT 0 AFTER `wis`; .sql = R"( ALTER TABLE `bot_spells_entries` CHANGE COLUMN `spellid` `spell_id` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `npc_spells_id`; +)" + }, + ManifestEntry{ + .version = 9046, + .description = "2024_05_18_bot_settings.sql", + .check = "SHOW TABLES LIKE 'bot_settings'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `bot_settings` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `char_id` INT UNSIGNED NOT NULL, + `bot_id` INT UNSIGNED NOT NULL, + `setting_id` INT UNSIGNED NOT NULL, + `setting_type` INT UNSIGNED NOT NULL, + `value` INT UNSIGNED NOT NULL, + `category_name` VARCHAR(64) NULL DEFAULT '', + `setting_name` VARCHAR(64) NULL DEFAULT '', + PRIMARY KEY (`id`) USING BTREE +) +COLLATE='utf8mb4_general_ci'; + +INSERT INTO bot_settings SELECT NULL, 0, bd.`bot_id`, 0, 0, bd.`expansion_bitmask`, 'BaseSetting', 'ExpansionBitmask' FROM bot_data bd +JOIN rule_values rv +WHERE rv.rule_name LIKE 'Bots:BotExpansionSettings' +AND bd.expansion_bitmask != rv.rule_value; + +INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, 1, 0, `show_helm`, 'BaseSetting', 'ShowHelm' FROM bot_data WHERE `show_helm` != 1; +INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, 2, 0, `follow_distance`, 'BaseSetting', 'FollowDistance' FROM bot_data WHERE `follow_distance` != 184; + +INSERT INTO bot_settings +SELECT NULL, 0, `bot_id`, 3, 0, `stop_melee_level`, 'BaseSetting', 'StopMeleeLevel' +FROM ( + SELECT `bot_id`, + (CASE + WHEN (`class` IN (2, 6, 10, 11, 12, 13, 14)) THEN 13 + ELSE 255 + END) AS `sml`, + `stop_melee_level` + FROM bot_data +) AS `subquery` +WHERE `sml` != `stop_melee_level`; + +INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, 4, 0, `enforce_spell_settings`, 'BaseSetting', 'EnforceSpellSettings' FROM bot_data WHERE `enforce_spell_settings` != 0; +INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, 5, 0, `archery_setting`, 'BaseSetting', 'RangedSetting' FROM bot_data WHERE `archery_setting` != 0; + +INSERT INTO bot_settings +SELECT NULL, 0, `bot_id`, 8, 0, `caster_range`, 'BaseSetting', 'CasterRange' +FROM ( + SELECT `bot_id`, + (CASE + WHEN (`class` IN (1, 7, 19, 16)) THEN 0 + WHEN `class` = 8 THEN 0 + ELSE 90 + END) AS `casterRange`, + `caster_range` + FROM bot_data +) AS `subquery` +WHERE `casterRange` != `caster_range`; + +ALTER TABLE `bot_data` + DROP COLUMN `show_helm`; +ALTER TABLE `bot_data` + DROP COLUMN `follow_distance`; +ALTER TABLE `bot_data` + DROP COLUMN `stop_melee_level`; +ALTER TABLE `bot_data` + DROP COLUMN `expansion_bitmask`; +ALTER TABLE `bot_data` + DROP COLUMN `enforce_spell_settings`; +ALTER TABLE `bot_data` + DROP COLUMN `archery_setting`; +ALTER TABLE `bot_data` + DROP COLUMN `caster_range`; + +UPDATE `bot_command_settings` SET `aliases`= 'bh' WHERE `bot_command`='behindmob'; +UPDATE `bot_command_settings` SET `aliases`= 'bs|settings' WHERE `bot_command`='botsettings'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|ranged|toggleranged|btr') ELSE 'ranged|toggleranged|btr' END WHERE `bot_command`='bottoggleranged' AND `aliases` NOT LIKE '%ranged|toggleranged|btr%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|cr') ELSE 'cr' END WHERE `bot_command`='casterrange' AND `aliases` NOT LIKE '%cr%'; +UPDATE `bot_command_settings` SET `aliases`= 'copy' WHERE `bot_command`='copysettings'; +UPDATE `bot_command_settings` SET `aliases`= 'default' WHERE `bot_command`='defaultsettings'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|enforce') ELSE 'enforce' END WHERE `bot_command`='enforcespellsettings' AND `aliases` NOT LIKE '%enforce%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|ib') ELSE 'ib' END WHERE `bot_command`='illusionblock' AND `aliases` NOT LIKE '%ib%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|ig') ELSE 'invgive|ig' END WHERE `bot_command`='inventorygive' AND `aliases` NOT LIKE '%ig%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|il') ELSE 'invlist|il' END WHERE `bot_command`='inventorylist' AND `aliases` NOT LIKE '%il%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|ir') ELSE 'invremove|ir' END WHERE `bot_command`='inventoryremove' AND `aliases` NOT LIKE '%ir%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|iw') ELSE 'invwindow|iw' END WHERE `bot_command`='inventorywindow' AND `aliases` NOT LIKE '%iw%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|iu') ELSE 'iu' END WHERE `bot_command`='itemuse' AND `aliases` NOT LIKE '%iu%'; +UPDATE `bot_command_settings` SET `aliases`= 'mmr' WHERE `bot_command`='maxmeleerange'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|pp') ELSE 'pp' END WHERE `bot_command`='pickpocket' AND `aliases` NOT LIKE '%pp%'; +UPDATE `bot_command_settings` SET `aliases`= 'sithp' WHERE `bot_command`='sithppercent'; +UPDATE `bot_command_settings` SET `aliases`= 'sitcombat' WHERE `bot_command`='sitincombat'; +UPDATE `bot_command_settings` SET `aliases`= 'sitmana' WHERE `bot_command`='sitmanapercent'; +UPDATE `bot_command_settings` SET `aliases`= 'aggrochecks' WHERE `bot_command`='spellaggrochecks'; +UPDATE `bot_command_settings` SET `aliases`= 'delays' WHERE `bot_command`='spelldelays'; +UPDATE `bot_command_settings` SET `aliases`= 'engagedpriority' WHERE `bot_command`='spellengagedpriority'; +UPDATE `bot_command_settings` SET `aliases`= 'holds' WHERE `bot_command`='spellholds'; +UPDATE `bot_command_settings` SET `aliases`= 'idlepriority' WHERE `bot_command`='spellidlepriority'; +UPDATE `bot_command_settings` SET `aliases`= 'maxhp' WHERE `bot_command`='spellmaxhppct'; +UPDATE `bot_command_settings` SET `aliases`= 'maxmana' WHERE `bot_command`='spellmaxmanapct'; +UPDATE `bot_command_settings` SET `aliases`= 'maxthresholds' WHERE `bot_command`='spellmaxthresholds'; +UPDATE `bot_command_settings` SET `aliases`= 'minhp' WHERE `bot_command`='spellminhppct'; +UPDATE `bot_command_settings` SET `aliases`= 'minmana' WHERE `bot_command`='spellminmanapct'; +UPDATE `bot_command_settings` SET `aliases`= 'minthresholds' WHERE `bot_command`='spellminthresholds'; +UPDATE `bot_command_settings` SET `aliases`= 'pursuepriority' WHERE `bot_command`='spellpursuepriority'; +UPDATE `bot_command_settings` SET `aliases`= 'targetcount' WHERE `bot_command`='spelltargetcount'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|vc') ELSE 'vc' END WHERE `bot_command`='viewcombos' AND `aliases` NOT LIKE '%vc%'; +)" + }, + ManifestEntry{ + .version = 9047, + .description = "2024_05_18_bot_update_spell_types.sql", + .check = "SELECT * FROM `bot_spells_entries` WHERE `type` > 21", + .condition = "not_empty", + .match = "", + .sql = R"( +UPDATE `bot_spells_entries` SET `type` = 0 WHERE `type` = 1; +UPDATE `bot_spells_entries` SET `type` = 1 WHERE `type` = 2; +UPDATE `bot_spells_entries` SET `type` = 2 WHERE `type` = 4; +UPDATE `bot_spells_entries` SET `type` = 3 WHERE `type` = 8; +UPDATE `bot_spells_entries` SET `type` = 4 WHERE `type` = 16; +UPDATE `bot_spells_entries` SET `type` = 5 WHERE `type` = 32; +UPDATE `bot_spells_entries` SET `type` = 6 WHERE `type` = 64; +UPDATE `bot_spells_entries` SET `type` = 7 WHERE `type` = 128; +UPDATE `bot_spells_entries` SET `type` = 8 WHERE `type` = 256; +UPDATE `bot_spells_entries` SET `type` = 9 WHERE `type` = 512; +UPDATE `bot_spells_entries` SET `type` = 10 WHERE `type` = 1024; +UPDATE `bot_spells_entries` SET `type` = 11 WHERE `type` = 2048; +UPDATE `bot_spells_entries` SET `type` = 12 WHERE `type` = 4096; +UPDATE `bot_spells_entries` SET `type` = 13 WHERE `type` = 8192; +UPDATE `bot_spells_entries` SET `type` = 14 WHERE `type` = 16384; +UPDATE `bot_spells_entries` SET `type` = 15 WHERE `type` = 32768; +UPDATE `bot_spells_entries` SET `type` = 16 WHERE `type` = 65536; +UPDATE `bot_spells_entries` SET `type` = 17 WHERE `type` = 131072; +UPDATE `bot_spells_entries` SET `type` = 18 WHERE `type` = 262144; +UPDATE `bot_spells_entries` SET `type` = 19 WHERE `type` = 524288; +UPDATE `bot_spells_entries` SET `type` = 20 WHERE `type` = 1048576; +UPDATE `bot_spells_entries` SET `type` = 21 WHERE `type` = 2097152; +)" + }, + ManifestEntry{ + .version = 9048, + .description = "2024_05_18_bot_fear_spell_type.sql", + .check = "SELECT * FROM `bot_spells_entries` where `type` = 22", + .condition = "empty", + .match = "", + .sql = R"( +UPDATE bot_spells_entries b, spells_new s +SET b.`type` = 22 +WHERE b.spellid = s.id +AND ( + s.`effectid1` = 23 OR + s.`effectid2` = 23 OR + s.`effectid3` = 23 OR + s.`effectid4` = 23 OR + s.`effectid5` = 23 OR + s.`effectid6` = 23 OR + s.`effectid7` = 23 OR + s.`effectid8` = 23 OR + s.`effectid9` = 23 OR + s.`effectid10` = 23 OR + s.`effectid11` = 23 OR + s.`effectid12` = 23 + ); +)" + }, + ManifestEntry{ + .version = 9049, + .description = "2024_05_18_correct_bot_spell_entries_types.sql", + .check = "SELECT * FROM `bot_spells_entries` where `npc_spells_id` = 3002 AND `spellid` = 14312", + .condition = "empty", + .match = "", + .sql = R"( +-- Class fixes +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3002 WHERE b.`spellid` = 14312; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3002 WHERE b.`spellid` = 14313; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3002 WHERE b.`spellid` = 14314; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3005 WHERE b.`spellid` = 15186; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3005 WHERE b.`spellid` = 15187; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3005 WHERE b.`spellid` = 15188; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14446; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14447; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14467; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14468; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14469; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3003 WHERE b.`spellid` = 14955; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3003 WHERE b.`spellid` = 14956; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14387; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14388; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14389; + +-- Minlevel fixes +UPDATE bot_spells_entries SET `minlevel` = 34 WHERE `spellid` = 1445 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `minlevel` = 2 WHERE `spellid` = 229 AND `npc_spells_id` = 3011; +UPDATE bot_spells_entries SET `minlevel` = 13 WHERE `spellid` = 333 AND `npc_spells_id` = 3013; +UPDATE bot_spells_entries SET `minlevel` = 29 WHERE `spellid` = 106 AND `npc_spells_id` = 3013; +UPDATE bot_spells_entries SET `minlevel` = 38 WHERE `spellid` = 754 AND `npc_spells_id` = 3010; +UPDATE bot_spells_entries SET `minlevel` = 58 WHERE `spellid` = 2589 AND `npc_spells_id` = 3003; +UPDATE bot_spells_entries SET `minlevel` = 67 WHERE `spellid` = 5305 AND `npc_spells_id` = 3004; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 14267 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 14268 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 14269 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `minlevel` = 23 WHERE `spellid` = 738 AND `npc_spells_id` = 3008; +UPDATE bot_spells_entries SET `minlevel` = 51 WHERE `spellid` = 1751 AND `npc_spells_id` = 3008; +UPDATE bot_spells_entries SET `minlevel` = 7 WHERE `spellid` = 734 AND `npc_spells_id` = 3008; +UPDATE bot_spells_entries SET `minlevel` = 5 WHERE `spellid` = 717 AND `npc_spells_id` = 3008; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 15186 AND `npc_spells_id` = 3005; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 15187 AND `npc_spells_id` = 3005; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 15188 AND `npc_spells_id` = 3005; +UPDATE bot_spells_entries SET `minlevel` = 80 WHERE `spellid` = 14446 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 80 WHERE `spellid` = 14447 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 14467 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 14468 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 14469 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14955 AND `npc_spells_id` = 3003; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14956 AND `npc_spells_id` = 3003; +UPDATE bot_spells_entries SET `minlevel` = 78 WHERE `spellid` = 14387 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14388 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14389 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14312 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14313 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14314 AND `npc_spells_id` = 3002; + +-- Maxlevel fixes +UPDATE bot_spells_entries SET `maxlevel` = 83 WHERE `spellid` = 14267 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `maxlevel` = 83 WHERE `spellid` = 14268 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `maxlevel` = 83 WHERE `spellid` = 14269 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spellid` = 14446 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spellid` = 14447 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spellid` = 14467 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spellid` = 14468 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spellid` = 14469 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `maxlevel` = 81 WHERE `spellid` = 14312 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `maxlevel` = 81 WHERE `spellid` = 14313 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `maxlevel` = 81 WHERE `spellid` = 14314 AND `npc_spells_id` = 3002; + +-- Type fixes +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 201; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spellid` = 752; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spellid` = 2117; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 2542; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 2544; +UPDATE bot_spells_entries SET `type` = 6 WHERE `spellid` = 2115; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 1403; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 1405; +UPDATE bot_spells_entries SET `type` = 9 WHERE `spellid` = 289; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 294; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 302; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 521; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 185; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 450; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 186; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 4074; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 195; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 1712; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 1703; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spellid` = 3229; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 3345; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 5509; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 6826; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 270; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 281; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 505; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 526; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 110; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 506; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 162; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 111; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 507; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 527; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 163; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 112; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 1588; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 1573; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 1592; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 1577; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 1578; +UPDATE bot_spells_entries SET `type` = 1 WHERE `spellid` = 1576; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 3386; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 3387; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 4900; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 3395; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 5394; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 5392; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 6827; +UPDATE bot_spells_entries SET `type` = 1 WHERE `spellid` = 5416; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 1437; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 1436; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 5348; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 8008; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 2571; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 370; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spellid` = 1741; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spellid` = 1296; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 270; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 2634; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 2942; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 3462; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 6828; +UPDATE bot_spells_entries SET `type` = 4 WHERE `spellid` = 14312; +UPDATE bot_spells_entries SET `type` = 4 WHERE `spellid` = 14313; +UPDATE bot_spells_entries SET `type` = 4 WHERE `spellid` = 14314; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 18392; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 18393; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 18394; +UPDATE bot_spells_entries SET `type` = 10 WHERE `spellid` = 15186; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 15187; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 15188; +UPDATE bot_spells_entries SET `type` = 1 WHERE `spellid` = 14446; +UPDATE bot_spells_entries SET `type` = 1 WHERE `spellid` = 14447; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 14467; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 14468; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 14469; +UPDATE bot_spells_entries SET `type` = 0 WHERE `spellid` = 14267; +UPDATE bot_spells_entries SET `type` = 0 WHERE `spellid` = 14268; +UPDATE bot_spells_entries SET `type` = 0 WHERE `spellid` = 14269; +UPDATE bot_spells_entries SET `type` = 10 WHERE `spellid` = 14955; +UPDATE bot_spells_entries SET `type` = 10 WHERE `spellid` = 14956; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 14387; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 14388; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 14389; +UPDATE bot_spells_entries SET `type` = 4 WHERE `spellid` = 10436; + +-- UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 3440; -- Ro's Illumination [#3440] from DoT [#8] to Debuff [#14] [Should be 0] +-- UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 303; -- Whirl till you hurl [#303] from Nuke [#0] to Debuff [#14] [Should be 0] +-- UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 619; -- Dyn's Dizzying Draught [#619] from Nuke [#0] to Debuff [#14] [Should be 0] + +-- UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 74; -- Mana Sieve [#74] from Nuke [#0] to Debuff [#14] +-- UPDATE bot_spells_entries SET `type` = 6 WHERE `spellid` = 1686; -- Theft of Thought [#1686] from Nuke [#0] to Lifetap [#6] + +-- UPDATE bot_spells_entries SET `type` = 1 WHERE `spellid` = 3694; -- Stoicism [#3694] from In-Combat Buff [#10] to Regular Heal [#1] +-- UPDATE bot_spells_entries SET `type` = 1 WHERE `spellid` = 4899; -- Breath of Trushar [#4899] from In-Combat Buff [#10] to Regular Heal [#1] + +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spellid` = 738; -- Selo's Consonant Chain [#738] from Slow [#13] to Snare [#7] +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spellid` = 1751; -- Largo's Assonant Binding [#1751] from Slow [#13] to Snare [#7] +-- UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 1748; -- Angstlich's Assonance [#1748] from Slow [#13] to DoT [#8] +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spellid` = 738; -- Selo's Consonant Chain [#738] from Slow [#13] to Snare [#7] +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spellid` = 1751; -- Largo's Assonant Binding [#1751] from Slow [#13] to Snare [#7] +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spellid` = 738; -- Selo's Consonant Chain [#738] from Slow [#13] to Snare [#7] + )" } // -- template; copy/paste this when you need to create a new entry diff --git a/common/database_schema.h b/common/database_schema.h index e0b87b94d..6bf75b6d1 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -415,6 +415,7 @@ namespace DatabaseSchema { "bot_pet_buffs", "bot_pet_inventories", "bot_pets", + "bot_settings", "bot_spell_casting_chances", "bot_spell_settings", "bot_spells_entries", diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index 8bf474f34..0fa85b75a 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -142,6 +142,13 @@ namespace Logs { EqTime, Corpses, XTargets, + BotSettings, + BotPreChecks, + BotHoldChecks, + BotDelayChecks, + BotThresholdChecks, + BotSpellTypeChecks, + TestDebug, MaxCategoryID /* Don't Remove this */ }; @@ -242,7 +249,14 @@ namespace Logs { "Zoning", "EqTime", "Corpses", - "XTargets" + "XTargets", + "Bot Settings", + "Bot Pre Checks", + "Bot Hold Checks", + "Bot Delay Checks", + "Bot Threshold Checks", + "Bot Spell Type Checks", + "Test Debug" }; } diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index 10c9cd98a..fdd33be14 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -844,6 +844,76 @@ OutF(LogSys, Logs::Detail, Logs::XTargets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) +#define LogBotSettings(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::General, Logs::BotSettings))\ + OutF(LogSys, Logs::General, Logs::BotSettings, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotSettingsDetail(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::Detail, Logs::BotSettings))\ + OutF(LogSys, Logs::Detail, Logs::BotSettings, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotPreChecks(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::General, Logs::BotPreChecks))\ + OutF(LogSys, Logs::General, Logs::BotPreChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotPreChecksDetail(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::Detail, Logs::BotPreChecks))\ + OutF(LogSys, Logs::Detail, Logs::BotPreChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotHoldChecks(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::General, Logs::BotHoldChecks))\ + OutF(LogSys, Logs::General, Logs::BotHoldChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotHoldChecksDetail(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::Detail, Logs::BotHoldChecks))\ + OutF(LogSys, Logs::Detail, Logs::BotHoldChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotDelayChecks(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::General, Logs::BotDelayChecks))\ + OutF(LogSys, Logs::General, Logs::BotDelayChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotDelayChecksDetail(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::Detail, Logs::BotDelayChecks))\ + OutF(LogSys, Logs::Detail, Logs::BotDelayChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotThresholdChecks(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::General, Logs::BotThresholdChecks))\ + OutF(LogSys, Logs::General, Logs::BotThresholdChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotThresholdChecksDetail(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::Detail, Logs::BotThresholdChecks))\ + OutF(LogSys, Logs::Detail, Logs::BotThresholdChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotSpellTypeChecks(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::General, Logs::BotSpellTypeChecks))\ + OutF(LogSys, Logs::General, Logs::BotSpellTypeChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotSpellTypeChecksDetail(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::Detail, Logs::BotSpellTypeChecks))\ + OutF(LogSys, Logs::Detail, Logs::BotSpellTypeChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogTestDebug(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::General, Logs::TestDebug))\ + OutF(LogSys, Logs::General, Logs::TestDebug, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogTestDebugDetail(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::Detail, Logs::TestDebug))\ + OutF(LogSys, Logs::Detail, Logs::TestDebug, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + #define Log(debug_level, log_category, message, ...) do {\ if (LogSys.IsLogEnabled(debug_level, log_category))\ LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ diff --git a/common/repositories/base/base_bot_data_repository.h b/common/repositories/base/base_bot_data_repository.h index 96622c2db..eb0cdcb99 100644 --- a/common/repositories/base/base_bot_data_repository.h +++ b/common/repositories/base/base_bot_data_repository.h @@ -64,13 +64,6 @@ public: int16_t poison; int16_t disease; int16_t corruption; - uint32_t show_helm; - uint32_t follow_distance; - uint8_t stop_melee_level; - int32_t expansion_bitmask; - uint8_t enforce_spell_settings; - uint8_t archery_setting; - uint32_t caster_range; }; static std::string PrimaryKey() @@ -126,13 +119,6 @@ public: "poison", "disease", "corruption", - "show_helm", - "follow_distance", - "stop_melee_level", - "expansion_bitmask", - "enforce_spell_settings", - "archery_setting", - "caster_range", }; } @@ -184,13 +170,6 @@ public: "poison", "disease", "corruption", - "show_helm", - "follow_distance", - "stop_melee_level", - "expansion_bitmask", - "enforce_spell_settings", - "archery_setting", - "caster_range", }; } @@ -276,13 +255,7 @@ public: e.poison = 0; e.disease = 0; e.corruption = 0; - e.show_helm = 0; - e.follow_distance = 200; - e.stop_melee_level = 255; - e.expansion_bitmask = -1; - e.enforce_spell_settings = 0; - e.archery_setting = 0; - e.caster_range = 300; + return e; } @@ -364,13 +337,6 @@ public: e.poison = row[42] ? static_cast(atoi(row[42])) : 0; e.disease = row[43] ? static_cast(atoi(row[43])) : 0; e.corruption = row[44] ? static_cast(atoi(row[44])) : 0; - e.show_helm = row[45] ? static_cast(strtoul(row[45], nullptr, 10)) : 0; - e.follow_distance = row[46] ? static_cast(strtoul(row[46], nullptr, 10)) : 200; - e.stop_melee_level = row[47] ? static_cast(strtoul(row[47], nullptr, 10)) : 255; - e.expansion_bitmask = row[48] ? static_cast(atoi(row[48])) : -1; - e.enforce_spell_settings = row[49] ? static_cast(strtoul(row[49], nullptr, 10)) : 0; - e.archery_setting = row[50] ? static_cast(strtoul(row[50], nullptr, 10)) : 0; - e.caster_range = row[51] ? static_cast(strtoul(row[51], nullptr, 10)) : 300; return e; } @@ -448,13 +414,6 @@ public: v.push_back(columns[42] + " = " + std::to_string(e.poison)); v.push_back(columns[43] + " = " + std::to_string(e.disease)); v.push_back(columns[44] + " = " + std::to_string(e.corruption)); - v.push_back(columns[45] + " = " + std::to_string(e.show_helm)); - v.push_back(columns[46] + " = " + std::to_string(e.follow_distance)); - v.push_back(columns[47] + " = " + std::to_string(e.stop_melee_level)); - v.push_back(columns[48] + " = " + std::to_string(e.expansion_bitmask)); - v.push_back(columns[49] + " = " + std::to_string(e.enforce_spell_settings)); - v.push_back(columns[50] + " = " + std::to_string(e.archery_setting)); - v.push_back(columns[51] + " = " + std::to_string(e.caster_range)); auto results = db.QueryDatabase( fmt::format( @@ -521,13 +480,6 @@ public: v.push_back(std::to_string(e.poison)); v.push_back(std::to_string(e.disease)); v.push_back(std::to_string(e.corruption)); - v.push_back(std::to_string(e.show_helm)); - v.push_back(std::to_string(e.follow_distance)); - v.push_back(std::to_string(e.stop_melee_level)); - v.push_back(std::to_string(e.expansion_bitmask)); - v.push_back(std::to_string(e.enforce_spell_settings)); - v.push_back(std::to_string(e.archery_setting)); - v.push_back(std::to_string(e.caster_range)); auto results = db.QueryDatabase( fmt::format( @@ -602,13 +554,6 @@ public: v.push_back(std::to_string(e.poison)); v.push_back(std::to_string(e.disease)); v.push_back(std::to_string(e.corruption)); - v.push_back(std::to_string(e.show_helm)); - v.push_back(std::to_string(e.follow_distance)); - v.push_back(std::to_string(e.stop_melee_level)); - v.push_back(std::to_string(e.expansion_bitmask)); - v.push_back(std::to_string(e.enforce_spell_settings)); - v.push_back(std::to_string(e.archery_setting)); - v.push_back(std::to_string(e.caster_range)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -687,13 +632,6 @@ public: e.poison = row[42] ? static_cast(atoi(row[42])) : 0; e.disease = row[43] ? static_cast(atoi(row[43])) : 0; e.corruption = row[44] ? static_cast(atoi(row[44])) : 0; - e.show_helm = row[45] ? static_cast(strtoul(row[45], nullptr, 10)) : 0; - e.follow_distance = row[46] ? static_cast(strtoul(row[46], nullptr, 10)) : 200; - e.stop_melee_level = row[47] ? static_cast(strtoul(row[47], nullptr, 10)) : 255; - e.expansion_bitmask = row[48] ? static_cast(atoi(row[48])) : -1; - e.enforce_spell_settings = row[49] ? static_cast(strtoul(row[49], nullptr, 10)) : 0; - e.archery_setting = row[50] ? static_cast(strtoul(row[50], nullptr, 10)) : 0; - e.caster_range = row[51] ? static_cast(strtoul(row[51], nullptr, 10)) : 300; all_entries.push_back(e); } @@ -763,13 +701,6 @@ public: e.poison = row[42] ? static_cast(atoi(row[42])) : 0; e.disease = row[43] ? static_cast(atoi(row[43])) : 0; e.corruption = row[44] ? static_cast(atoi(row[44])) : 0; - e.show_helm = row[45] ? static_cast(strtoul(row[45], nullptr, 10)) : 0; - e.follow_distance = row[46] ? static_cast(strtoul(row[46], nullptr, 10)) : 200; - e.stop_melee_level = row[47] ? static_cast(strtoul(row[47], nullptr, 10)) : 255; - e.expansion_bitmask = row[48] ? static_cast(atoi(row[48])) : -1; - e.enforce_spell_settings = row[49] ? static_cast(strtoul(row[49], nullptr, 10)) : 0; - e.archery_setting = row[50] ? static_cast(strtoul(row[50], nullptr, 10)) : 0; - e.caster_range = row[51] ? static_cast(strtoul(row[51], nullptr, 10)) : 300; all_entries.push_back(e); } @@ -889,13 +820,6 @@ public: v.push_back(std::to_string(e.poison)); v.push_back(std::to_string(e.disease)); v.push_back(std::to_string(e.corruption)); - v.push_back(std::to_string(e.show_helm)); - v.push_back(std::to_string(e.follow_distance)); - v.push_back(std::to_string(e.stop_melee_level)); - v.push_back(std::to_string(e.expansion_bitmask)); - v.push_back(std::to_string(e.enforce_spell_settings)); - v.push_back(std::to_string(e.archery_setting)); - v.push_back(std::to_string(e.caster_range)); auto results = db.QueryDatabase( fmt::format( @@ -963,13 +887,6 @@ public: v.push_back(std::to_string(e.poison)); v.push_back(std::to_string(e.disease)); v.push_back(std::to_string(e.corruption)); - v.push_back(std::to_string(e.show_helm)); - v.push_back(std::to_string(e.follow_distance)); - v.push_back(std::to_string(e.stop_melee_level)); - v.push_back(std::to_string(e.expansion_bitmask)); - v.push_back(std::to_string(e.enforce_spell_settings)); - v.push_back(std::to_string(e.archery_setting)); - v.push_back(std::to_string(e.caster_range)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/base/base_bot_settings_repository.h b/common/repositories/base/base_bot_settings_repository.h new file mode 100644 index 000000000..2018177dc --- /dev/null +++ b/common/repositories/base/base_bot_settings_repository.h @@ -0,0 +1,464 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_BOT_SETTINGS_REPOSITORY_H +#define EQEMU_BASE_BOT_SETTINGS_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseBotSettingsRepository { +public: + struct BotSettings { + uint32_t id; + uint32_t char_id; + uint32_t bot_id; + uint16_t setting_id; + uint8_t setting_type; + int32_t value; + std::string category_name; + std::string setting_name; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "char_id", + "bot_id", + "setting_id", + "setting_type", + "value", + "category_name", + "setting_name", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "char_id", + "bot_id", + "setting_id", + "setting_type", + "value", + "category_name", + "setting_name", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("bot_settings"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static BotSettings NewEntity() + { + BotSettings e{}; + + e.id = 0; + e.char_id = 0; + e.bot_id = 0; + e.setting_id = 0; + e.setting_type = 0; + e.value = 0; + e.category_name = ""; + e.setting_name = ""; + + return e; + } + + static BotSettings GetBotSettings( + const std::vector &bot_settingss, + int bot_settings_id + ) + { + for (auto &bot_settings : bot_settingss) { + if (bot_settings.id == bot_settings_id) { + return bot_settings; + } + } + + return NewEntity(); + } + + static BotSettings FindOne( + Database& db, + int bot_settings_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + bot_settings_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + BotSettings e{}; + + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.bot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.setting_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.setting_type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.value = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.category_name = row[6] ? row[6] : ""; + e.setting_name = row[7] ? row[7] : ""; + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int bot_settings_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + bot_settings_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const BotSettings &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[0] + " = " + std::to_string(e.id)); + v.push_back(columns[1] + " = " + std::to_string(e.char_id)); + v.push_back(columns[2] + " = " + std::to_string(e.bot_id)); + v.push_back(columns[3] + " = " + std::to_string(e.setting_id)); + v.push_back(columns[4] + " = " + std::to_string(e.setting_type)); + v.push_back(columns[5] + " = " + std::to_string(e.value)); + v.push_back(columns[6] + " = '" + Strings::Escape(e.category_name) + "'"); + v.push_back(columns[7] + " = '" + Strings::Escape(e.setting_name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.bot_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static BotSettings InsertOne( + Database& db, + BotSettings e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.setting_id)); + v.push_back(std::to_string(e.setting_type)); + v.push_back(std::to_string(e.value)); + v.push_back("'" + Strings::Escape(e.category_name) + "'"); + v.push_back("'" + Strings::Escape(e.setting_name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.setting_id)); + v.push_back(std::to_string(e.setting_type)); + v.push_back(std::to_string(e.value)); + v.push_back("'" + Strings::Escape(e.category_name) + "'"); + v.push_back("'" + Strings::Escape(e.setting_name) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + BotSettings e{}; + + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.bot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.setting_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.setting_type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.value = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.category_name = row[6] ? row[6] : ""; + e.setting_name = row[7] ? row[7] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + BotSettings e{}; + + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.bot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.setting_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.setting_type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.value = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.category_name = row[6] ? row[6] : ""; + e.setting_name = row[7] ? row[7] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const BotSettings &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.setting_id)); + v.push_back(std::to_string(e.setting_type)); + v.push_back(std::to_string(e.value)); + v.push_back("'" + Strings::Escape(e.category_name) + "'"); + v.push_back("'" + Strings::Escape(e.setting_name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.setting_id)); + v.push_back(std::to_string(e.setting_type)); + v.push_back(std::to_string(e.value)); + v.push_back("'" + Strings::Escape(e.category_name) + "'"); + v.push_back("'" + Strings::Escape(e.setting_name) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_BOT_SETTINGS_REPOSITORY_H diff --git a/common/repositories/bot_data_repository.h b/common/repositories/bot_data_repository.h index f6508479f..e8d0038a4 100644 --- a/common/repositories/bot_data_repository.h +++ b/common/repositories/bot_data_repository.h @@ -44,46 +44,6 @@ public: */ // Custom extended repository methods here - static bool SaveAllHelmAppearances(Database& db, const uint32 owner_id, const bool show_flag) - { - auto results = db.QueryDatabase( - fmt::format( - "UPDATE `{}` SET `show_helm` = {} WHERE `owner_id` = {}", - TableName(), - show_flag ? 1 : 0, - owner_id - ) - ); - - return results.Success(); - } - - static bool ToggleAllHelmAppearances(Database& db, const uint32 owner_id) - { - auto results = db.QueryDatabase( - fmt::format( - "UPDATE `{}` SET `show_helm` = (`show_helm` XOR '1') WHERE `owner_id` = {}", - TableName(), - owner_id - ) - ); - - return results.Success(); - } - - static bool SaveAllFollowDistances(Database& db, const uint32 owner_id, const uint32 follow_distance) - { - auto results = db.QueryDatabase( - fmt::format( - "UPDATE `{}` SET `follow_distance` = {} WHERE `owner_id` = {}", - TableName(), - follow_distance, - owner_id - ) - ); - - return results.Success(); - } }; #endif //EQEMU_BOT_DATA_REPOSITORY_H diff --git a/common/repositories/bot_settings_repository.h b/common/repositories/bot_settings_repository.h new file mode 100644 index 000000000..7cf1d0dbd --- /dev/null +++ b/common/repositories/bot_settings_repository.h @@ -0,0 +1,50 @@ +#ifndef EQEMU_BOT_SETTINGS_REPOSITORY_H +#define EQEMU_BOT_SETTINGS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_bot_settings_repository.h" + +class BotSettingsRepository: public BaseBotSettingsRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * BotSettingsRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * BotSettingsRepository::GetWhereNeverExpires() + * BotSettingsRepository::GetWhereXAndY() + * BotSettingsRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + +}; + +#endif //EQEMU_BOT_SETTINGS_REPOSITORY_H diff --git a/common/ruletypes.h b/common/ruletypes.h index a42832527..aec9bcb8b 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -377,6 +377,9 @@ RULE_BOOL(Map, MobZVisualDebug, false, "Displays spell effects determining wheth RULE_BOOL(Map, MobPathingVisualDebug, false, "Displays nodes in pathing points in realtime to help with visual debugging") RULE_REAL(Map, FixPathingZMaxDeltaSendTo, 20, "At runtime in SendTo: maximum change in Z to allow the BestZ code to apply") RULE_INT(Map, FindBestZHeightAdjust, 1, "Adds this to the current Z before seeking the best Z position") +RULE_BOOL(Map, CheckForLoSCheat, false, "Runs predefined zone checks to check for LoS cheating through doors and such.") +RULE_BOOL(Map, EnableLoSCheatExemptions, false, "Enables exemptions for the LoS Cheat check.") +RULE_REAL(Map, RangeCheckForLoSCheat, 20.0, "Default 20.0. Range to check if one is within range of a door.") RULE_CATEGORY_END() RULE_CATEGORY(Pathing) @@ -747,6 +750,7 @@ RULE_INT(Bots, CommandSpellRank, 1, "Filters bot command spells by rank. 1, 2 an RULE_INT(Bots, CreationLimit, 150, "Number of bots that each account can create") RULE_BOOL(Bots, FinishBuffing, false, "Allow for buffs to complete even if the bot caster is out of mana. Only affects buffing out of combat") RULE_BOOL(Bots, GroupBuffing, false, "Bots will cast single target buffs as group buffs, default is false for single. Does not make single target buffs work for MGB") +RULE_BOOL(Bots, RaidBuffing, false, "Bots will cast single target buffs as raid buffs, default is false for single. Does not make single target buffs work for MGB") RULE_INT(Bots, HealRotationMaxMembers, 24, "Maximum number of heal rotation members") RULE_INT(Bots, HealRotationMaxTargets, 12, "Maximum number of heal rotation targets") RULE_REAL(Bots, ManaRegen, 2.0, "Adjust mana regen. Acts as a final multiplier, stacks with Rule Character:ManaRegenMultiplier.") @@ -776,6 +780,91 @@ RULE_BOOL(Bots, CanClickMageEpicV1, true, "Whether or not bots are allowed to cl RULE_BOOL(Bots, BotsIgnoreLevelBasedHasteCaps, false, "Ignores hard coded level based haste caps.") RULE_INT(Bots, BotsHasteCap, 100, "Haste cap for non-v3(over haste) haste") RULE_INT(Bots, BotsHastev3Cap, 25, "Haste cap for v3(over haste) haste") +RULE_BOOL(Bots, CrossRaidBuffingAndHealing, true, "If True, bots will be able to cast on all raid members rather than just their raid group members. Default true.") +RULE_BOOL(Bots, CanCastIllusionsOnPets, false, "If True, bots will be able to cast spells that have an illusion effect on pets. Default false.") +RULE_BOOL(Bots, CanCastPetOnlyOnOthersPets, false, "If True, bots will be able to cast pet only spells on other's pets. Default false.") +RULE_BOOL(Bots, RequirePetAffinity, true, "If True, bots will be need to have the Pet Affinity AA to allow their pets to be hit with group spells.") +RULE_INT(Bots, SpellResistLimit, 150, "150 Default. This is the resist cap where bots will refuse to cast spells on enemies due to a high resist chance.") +RULE_INT(Bots, StunCastChanceIfCasting, 50, "50 Default. Chance for non-Paladins to cast a stun spell if the target is casting.") +RULE_INT(Bots, StunCastChanceNormal, 15, "15 Default. Chance for non-Paladins to cast a stun spell on the target.") +RULE_INT(Bots, StunCastChancePaladins, 75, "75 Default. Chance for Paladins to cast a stun spell if the target is casting.") +RULE_INT(Bots, PercentChanceToCastNuke, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastHeal, 90, "The chance for a bot to attempt to cast the given spell type in combat. Default 90%.") +RULE_INT(Bots, PercentChanceToCastRoot, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastBuff, 90, "The chance for a bot to attempt to cast the given spell type in combat. Default 90%.") +RULE_INT(Bots, PercentChanceToCastEscape, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastLifetap, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastSnare, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastDOT, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastDispel, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastInCombatBuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastMez, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastSlow, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastDebuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastCure, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastHateRedux, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastFear, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastOtherType, 90, "The chance for a bot to attempt to cast the remaining spell types in combat. Default 0-%.") +RULE_INT(Bots, MinDelayBetweenInCombatCastAttempts, 250, "The minimum delay in milliseconds between cast attempts while in-combat. This is rolled between the min and max. Default 500ms.") +RULE_INT(Bots, MaxDelayBetweenInCombatCastAttempts, 2000, "The maximum delay in milliseconds between cast attempts while in-combat. This is rolled between the min and max. Default 2000ms.") +RULE_INT(Bots, MinDelayBetweenOutCombatCastAttempts, 125, "The minimum delay in milliseconds between cast attempts while out-of-combat. This is rolled between the min and max. Default 200ms.") +RULE_INT(Bots, MaxDelayBetweenOutCombatCastAttempts, 500, "The maximum delay in milliseconds between cast attempts while out-of-combat. This is rolled between the min and max. Default 500ms.") +RULE_INT(Bots, MezChance, 35, "35 Default. Chance for a bot to attempt to Mez a target after validating it is eligible.") +RULE_INT(Bots, AEMezChance, 35, "35 Default. Chance for a bot to attempt to AE Mez targets after validating they are eligible.") +RULE_INT(Bots, MezSuccessDelay, 3500, "3500 (3.5 sec) Default. Delay between successful Mez attempts.") +RULE_INT(Bots, AEMezSuccessDelay, 5000, "5000 (5 sec) Default. Delay between successful AEMez attempts.") +RULE_INT(Bots, MezFailDelay, 2000, "2000 (2 sec) Default. Delay between failed Mez attempts.") +RULE_INT(Bots, MezAEFailDelay, 4000, "4000 (4 sec) Default. Delay between failed AEMez attempts.") +RULE_INT(Bots, MinGroupHealTargets, 3, "Minimum number of targets in valid range that are required for a group heal to cast. Default 3.") +RULE_INT(Bots, MinGroupCureTargets, 3, "Minimum number of targets in valid range that are required for a cure heal to cast. Default 3.") +RULE_INT(Bots, MinTargetsForAESpell, 3, "Minimum number of targets in valid range that are required for an AE spell to cast. Default 3.") +RULE_INT(Bots, MinTargetsForGroupSpell, 3, "Minimum number of targets in valid range that are required for an group spell to cast. Default 3.") +RULE_BOOL(Bots, AllowBuffingHealingFamiliars, false, "Determines if bots are allowed to buff and heal familiars. Default false.") +RULE_BOOL(Bots, RunSpellTypeChecksOnSpawn, false, "This will run a serious of checks on spell types and output errors to LogBotSpellTypeChecks") +RULE_BOOL(Bots, AllowMagicianEpicPet, false, "If enabled, magician bots can summon their epic pets following the rules AllowMagicianEpicPetLevel") +RULE_INT(Bots, AllowMagicianEpicPetLevel, 50, "If AllowMagicianEpicPet is enabled, bots can start using their epic pets at this level") +RULE_INT(Bots, RequiredMagicianEpicPetItemID, 28034, "If AllowMagicianEpicPet is enabled and this is set, bots will be required to have this item ID equipped to cast their epic. Takes in to account AllowMagicianEpicPetLevel as well. Set to 0 to disable requirement") +RULE_STRING(Bots, EpicPetSpellName, "", "'teleport_zone' in the spell to be cast for epic pets. This must be in their spell list to cast.") +RULE_BOOL(Bots, AllowSpellPulling, true, "If enabled bots will use a spell to pull when within range. Uses PullSpellID.") +RULE_INT(Bots, PullSpellID, 5225, "Default 5225 - Throw Stone. Spell that will be cast to pull by bots") +RULE_BOOL(Bots, AllowBotEquipAnyClassGear, false, "Allows Bots to wear Equipment even if their class is not valid") +RULE_BOOL(Bots, BotArcheryConsumesAmmo, true, "Set to false to disable Archery Ammo Consumption") +RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption") +RULE_INT(Bots, StackSizeMin, 100, "100 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).") +RULE_INT(Bots, HasOrMayGetAggroThreshold, 90, "90 Default. Percent threshold of total hate where bots will stop casting spells that generate hate if they are set to try to not pull aggro via spells.") +RULE_BOOL(Bots, UseFlatNormalMeleeRange, false, "False Default. If true, bots melee distance will be a flat distance set by Bots:NormalMeleeRangeDistance.") +RULE_REAL(Bots, NormalMeleeRangeDistance, 0.75, "Multiplier of the max melee range at which a bot will stand in melee combat. 0.75 Recommended, max melee for all abilities to land.") +RULE_REAL(Bots, PercentMinMeleeDistance, 0.60, "Multiplier of the max melee range - Minimum distance from target a bot will stand while in melee combat before trying to adjust. 0.60 Recommended.") +RULE_REAL(Bots, MaxDistanceForMelee, 20, "Maximum distance bots will stand for melee. Default 20 to allow all special attacks to land.") +RULE_REAL(Bots, TauntNormalMeleeRangeDistance, 0.50, "Multiplier of the max melee range at which a taunting bot will stand in melee combat. 0.50 Recommended, closer than others .") +RULE_REAL(Bots, PercentTauntMinMeleeDistance, 0.25, "Multiplier of max melee range - Minimum distance from target a taunting bot will stand while in melee combat before trying to adjust. 0.25 Recommended.") +RULE_REAL(Bots, PercentMaxMeleeRangeDistance, 0.95, "Multiplier of the max melee range at which a bot will stand in melee combat. 0.95 Recommended, max melee while disabling special attacks/taunt.") +RULE_REAL(Bots, PercentMinMaxMeleeRangeDistance, 0.75, "Multiplier of the closest max melee range at which a bot will stand in melee combat before trying to adjust. 0.75 Recommended, max melee while disabling special attacks/taunt.") +RULE_BOOL(Bots, CastersStayJustOutOfMeleeRange, true, "True Default. If true, caster bots will stay just out of melee range. Otherwise they use Bots:PercentMinCasterRangeDistance.") +RULE_REAL(Bots, PercentMinCasterRangeDistance, 0.60, "Multiplier of the closest caster range at which a bot will stand while casting before trying to adjust. 0.60 Recommended.") +RULE_BOOL(Bots, TauntingBotsFollowTopHate, true, "True Default. If true, bots that are taunting will attempt to stick with whoever currently is top hate.") +RULE_REAL(Bots, DistanceTauntingBotsStickMainHate, 25.00, "If TauntingBotsFollowTopHate is enabled, this is the distance bots will try to stick to whoever currently is Top Hate.") +RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, false, "False Default. If true, when bots are at max melee distance, special abilities including taunt will be disabled.") +RULE_INT(Bots, MinJitterTimer, 500, "Minimum ms between bot movement jitter checks.") +RULE_INT(Bots, MaxJitterTimer, 2500, "Maximum ms between bot movement jitter checks. Set to 0 to disable timer checks.") +RULE_BOOL(Bots, PreventBotCampOnFD, true, "True Default. If true, players will not be able to camp bots while feign death.") +RULE_BOOL(Bots, PreventBotSpawnOnFD, true, "True Default. If true, players will not be able to spawn bots while feign death.") +RULE_BOOL(Bots, PreventBotSpawnOnEngaged, true, "True Default. If true, players will not be able to spawn bots while you, your group or raid are engaged.") +RULE_BOOL(Bots, PreventBotCampOnEngaged, true, "True Default. If true, players will not be able to camp bots while you, your group or raid are engaged.") +RULE_BOOL(Bots, CopySettingsOwnBotsOnly, true, "Determines whether a bot you are copying settings from must be a bot you own or not, default true.") +RULE_BOOL(Bots, AllowCopySettingsAnon, true, "If player's are allowed to copy settings of bots owned by anonymous players.") +RULE_BOOL(Bots, AllowCharmedPetBuffs, true, "Whether or not bots are allowed to cast buff charmed pets, default true.") +RULE_BOOL(Bots, AllowCharmedPetHeals, true, "Whether or not bots are allowed to cast heal charmed pets, default true.") +RULE_BOOL(Bots, AllowCharmedPetCures, true, "Whether or not bots are allowed to cast cure charmed pets, default true.") +RULE_BOOL(Bots, ShowResistMessagesToOwner, true, "Default True. If enabled, when a bot's spell is resisted it will send a spell failure to their owner.") +RULE_BOOL(Bots, BotBuffLevelRestrictions, true, "Buffs will not land on low level bots like live players") +RULE_BOOL(Bots, BotsUseLiveBlockedMessage, false, "Setting whether detailed spell block messages should be used for bots as players do on the live servers") +RULE_BOOL(Bots, BotSoftDeletes, true, "When bots are deleted, they are only soft deleted") +RULE_INT(Bots, MinStatusToBypassSpawnLimit, 100, "Minimum status to bypass the anti-spam system") +RULE_INT(Bots, StatusSpawnLimit, 120, "Minimum status to bypass spawn limit. Default 120.") +RULE_INT(Bots, MinStatusToBypassCreateLimit, 100, "Minimum status to bypass the anti-spam system") +RULE_INT(Bots, StatusCreateLimit, 120, "Minimum status to bypass spawn limit. Default 120.") +RULE_BOOL(Bots, BardsAnnounceCasts, false, "This determines whether or not Bard bots will announce that they're casting songs (Buffs, Heals, Nukes, Slows, etc.) they will always announce Mez.") RULE_CATEGORY_END() RULE_CATEGORY(Chat) @@ -1001,6 +1090,30 @@ RULE_CATEGORY_END() RULE_CATEGORY(Command) RULE_BOOL(Command, DyeCommandRequiresDyes, false, "Enable this to require a Prismatic Dye (32557) each time someone uses #dye.") RULE_BOOL(Command, HideMeCommandDisablesTells, true, "Disable this to allow tells to be received when using #hideme.") +RULE_INT(Command, MaxHelpLineLength, 53, "Maximum length of a line before splitting it in to new lines for DiaWind. Default 53.") +RULE_STRING(Command, DescriptionColor, "light_grey", "Color for command help windows") +RULE_STRING(Command, DescriptionHeaderColor, "indian_red", "Color for command help windows") +RULE_STRING(Command, AltDescriptionColor, "light_grey", "Color for command help windows") +RULE_STRING(Command, NoteColor, "dark_orange", "Color for command help windows") +RULE_STRING(Command, NoteHeaderColor, "indian_red", "Color for command help windows") +RULE_STRING(Command, AltNoteColor, "dark_orange", "Color for command help windows") +RULE_STRING(Command, ExampleColor, "goldenrod", "Color for command help windows") +RULE_STRING(Command, ExampleHeaderColor, "indian_red", "Color for command help windows") +RULE_STRING(Command, SubExampleColor, "slate_blue", "Color for command help windows") +RULE_STRING(Command, AltExampleColor, "goldenrod", "Color for command help windows") +RULE_STRING(Command, SubAltExampleColor, "goldenrod", "Color for command help windows") +RULE_STRING(Command, OptionColor, "light_grey", "Color for command help windows") +RULE_STRING(Command, OptionHeaderColor, "indian_red", "Color for command help windows") +RULE_STRING(Command, SubOptionColor, "light_grey", "Color for command help windows") +RULE_STRING(Command, AltOptionColor, "light_grey", "Color for command help windows") +RULE_STRING(Command, SubAltOptionColor, "light_grey", "Color for command help windows") +RULE_STRING(Command, ActionableColor, "light_grey", "Color for command help windows") +RULE_STRING(Command, ActionableHeaderColor, "indian_red", "Color for command help windows") +RULE_STRING(Command, AltActionableColor, "light_grey", "Color for command help windows") +RULE_STRING(Command, HeaderColor, "indian_red", "Color for command help windows") +RULE_STRING(Command, SecondaryHeaderColor, "slate_blue", "Color for command help windows") +RULE_STRING(Command, AltHeaderColor, "indian_red", "Color for command help windows") +RULE_STRING(Command, FillerLineColor, "dark_grey", "Color for command help windows") RULE_CATEGORY_END() RULE_CATEGORY(Doors) diff --git a/common/spdat.cpp b/common/spdat.cpp index 3acac1c8b..53bf03f6d 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -125,9 +125,26 @@ bool IsMesmerizeSpell(uint16 spell_id) return IsEffectInSpell(spell_id, SE_Mez); } +bool SpellBreaksMez(uint16 spell_id) +{ + if (IsDetrimentalSpell(spell_id) && IsAnyDamageSpell(spell_id)) { + return true; + } + + return false; +} + bool IsStunSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_Stun); + if (IsEffectInSpell(spell_id, SE_Stun)) { + return true; + } + + if (IsEffectInSpell(spell_id, SE_SpinTarget)) { + return true; + } + + return false; } bool IsSummonSpell(uint16 spell_id) @@ -164,13 +181,7 @@ bool IsDamageSpell(uint16 spell_id) const auto effect_id = spell.effect_id[i]; if ( spell.base_value[i] < 0 && - ( - effect_id == SE_CurrentHPOnce || - ( - effect_id == SE_CurrentHP && - spell.buff_duration < 1 - ) - ) + (effect_id == SE_CurrentHPOnce || effect_id == SE_CurrentHP) ) { return true; } @@ -179,6 +190,62 @@ bool IsDamageSpell(uint16 spell_id) return false; } +bool IsAnyDamageSpell(uint16 spell_id) +{ + if (IsLifetapSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + for (int i = 0; i < EFFECT_COUNT; i++) { + const auto effect_id = spell.effect_id[i]; + if ( + spell.base_value[i] < 0 && + ( + effect_id == SE_CurrentHPOnce || + ( + effect_id == SE_CurrentHP && + spell.buff_duration < 1 + ) + ) + ) { + return true; + } + } + + return false; +} + +bool IsDamageOverTimeSpell(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + if (IsLifetapSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + if (spell.good_effect || !spell.buff_duration_formula) { + return false; + } + + for (int i = 0; i < EFFECT_COUNT; i++) { + const auto effect_id = spell.effect_id[i]; + if ( + spell.base_value[i] < 0 && + effect_id == SE_CurrentHP && + spell.buff_duration > 1 + ) { + return true; + } + } + + return false; +} bool IsFearSpell(uint16 spell_id) { @@ -409,7 +476,8 @@ bool IsSummonPetSpell(uint16 spell_id) return ( IsEffectInSpell(spell_id, SE_SummonPet) || IsEffectInSpell(spell_id, SE_SummonBSTPet) || - IsEffectInSpell(spell_id, SE_Familiar) + IsEffectInSpell(spell_id, SE_Familiar) || + IsEffectInSpell(spell_id, SE_NecPet) ); } @@ -560,12 +628,11 @@ bool IsPBAENukeSpell(uint16 spell_id) if ( IsPureNukeSpell(spell_id) && - spell.aoe_range > 0 && - spell.target_type == ST_AECaster + !IsTargetRequiredForSpell(spell_id) ) { return true; } - + return false; } @@ -588,6 +655,137 @@ bool IsAERainNukeSpell(uint16 spell_id) return false; } +bool IsAnyNukeOrStunSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + if (IsSelfConversionSpell(spell_id) || IsEscapeSpell(spell_id)) { + return false; + } + + if ( + IsPBAENukeSpell(spell_id) || + IsAERainNukeSpell(spell_id) || + IsPureNukeSpell(spell_id) || + IsStunSpell(spell_id) || + (IsDamageSpell(spell_id) && !IsDamageOverTimeSpell(spell_id)) + ) { + return true; + } + + return false; +} + +bool IsAnyAESpell(uint16 spell_id) { + //if ( + // spells[spell_id].target_type == ST_Target || + // spells[spell_id].target_type == ST_Self || + // spells[spell_id].target_type == ST_Animal || + // spells[spell_id].target_type == ST_Undead || + // spells[spell_id].target_type == ST_Summoned || + // spells[spell_id].target_type == ST_Tap || + // spells[spell_id].target_type == ST_Pet || + // spells[spell_id].target_type == ST_Corpse || + // spells[spell_id].target_type == ST_Plant || + // spells[spell_id].target_type == ST_Giant || + // spells[spell_id].target_type == ST_Dragon || + // spells[spell_id].target_type == ST_HateList || + // spells[spell_id].target_type == ST_LDoNChest_Cursed || + // spells[spell_id].target_type == ST_Muramite || + // spells[spell_id].target_type == ST_SummonedPet || + // spells[spell_id].target_type == ST_TargetsTarget || + // spells[spell_id].target_type == ST_PetMaster //|| + // //spells[spell_id].target_type == ST_AEBard //TODO needed? + //) { + // return false; + //} + + if (IsAESpell(spell_id) || IsPBAENukeSpell(spell_id) || IsPBAESpell(spell_id) || IsAERainSpell(spell_id) || IsAERainNukeSpell(spell_id) || IsAEDurationSpell(spell_id)) { + return true; + } + + return false; +} + +bool IsAESpell(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + switch (spells[spell_id].target_type) { + case ST_TargetOptional: + case ST_GroupTeleport : + case ST_Target: + case ST_Self: + case ST_Animal: + case ST_Undead: + case ST_Summoned: + case ST_Tap: + case ST_Pet: + case ST_Corpse: + case ST_Plant: + case ST_Giant: + case ST_Dragon: + case ST_LDoNChest_Cursed: + case ST_Muramite: + case ST_SummonedPet: + case ST_GroupNoPets: + case ST_Group: + case ST_GroupClientAndPet: + case ST_TargetsTarget: + case ST_PetMaster: + return false; + default: + break; + } + + if ( + spells[spell_id].aoe_range > 0 + ) { + return true; + } + + return false; +} + +bool IsPBAESpell(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + if ( + spell.aoe_range > 0 && + spell.target_type == ST_AECaster + ) { + return true; + } + + return false; +} + +bool IsAERainSpell(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + if ( + spell.aoe_range > 0 && + spell.aoe_duration > 1000 + ) { + return true; + } + + return false; +} + bool IsPartialResistableSpell(uint16 spell_id) { if (!IsValidSpell(spell_id)) { @@ -644,7 +842,9 @@ bool IsGroupSpell(uint16 spell_id) return ( spell.target_type == ST_AEBard || spell.target_type == ST_Group || - spell.target_type == ST_GroupTeleport + spell.target_type == ST_GroupTeleport || + spell.target_type == ST_GroupNoPets || + spell.target_type == ST_GroupClientAndPet ); } @@ -1265,6 +1465,7 @@ bool IsCompleteHealSpell(uint16 spell_id) } return false; + } bool IsFastHealSpell(uint16 spell_id) @@ -1386,20 +1587,61 @@ bool IsRegularSingleTargetHealSpell(uint16 spell_id) return false; } -bool IsRegularGroupHealSpell(uint16 spell_id) +bool IsRegularPetHealSpell(uint16 spell_id) { spell_id = ( IsEffectInSpell(spell_id, SE_CurrentHP) ? spell_id : GetSpellTriggerSpellID(spell_id, SE_CurrentHP) - ); + ); if (!spell_id) { spell_id = ( IsEffectInSpell(spell_id, SE_CurrentHPOnce) ? spell_id : GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce) + ); + } + + if (spell_id) { + if ( + spells[spell_id].target_type == ST_Pet && + !IsCompleteHealSpell(spell_id) && + !IsHealOverTimeSpell(spell_id) && + !IsGroupSpell(spell_id) + ) { + for (int i = 0; i < EFFECT_COUNT; i++) { + if ( + spells[spell_id].base_value[i] > 0 && + spells[spell_id].buff_duration == 0 && + ( + spells[spell_id].effect_id[i] == SE_CurrentHP || + spells[spell_id].effect_id[i] == SE_CurrentHPOnce + ) + ) { + return true; + } + } + } + } + + return false; +} + +bool IsRegularGroupHealSpell(uint16 spell_id) +{ + spell_id = ( + IsEffectInSpell(spell_id, SE_CurrentHP) ? + spell_id : + GetSpellTriggerSpellID(spell_id, SE_CurrentHP) ); + + if (!spell_id) { + spell_id = ( + IsEffectInSpell(spell_id, SE_CurrentHPOnce) ? + spell_id : + GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce) + ); } if (spell_id) { @@ -1415,8 +1657,8 @@ bool IsRegularGroupHealSpell(uint16 spell_id) ( spells[spell_id].effect_id[i] == SE_CurrentHP || spells[spell_id].effect_id[i] == SE_CurrentHPOnce - ) - ) { + ) + ) { return true; } } @@ -1429,9 +1671,14 @@ bool IsRegularGroupHealSpell(uint16 spell_id) bool IsGroupCompleteHealSpell(uint16 spell_id) { if ( - IsGroupSpell(spell_id) && - IsCompleteHealSpell(spell_id) - ) { + ( + spell_id == SPELL_COMPLETE_HEAL || + IsEffectInSpell(spell_id, SE_CompleteHeal) || + IsPercentalHealSpell(spell_id) || + GetSpellTriggerSpellID(spell_id, SE_CompleteHeal) + ) && + IsGroupSpell(spell_id) + ) { return true; } @@ -1441,9 +1688,92 @@ bool IsGroupCompleteHealSpell(uint16 spell_id) bool IsGroupHealOverTimeSpell(uint16 spell_id) { if ( - IsGroupSpell(spell_id) && - IsHealOverTimeSpell(spell_id) && - spells[spell_id].buff_duration < 10 + ( + IsEffectInSpell(spell_id, SE_HealOverTime) || + GetSpellTriggerSpellID(spell_id, SE_HealOverTime) + ) && + IsGroupSpell(spell_id) + ) { + return true; + } + + return false; +} + +bool IsAnyHealSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + if (spell_id == SPELL_NATURES_RECOVERY) { + return false; + } + + //spell_id != SPELL_ADRENALINE_SWELL && + //spell_id != SPELL_ADRENALINE_SWELL_RK2 && + //spell_id != SPELL_ADRENALINE_SWELL_RK3 && + if ( + IsHealOverTimeSpell(spell_id) || + IsGroupHealOverTimeSpell(spell_id) || + IsFastHealSpell(spell_id) || + IsVeryFastHealSpell(spell_id) || + IsRegularSingleTargetHealSpell(spell_id) || + IsRegularGroupHealSpell(spell_id) || + IsCompleteHealSpell(spell_id) || + IsGroupCompleteHealSpell(spell_id) || + IsRegularPetHealSpell(spell_id) + ) { + return true; + } + + return false; +} + +bool IsAnyBuffSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + if ( + spell_id == SPELL_NATURES_RECOVERY || + IsBuffSpell(spell_id) && + IsBeneficialSpell(spell_id) && + !IsBardSong(spell_id) && + !IsEscapeSpell(spell_id) && + (!IsSummonPetSpell(spell_id) && !IsEffectInSpell(spell_id, SE_TemporaryPets)) + ) { + return true; + } + + return false; +} +bool IsDispelSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + if ( + IsEffectInSpell(spell_id, SE_CancelMagic) || + IsEffectInSpell(spell_id, SE_DispelBeneficial) || + IsEffectInSpell(spell_id, SE_DispelBeneficial) + ) { + return true; + } + + return false; +} + +bool IsEscapeSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + if ( + IsInvulnerabilitySpell(spell_id) || + IsEffectInSpell(spell_id, SE_FeignDeath) || + IsEffectInSpell(spell_id, SE_DeathSave) || + IsEffectInSpell(spell_id, SE_Destroy) || + (IsEffectInSpell(spell_id, SE_WipeHateList) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_WipeHateList)] > 0) ) { return true; } @@ -1464,7 +1794,8 @@ bool IsDebuffSpell(uint16 spell_id) IsEffectInSpell(spell_id, SE_CancelMagic) || IsEffectInSpell(spell_id, SE_MovementSpeed) || IsFearSpell(spell_id) || - IsEffectInSpell(spell_id, SE_InstantHate) + IsEffectInSpell(spell_id, SE_InstantHate) || + IsEffectInSpell(spell_id, SE_TossUp) ) { return false; } @@ -1472,6 +1803,22 @@ bool IsDebuffSpell(uint16 spell_id) return true; } +bool IsHateReduxSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + if ( + (IsEffectInSpell(spell_id, SE_InstantHate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_InstantHate)] < 0) || + (IsEffectInSpell(spell_id, SE_Hate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_Hate)] < 0) || + (IsEffectInSpell(spell_id, SE_ReduceHate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_ReduceHate)] < 0) + ) { + return true; + } + + return false; +} + bool IsResistDebuffSpell(uint16 spell_id) { if ( @@ -2383,7 +2730,7 @@ bool AegolismStackingIsSymbolSpell(uint16 spell_id) { if ((i < 2 && spells[spell_id].effect_id[i] != SE_CHA) || i > 3 && spells[spell_id].effect_id[i] != SE_Blank) { - return 0;; + return 0; } if (i == 2 && spells[spell_id].effect_id[i] == SE_TotalHP) { @@ -2430,3 +2777,401 @@ bool AegolismStackingIsArmorClassSpell(uint16 spell_id) { return 0; } + +int8 SpellEffectsCount(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + int8 i = 0; + + for (int i = 0; i < EFFECT_COUNT; i++) { + if (!IsBlankSpellEffect(spell_id, i)) { + ++i; + } + } + + return i; +} + +bool IsLichSpell(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + if ( + GetSpellTargetType(spell_id) == ST_Self && + IsEffectInSpell(spell_id, SE_CurrentMana) && + IsEffectInSpell(spell_id, SE_CurrentHP) && + spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_CurrentMana)] > 0 && + spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_CurrentHP)] < 0 && + spells[spell_id].buff_duration > 0 + ) { + return true; + } + + return false; +} + +bool BOT_SPELL_TYPES_DETRIMENTAL(uint16 spellType, uint8 cls) { + switch (spellType) { + case BotSpellTypes::Nuke: + case BotSpellTypes::Root: + case BotSpellTypes::Lifetap: + case BotSpellTypes::Snare: + case BotSpellTypes::DOT: + case BotSpellTypes::Dispel: + case BotSpellTypes::Mez: + case BotSpellTypes::Charm: + case BotSpellTypes::Slow: + case BotSpellTypes::Debuff: + case BotSpellTypes::HateRedux: + case BotSpellTypes::Fear: + case BotSpellTypes::Stun: + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::AEMez: + case BotSpellTypes::AEStun: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::AESlow: + case BotSpellTypes::AESnare: + case BotSpellTypes::AEFear: + case BotSpellTypes::AEDispel: + case BotSpellTypes::AERoot: + case BotSpellTypes::AEDoT: + case BotSpellTypes::AELifetap: + case BotSpellTypes::PBAENuke: + return true; + case BotSpellTypes::InCombatBuff: + if (cls == Class::ShadowKnight) { + return true; + } + + return false; + default: + return false; + } + + return false; +} + +bool BOT_SPELL_TYPES_BENEFICIAL(uint16 spellType, uint8 cls) { + switch (spellType) { + case BotSpellTypes::RegularHeal: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + case BotSpellTypes::Buff: + case BotSpellTypes::Cure: + case BotSpellTypes::GroupCures: + case BotSpellTypes::DamageShields: + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::OutOfCombatBuffSong: + case BotSpellTypes::Pet: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::PreCombatBuffSong: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::Resurrect: + return true; + case BotSpellTypes::InCombatBuff: + if (cls == Class::ShadowKnight) { + return false; + } + + return true; + default: + return false; + } + + return false; +} + +bool BOT_SPELL_TYPES_OTHER_BENEFICIAL(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::RegularHeal: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + case BotSpellTypes::Buff: + case BotSpellTypes::Cure: + case BotSpellTypes::GroupCures: + case BotSpellTypes::DamageShields: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::ResistBuffs: + return true; + default: + return false; + } + + return false; +} + +bool BOT_SPELL_TYPES_INNATE(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Nuke: + case BotSpellTypes::AEDispel: + case BotSpellTypes::Dispel: + case BotSpellTypes::AERoot: + case BotSpellTypes::Root: + case BotSpellTypes::AESlow: + case BotSpellTypes::Slow: + case BotSpellTypes::Charm: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::Debuff: + case BotSpellTypes::AEDoT: + case BotSpellTypes::DOT: + case BotSpellTypes::AELifetap: + case BotSpellTypes::Lifetap: + case BotSpellTypes::AEStun: + case BotSpellTypes::Stun: + case BotSpellTypes::AEMez: + case BotSpellTypes::Mez: + return true; + default: + return false; + } + + return false; +} + +bool IsBotSpellType(uint16 spellType) { + if (BOT_SPELL_TYPES_DETRIMENTAL(spellType) && BOT_SPELL_TYPES_BENEFICIAL(spellType) && BOT_SPELL_TYPES_INNATE(spellType)) { + return true; + } + + return false; +} + +bool IsAEBotSpellType(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::AEDebuff: + case BotSpellTypes::AEFear: + case BotSpellTypes::AEMez: + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::AESlow: + case BotSpellTypes::AESnare: + case BotSpellTypes::AEStun: + case BotSpellTypes::AEDispel: + case BotSpellTypes::AEDoT: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::AELifetap: + case BotSpellTypes::AERoot: + return true; + default: + return false; + } + + return false; +} + +bool IsGroupBotSpellType(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::GroupCures: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + return true; + default: + return false; + } + + return false; +} + +bool IsGroupTargetOnlyBotSpellType(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::GroupCures: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::GroupHeals: + return true; + default: + return false; + } + + return false; +} + +bool IsPetBotSpellType(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::PetBuffs: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + return true; + default: + return false; + } + + return false; +} + +bool IsClientBotSpellType(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::RegularHeal: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + case BotSpellTypes::Buff: + case BotSpellTypes::Cure: + case BotSpellTypes::GroupCures: + case BotSpellTypes::DamageShields: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::ResistBuffs: + return true; + default: + return false; + } + + return false; +} + +bool IsHealBotSpellType(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::FastHeals: + case BotSpellTypes::RegularHeal: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + return true; + default: + return false; + } + + return false; +} + +bool SpellTypeRequiresLoS(uint16 spellType, uint16 cls) { + if (IsAEBotSpellType(spellType)) { // These gather their own targets later + return false; + } + + switch (spellType) { + case BotSpellTypes::RegularHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + return false; + case BotSpellTypes::InCombatBuff: + if (cls && cls == Class::ShadowKnight) { + return true; + } + + return false; + default: + return true; + } + + return true; +} + +bool SpellTypeRequiresTarget(uint16 spellType, uint16 cls) { + switch (spellType) { + case BotSpellTypes::Escape: + if (cls == Class::ShadowKnight) { + return false; + } + + return true; + case BotSpellTypes::Pet: + return false; + default: + return true; + } + + return true; +} + +bool IsValidSpellAndLoS(uint32 spell_id, bool hasLoS) { + if (!IsValidSpell(spell_id)) { + return false; + } + + if (!hasLoS && IsTargetRequiredForSpell(spell_id)) { + return false; + } + + return true; +} + +bool IsInstantHealSpell(uint32 spell_id) { + if (IsRegularSingleTargetHealSpell(spell_id) || IsRegularGroupHealSpell(spell_id) || IsRegularPetHealSpell(spell_id) || IsRegularGroupHealSpell(spell_id) || spell_id == SPELL_COMPLETE_HEAL) { + return true; + } + + return false; +} + +bool IsResurrectSpell(uint16 spell_id) +{ + return IsEffectInSpell(spell_id, SE_Revive); +} + +bool RequiresStackCheck(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::FastHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::RegularHeal: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::GroupCompleteHeals: + return false; + default: + return true; + } + + return true; +} diff --git a/common/spdat.h b/common/spdat.h index d597c79fa..432774b95 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -213,6 +213,10 @@ #define SPELL_BLOODTHIRST 8476 #define SPELL_AMPLIFICATION 2603 #define SPELL_DIVINE_REZ 2738 +#define SPELL_NATURES_RECOVERY 2520 +#define SPELL_ADRENALINE_SWELL 14445 +#define SPELL_ADRENALINE_SWELL_RK2 14446 +#define SPELL_ADRENALINE_SWELL_RK3 14447 // discipline IDs. #define DISC_UNHOLY_AURA 4520 @@ -647,14 +651,84 @@ enum SpellTypes : uint32 SpellType_PreCombatBuffSong = (1 << 21) }; -const uint32 SPELL_TYPE_MIN = (SpellType_Nuke << 1) - 1; -const uint32 SPELL_TYPE_MAX = (SpellType_PreCombatBuffSong << 1) - 1; -const uint32 SPELL_TYPE_ANY = 0xFFFFFFFF; +namespace BotSpellTypes +{ + constexpr uint16 Nuke = 0; + constexpr uint16 RegularHeal = 1; + constexpr uint16 Root = 2; + constexpr uint16 Buff = 3; + constexpr uint16 Escape = 4; + constexpr uint16 Pet = 5; + constexpr uint16 Lifetap = 6; + constexpr uint16 Snare = 7; + constexpr uint16 DOT = 8; + constexpr uint16 Dispel = 9; + constexpr uint16 InCombatBuff = 10; + constexpr uint16 Mez = 11; + constexpr uint16 Charm = 12; + constexpr uint16 Slow = 13; + constexpr uint16 Debuff = 14; + constexpr uint16 Cure = 15; + constexpr uint16 Resurrect = 16; + constexpr uint16 HateRedux = 17; + constexpr uint16 InCombatBuffSong = 18; + constexpr uint16 OutOfCombatBuffSong = 19; + constexpr uint16 PreCombatBuff = 20; + constexpr uint16 PreCombatBuffSong = 21; + constexpr uint16 Fear = 22; + constexpr uint16 Stun = 23; + constexpr uint16 GroupCures = 24; + constexpr uint16 CompleteHeal = 25; + constexpr uint16 FastHeals = 26; + constexpr uint16 VeryFastHeals = 27; + constexpr uint16 GroupHeals = 28; + constexpr uint16 GroupCompleteHeals = 29; + constexpr uint16 GroupHoTHeals = 30; + constexpr uint16 HoTHeals = 31; + constexpr uint16 AENukes = 32; + constexpr uint16 AERains = 33; + constexpr uint16 AEMez = 34; + constexpr uint16 AEStun = 35; + constexpr uint16 AEDebuff = 36; + constexpr uint16 AESlow = 37; + constexpr uint16 AESnare = 38; + constexpr uint16 AEFear = 39; + constexpr uint16 AEDispel = 40; + constexpr uint16 AERoot = 41; + constexpr uint16 AEDoT = 42; + constexpr uint16 AELifetap = 43; + constexpr uint16 PBAENuke = 44; + constexpr uint16 PetBuffs = 45; + constexpr uint16 PetRegularHeals = 46; + constexpr uint16 PetCompleteHeals = 47; + constexpr uint16 PetFastHeals = 48; + constexpr uint16 PetVeryFastHeals = 49; + constexpr uint16 PetHoTHeals = 50; + constexpr uint16 DamageShields = 51; + constexpr uint16 ResistBuffs = 52; + + constexpr uint16 START = BotSpellTypes::Nuke; // Do not remove or change this + constexpr uint16 END = BotSpellTypes::ResistBuffs; // Do not remove this, increment as needed +} const uint32 SPELL_TYPES_DETRIMENTAL = (SpellType_Nuke | SpellType_Root | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Charm | SpellType_Debuff | SpellType_Slow); const uint32 SPELL_TYPES_BENEFICIAL = (SpellType_Heal | SpellType_Buff | SpellType_Escape | SpellType_Pet | SpellType_InCombatBuff | SpellType_Cure | SpellType_HateRedux | SpellType_InCombatBuffSong | SpellType_OutOfCombatBuffSong | SpellType_PreCombatBuff | SpellType_PreCombatBuffSong); const uint32 SPELL_TYPES_INNATE = (SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root); +bool BOT_SPELL_TYPES_DETRIMENTAL (uint16 spellType, uint8 cls = 0); +bool BOT_SPELL_TYPES_BENEFICIAL (uint16 spellType, uint8 cls = 0); +bool BOT_SPELL_TYPES_OTHER_BENEFICIAL(uint16 spellType); +bool BOT_SPELL_TYPES_INNATE (uint16 spellType); +bool IsBotSpellType (uint16 spellType); +bool IsAEBotSpellType(uint16 spellType); +bool IsGroupBotSpellType(uint16 spellType); +bool IsGroupTargetOnlyBotSpellType(uint16 spellType); +bool IsPetBotSpellType(uint16 spellType); +bool IsClientBotSpellType(uint16 spellType); +bool IsHealBotSpellType(uint16 spellType); +bool SpellTypeRequiresLoS(uint16 spellType, uint16 cls = 0); +bool SpellTypeRequiresTarget(uint16 spellType, uint16 cls = 0); + // These should not be used to determine spell category.. // They are a graphical affects (effects?) index only // TODO: import sai list @@ -1503,6 +1577,7 @@ bool IsTargetableAESpell(uint16 spell_id); bool IsSacrificeSpell(uint16 spell_id); bool IsLifetapSpell(uint16 spell_id); bool IsMesmerizeSpell(uint16 spell_id); +bool SpellBreaksMez(uint16 spell_id); bool IsStunSpell(uint16 spell_id); bool IsSlowSpell(uint16 spell_id); bool IsHasteSpell(uint16 spell_id); @@ -1536,6 +1611,11 @@ bool IsPureNukeSpell(uint16 spell_id); bool IsAENukeSpell(uint16 spell_id); bool IsPBAENukeSpell(uint16 spell_id); bool IsAERainNukeSpell(uint16 spell_id); +bool IsAnyNukeOrStunSpell(uint16 spell_id); +bool IsAnyAESpell(uint16 spell_id); +bool IsAESpell(uint16 spell_id); +bool IsPBAESpell(uint16 spell_id); +bool IsAERainSpell(uint16 spell_id); bool IsPartialResistableSpell(uint16 spell_id); bool IsResistableSpell(uint16 spell_id); bool IsGroupSpell(uint16 spell_id); @@ -1545,8 +1625,11 @@ bool IsEffectInSpell(uint16 spell_id, int effect_id); uint16 GetSpellTriggerSpellID(uint16 spell_id, int effect_id); bool IsBlankSpellEffect(uint16 spell_id, int effect_index); bool IsValidSpell(uint32 spell_id); +bool IsValidSpellAndLoS(uint32 spell_id, bool hasLoS = true); bool IsSummonSpell(uint16 spell_id); bool IsDamageSpell(uint16 spell_id); +bool IsAnyDamageSpell(uint16 spell_id); +bool IsDamageOverTimeSpell(uint16 spell_i); bool IsFearSpell(uint16 spell_id); bool IsCureSpell(uint16 spell_id); bool IsHarmTouchSpell(uint16 spell_id); @@ -1585,10 +1668,16 @@ bool IsCompleteHealSpell(uint16 spell_id); bool IsFastHealSpell(uint16 spell_id); bool IsVeryFastHealSpell(uint16 spell_id); bool IsRegularSingleTargetHealSpell(uint16 spell_id); +bool IsRegularPetHealSpell(uint16 spell_id); bool IsRegularGroupHealSpell(uint16 spell_id); bool IsGroupCompleteHealSpell(uint16 spell_id); bool IsGroupHealOverTimeSpell(uint16 spell_id); +bool IsAnyHealSpell(uint16 spell_id); +bool IsAnyBuffSpell(uint16 spell_id); +bool IsDispelSpell(uint16 spell_id); +bool IsEscapeSpell(uint16 spell_id); bool IsDebuffSpell(uint16 spell_id); +bool IsHateReduxSpell(uint16 spell_id); bool IsResistDebuffSpell(uint16 spell_id); bool IsSelfConversionSpell(uint16 spell_id); bool IsBuffSpell(uint16 spell_id); @@ -1628,5 +1717,10 @@ bool IsCastRestrictedSpell(uint16 spell_id); bool IsAegolismSpell(uint16 spell_id); bool AegolismStackingIsSymbolSpell(uint16 spell_id); bool AegolismStackingIsArmorClassSpell(uint16 spell_id); +int8 SpellEffectsCount(uint16 spell_id); +bool IsLichSpell(uint16 spell_id); +bool IsInstantHealSpell(uint32 spell_id); +bool IsResurrectSpell(uint16 spell_id); +bool RequiresStackCheck(uint16 spellType); #endif diff --git a/common/version.h b/common/version.h index fbbcd0a7f..7cf293612 100644 --- a/common/version.h +++ b/common/version.h @@ -43,7 +43,7 @@ */ #define CURRENT_BINARY_DATABASE_VERSION 9284 -#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9045 +#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9049 //TODO update as needed #endif diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 1bc7efa76..1a8c7b929 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -1278,6 +1278,39 @@ bool Mob::CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarge return zone->zonemap->CheckLoS(posWatcher, posTarget); } +bool Mob::CheckPositioningLosFN(Mob* other, float posX, float posY, float posZ) { + if (zone->zonemap == nullptr) { + //not sure what the best return is on error + //should make this a database variable, but im lazy today +#ifdef LOS_DEFAULT_CAN_SEE + return(true); +#else + return(false); +#endif + } + + if (!other) { + return(true); + } + glm::vec3 myloc; + glm::vec3 oloc; + +#define LOS_DEFAULT_HEIGHT 6.0f + + oloc.x = other->GetX(); + oloc.y = other->GetY(); + oloc.z = other->GetZ() + (other->GetSize() == 0.0 ? LOS_DEFAULT_HEIGHT : other->GetSize()) / 2 * SEE_POSITION; + + myloc.x = posX; + myloc.y = posY; + myloc.z = posZ + (GetSize() == 0.0 ? LOS_DEFAULT_HEIGHT : GetSize()) / 2 * HEAD_POSITION; + +#if LOSDEBUG>=5 + LogDebug("LOS from ([{}], [{}], [{}]) to ([{}], [{}], [{}]) sizes: ([{}], [{}])", myloc.x, myloc.y, myloc.z, oloc.x, oloc.y, oloc.z, GetSize(), mobSize); +#endif + return zone->zonemap->CheckLoS(myloc, oloc); +} + //offensive spell aggro int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool is_proc) { @@ -1659,3 +1692,89 @@ void Mob::RogueEvade(Mob *other) return; } +bool Mob::DoLosChecks(Mob* who, Mob* other) { + if (!who->CheckLosFN(other) || !who->CheckWaterLoS(other)) { + if (who->CheckLosCheatExempt(who, other)) { + return true; + } + + if (CheckLosCheat(who, other)) { + return true; + } + + return false; + } + + return true; +} + +bool Mob::CheckLosCheat(Mob* who, Mob* other) { + if (RuleB(Map, CheckForLoSCheat)) { + auto& door_list = entity_list.GetDoorsList(); + for (auto itr : door_list) { + Doors* door = itr.second; + if (door && !door->IsDoorOpen() && (door->GetTriggerType() == 255 || door->GetLockpick() != 0 || door->GetKeyItem() != 0 || door->GetNoKeyring() != 0)) { + if (DistanceNoZ(who->GetPosition(), door->GetPosition()) <= 50) { + auto who_to_door = DistanceNoZ(who->GetPosition(), door->GetPosition()); + auto other_to_door = DistanceNoZ(other->GetPosition(), door->GetPosition()); + auto who_to_other = DistanceNoZ(who->GetPosition(), other->GetPosition()); + auto distance_difference = who_to_other - (who_to_door + other_to_door); + if (distance_difference >= (-1 * RuleR(Maps, RangeCheckForLoSCheat)) && distance_difference <= RuleR(Maps, RangeCheckForLoSCheat)) { + LogTestDebug("CheckLosCheat failed at Door [{}], TriggerType [{}], GetLockpick [{}], GetKeyItem [{}], GetNoKeyring [{}]", door->GetDoorID(), door->GetTriggerType(), door->GetLockpick(), door->GetKeyItem(), door->GetNoKeyring()); //deleteme + return false; + } + } + } + } + } + + //if (RuleB(Map, CheckForLoSCheat)) { + // uint8 zone_id = zone->GetZoneID(); + // // ZoneID, target XYZ, my range from target + // //float zone_basic_checks[] = { 6, 36 }; + // //float zone_basic_x_coord[] = { -295, -179.908 }; + // //float zone_basic_y_coord[] = { -18, -630.708 }; + // //float zone_basic_y_coord[] = { 50.97, -69.971 }; + // //float zone_basic_range_check[] = { 21, 10 }; + // //if door and target infront, fail + // //if door and target behind, fail + // + // if (zone_id == 103) { + // Doors* door_to_check = entity_list.FindDoor(8); + // TestDebug("Entered LoSCheat for ZoneID: [{}]", zone_id); //deleteme + // glm::vec4 who_check; who_check.x = 1202; who_check.y = 559; who_check.z = -158.94; + // glm::vec4 other_check; other_check.x = 1291; other_check.y = 559; other_check.z = -158.19; + // float my_distance = DistanceNoZ(who->GetPosition(), who_check); + // float tar_distance = DistanceNoZ(other->GetPosition(), other_check); + // float my_range = 16; + // float tar_range = 75; + // if (my_distance <= my_range && tar_distance <= tar_range && !quest_manager.isdooropen(8)) { + // TestDebug("Door is NOT open"); //deleteme + // TestDebug("LoSCheat failed"); //deleteme + // return false; + // } + // TestDebug("LoS Check for ZoneID: [{}] was [{}] units for [{}], [{}] units for [{}]", zone_id, my_distance, who->GetCleanName(), tar_distance, other->GetCleanName()); //deleteme + // } + //} + return true; +} + +bool Mob::CheckLosCheatExempt(Mob* who, Mob* other) { + if (RuleB(Map, EnableLoSCheatExemptions)) { + glm::vec4 exempt_check_who; + glm::vec4 exempt_check_other; + /* This is an exmaple of how to configure exemptions for LoS checks. + if (zone->GetZoneID() == 222) { //PoEarthB + exempt_check_who.x = 2051; exempt_check_who.y = 407; exempt_check_who.z = -219; //Middle of councilman spawns + //check to be sure the player and the target are in the pit to PoEarthB + //if the player is inside the cove they cannot be higher than the ceiling (no exploiting from uptop) + //otherwise they can pass LoS checks even if they don't have true LoS + if (who->GetZ() <= -171 && other->GetZ() <= -171 && DistanceNoZ(other->GetPosition(), exempt_check_who) <= 800 && DistanceNoZ(who->GetPosition(), exempt_check_who) <= 800) { + return true; + } + } + */ + } + + return false; +} diff --git a/zone/attack.cpp b/zone/attack.cpp index 6ffe44099..4a2e03bbf 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1246,7 +1246,12 @@ int64 Mob::GetWeaponDamage(Mob *against, const EQ::ItemInstance *weapon_item, in return 0; } - if (!weapon_item->IsClassEquipable(GetClass())) { + if (!weapon_item->IsClassEquipable(GetClass()) && + ( + !IsBot() || + (IsBot() && !RuleB(Bots, AllowBotEquipAnyClassGear)) + ) + ) { return 0; } diff --git a/zone/bot.cpp b/zone/bot.cpp index ce7edcc82..f634eb43e 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -69,23 +69,20 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm RestRegenHP = 0; RestRegenMana = 0; RestRegenEndurance = 0; - m_enforce_spell_settings = false; - m_bot_archery_setting = false; - m_expansion_bitmask = -1; - m_bot_caster_range = 0; + SetBotID(0); SetBotSpellID(0); SetSpawnStatus(false); - SetBotCharmer(false); - SetPetChooser(false); - SetRangerAutoWeaponSelect(false); - SetTaunting(GetClass() == Class::Warrior || GetClass() == Class::Paladin || GetClass() == Class::ShadowKnight); + SetBotCharmer(false); SetDefaultBotStance(); + SetTaunting((GetClass() == Class::Warrior || GetClass() == Class::Paladin || GetClass() == Class::ShadowKnight) && (GetBotStance() == Stance::Aggressive)); - SetAltOutOfCombatBehavior(GetClass() == Class::Bard); // will need to be updated if more classes make use of this flag - SetShowHelm(true); SetPauseAI(false); + m_combat_jitter_timer.Disable(); + auto_save_timer.Disable(); + m_rogue_evade_timer.Disable(); + m_monk_evade_timer.Disable(); m_auto_defend_timer.Disable(); SetGuardFlag(false); SetHoldFlag(false); @@ -98,12 +95,12 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm m_previous_pet_order = SPO_Guard; rest_timer.Disable(); - ping_timer.Disable(); - SetFollowDistance(BOT_FOLLOW_DISTANCE_DEFAULT); - if (IsCasterClass(GetClass())) - SetStopMeleeLevel((uint8)RuleI(Bots, CasterStopMeleeLevel)); - else - SetStopMeleeLevel(255); + ping_timer.Disable(); + + LoadDefaultBotSettings(); + SetCastedSpellType(UINT16_MAX); + SetCommandedSpell(false); + //DisableBotSpellTimers(); // Do this once and only in this constructor GenerateAppearance(); @@ -131,8 +128,7 @@ Bot::Bot( uint32 botSpellsID, double totalPlayTime, uint32 lastZoneId, - NPCType *npcTypeData, - int32 expansion_bitmask + NPCType *npcTypeData ) : NPC(npcTypeData, nullptr, glm::vec4(), Ground, false), rest_timer(1), ping_timer(1) { @@ -175,13 +171,12 @@ Bot::Bot( RestRegenHP = 0; RestRegenMana = 0; RestRegenEndurance = 0; - m_expansion_bitmask = expansion_bitmask; SetBotID(botID); SetBotSpellID(botSpellsID); SetSpawnStatus(false); SetBotCharmer(false); - SetPetChooser(false); - SetRangerAutoWeaponSelect(false); + SetCastedSpellType(UINT16_MAX); + SetCommandedSpell(false); bool stance_flag = false; if (!database.botdb.LoadStance(this, stance_flag) && bot_owner) { @@ -207,6 +202,10 @@ Bot::Bot( SetTaunting((GetClass() == Class::Warrior || GetClass() == Class::Paladin || GetClass() == Class::ShadowKnight) && (GetBotStance() == Stance::Aggressive)); SetPauseAI(false); + m_combat_jitter_timer.Disable(); + auto_save_timer.Disable(); + m_rogue_evade_timer.Disable(); + m_monk_evade_timer.Disable(); m_auto_defend_timer.Disable(); SetGuardFlag(false); SetHoldFlag(false); @@ -220,11 +219,6 @@ Bot::Bot( rest_timer.Disable(); ping_timer.Disable(); - SetFollowDistance(BOT_FOLLOW_DISTANCE_DEFAULT); - if (IsCasterClass(GetClass())) - SetStopMeleeLevel((uint8)RuleI(Bots, CasterStopMeleeLevel)); - else - SetStopMeleeLevel(255); strcpy(name, GetCleanName()); @@ -235,7 +229,10 @@ Bot::Bot( EquipBot(); if (GetClass() == Class::Rogue) { - m_evade_timer.Start(); + m_rogue_evade_timer.Start(); + } + if (GetClass() == Class::Monk) { + m_monk_evade_timer.Start(); } m_CastingRoles.GroupHealer = false; @@ -251,6 +248,10 @@ Bot::Bot( LoadAAs(); + LoadDefaultBotSettings(); + + database.botdb.LoadBotSettings(this); + if (database.botdb.LoadBuffs(this)) { //reapply some buffs uint32 buff_count = GetMaxBuffSlots(); @@ -269,6 +270,10 @@ Bot::Bot( switch (spell.effect_id[x1]) { case SE_IllusionCopy: case SE_Illusion: { + if (GetIllusionBlock()) { + break; + } + if (spell.base_value[x1] == -1) { if (gender == Gender::Female) { gender = Gender::Male; @@ -538,39 +543,51 @@ void Bot::SetSuffix(std::string_view bot_suffix) { } } -uint32 Bot::GetBotArcheryRange() { +uint32 Bot::GetBotRangedValue() { const EQ::ItemInstance *range_inst = GetBotItem(EQ::invslot::slotRange); const EQ::ItemInstance *ammo_inst = GetBotItem(EQ::invslot::slotAmmo); - if (!range_inst || !ammo_inst) + if (!range_inst) return 0; const EQ::ItemData *range_item = range_inst->GetItem(); - const EQ::ItemData *ammo_item = ammo_inst->GetItem(); - if (!range_item || !ammo_item || range_item->ItemType != EQ::item::ItemTypeBow || ammo_item->ItemType != EQ::item::ItemTypeArrow) - return 0; + const EQ::ItemData *ammo_item = nullptr; + + if (ammo_inst) { + ammo_item = ammo_inst->GetItem(); + } - // everything is good! - return (range_item->Range + ammo_item->Range); + // Bow requires arrows + if (range_item && range_item->ItemType == EQ::item::ItemTypeBow && (!ammo_item || ammo_item->ItemType != EQ::item::ItemTypeArrow)) { + return 0; + } + + // Throwing items + if (range_item && (range_item->ItemType == EQ::item::ItemTypeSmallThrowing || range_item->ItemType == EQ::item::ItemTypeLargeThrowing)) { + return range_item->Range; + } + + // Bows and arrows + if (range_item && ammo_item && range_item->ItemType == EQ::item::ItemTypeBow && ammo_item->ItemType == EQ::item::ItemTypeArrow) { + return (range_item->Range + ammo_item->Range); + } + + return 0; } -void Bot::ChangeBotArcherWeapons(bool isArcher) { - if ((GetClass()==Class::Warrior) || (GetClass()==Class::Paladin) || (GetClass()==Class::Ranger) || (GetClass()==Class::ShadowKnight) || (GetClass()==Class::Rogue)) { - if (!isArcher) { - BotAddEquipItem(EQ::invslot::slotPrimary, GetBotItemBySlot(EQ::invslot::slotPrimary)); - BotAddEquipItem(EQ::invslot::slotSecondary, GetBotItemBySlot(EQ::invslot::slotSecondary)); - SetAttackTimer(); - BotGroupSay(this, "My blade is ready"); - } else { - BotRemoveEquipItem(EQ::invslot::slotPrimary); - BotRemoveEquipItem(EQ::invslot::slotSecondary); - BotAddEquipItem(EQ::invslot::slotAmmo, GetBotItemBySlot(EQ::invslot::slotAmmo)); - BotAddEquipItem(EQ::invslot::slotSecondary, GetBotItemBySlot(EQ::invslot::slotRange)); - SetAttackTimer(); - BotGroupSay(this, "My bow is true and ready"); - } +void Bot::ChangeBotRangedWeapons(bool isRanged) { + if (!isRanged) { + BotAddEquipItem(EQ::invslot::slotPrimary, GetBotItemBySlot(EQ::invslot::slotPrimary)); + BotAddEquipItem(EQ::invslot::slotSecondary, GetBotItemBySlot(EQ::invslot::slotSecondary)); + SetAttackTimer(); + BotGroupSay(this, "My blade is ready"); + } else { + BotRemoveEquipItem(EQ::invslot::slotPrimary); + BotRemoveEquipItem(EQ::invslot::slotSecondary); + BotAddEquipItem(EQ::invslot::slotAmmo, GetBotItemBySlot(EQ::invslot::slotAmmo)); + BotAddEquipItem(EQ::invslot::slotSecondary, GetBotItemBySlot(EQ::invslot::slotRange)); + SetAttackTimer(); + BotGroupSay(this, "My bow is true and ready"); //TODO bot rewrite - make this say throwing or bow } - else - BotGroupSay(this, "I don't know how to use a bow"); } void Bot::Sit() { @@ -1304,10 +1321,13 @@ bool Bot::IsValidName() bool Bot::IsValidName(std::string& name) { - if (name.length() < 4) + if (name.length() < 4 || name.length() > 15) { return false; - if (!isupper(name[0])) + } + + if (!isupper(name[0])) { return false; + } for (char c : name.substr(1)) { if (!RuleB(Bots, AllowCamelCaseNames) && !islower(c)) { @@ -1344,6 +1364,7 @@ bool Bot::Save() database.botdb.SaveBuffs(this); database.botdb.SaveTimers(this); database.botdb.SaveStance(this); + database.botdb.SaveBotSettings(this); if (!SavePet()) bot_owner->Message(Chat::White, "Failed to save pet for '%s'", GetCleanName()); @@ -1389,24 +1410,30 @@ bool Bot::DeleteBot() RemoveBotFromRaid(this); } - if (!database.botdb.DeleteItems(GetBotID())) { - return false; - } + if (!RuleB(Bots, BotSoftDeletes)) { + if (!database.botdb.DeleteItems(GetBotID())) { + return false; + } - if (!database.botdb.DeleteTimers(GetBotID())) { - return false; - } + if (!database.botdb.DeleteTimers(GetBotID())) { + return false; + } - if (!database.botdb.DeleteBuffs(GetBotID())) { - return false; - } + if (!database.botdb.DeleteBuffs(GetBotID())) { + return false; + } - if (!database.botdb.DeleteStance(GetBotID())) { - return false; - } + if (!database.botdb.DeleteStance(GetBotID())) { + return false; + } - if (!database.botdb.DeleteBot(GetBotID())) { - return false; + if (!database.botdb.DeleteBotSettings(GetBotID())) { + return false; + } + + if (!database.botdb.DeleteBot(GetBotID())) { + return false; + } } return true; @@ -1705,94 +1732,137 @@ void Bot::AI_Bot_Init() } void Bot::SpellProcess() { - if (spellend_timer.Check(false)) { + if (spellend_timer.Check(false)) { NPC::SpellProcess(); if (GetClass() == Class::Bard && casting_spell_id != 0) casting_spell_id = 0; } } void Bot::BotMeditate(bool isSitting) { - if (isSitting) { - if (GetManaRatio() < 99.0f || GetHPRatio() < 99.0f) { - if (!IsEngaged() && !IsSitting()) { - Sit(); - } - } else { - if (IsSitting()) { - Stand(); - } + if (GetManaRatio() < GetManaWhenToMed() || (GetHPRatio() < GetHPWhenToMed() && GetLevel() < GetStopMeleeLevel())) { + if ((!IsEngaged() || (IsEngaged() && GetMedInCombat() && !HasTargetReflection())) && !isSitting) { + Sit(); } - } else { - if (IsSitting()) { + } + else { + if (isSitting) { Stand(); } } } -void Bot::BotRangedAttack(Mob* other) { - //make sure the attack and ranged timers are up - //if the ranged timer is disabled, then they have no ranged weapon and shouldent be attacking anyhow - if ((attack_timer.Enabled() && !attack_timer.Check(false)) || (ranged_timer.Enabled() && !ranged_timer.Check())) { - LogCombatDetail("Bot Archery attack canceled. Timer not up. Attack [{}] ranged [{}]", attack_timer.GetRemainingTime(), ranged_timer.GetRemainingTime()); +void Bot::BotRangedAttack(Mob* other, bool CanDoubleAttack) { + if (!TargetValidation(other)) { return; } + + if (!CanDoubleAttack && ((attack_timer.Enabled() && !attack_timer.Check(false)) || (ranged_timer.Enabled() && !ranged_timer.Check()))) { + LogCombatDetail("Bot ranged attack canceled. Timer not up. Attack [{}] ranged [{}]", attack_timer.GetRemainingTime(), ranged_timer.GetRemainingTime()); Message(0, "Error: Timer not up. Attack %d, ranged %d", attack_timer.GetRemainingTime(), ranged_timer.GetRemainingTime()); return; } const auto rangedItem = GetBotItem(EQ::invslot::slotRange); const EQ::ItemData* RangeWeapon = nullptr; - if (rangedItem) + if (rangedItem) { RangeWeapon = rangedItem->GetItem(); + } + + if (!RangeWeapon) { + return; + } const auto ammoItem = GetBotItem(EQ::invslot::slotAmmo); const EQ::ItemData* Ammo = nullptr; if (ammoItem) Ammo = ammoItem->GetItem(); - if (!RangeWeapon || !Ammo) + // Bow requires arrows + if ( + !Ammo || + (RangeWeapon && + ( + (RangeWeapon->ItemType != EQ::item::ItemTypeBow && RangeWeapon->ItemType != EQ::item::ItemTypeSmallThrowing && RangeWeapon->ItemType != EQ::item::ItemTypeLargeThrowing) || + (RangeWeapon->ItemType == EQ::item::ItemTypeBow && (Ammo->ItemType != EQ::item::ItemTypeArrow)) || + ( + (RangeWeapon->ItemType == EQ::item::ItemTypeSmallThrowing || RangeWeapon->ItemType == EQ::item::ItemTypeLargeThrowing) && + ammoItem->GetCharges() < 1 || + ( + (RuleI(Bots, StackSizeMin) != -1 && rangedItem->GetCharges() != RangeWeapon->StackSize) || + rangedItem->GetCharges() < RuleI(Bots, StackSizeMin) + ) + ) + ) + ) + ) { + if (!Ammo || ammoItem->GetCharges() < 1) { + GetOwner()->Message(Chat::Yellow, "I do not have enough any ammo."); + } + return; + } - LogCombatDetail("Shooting [{}] with bow [{}] ([{}]) and arrow [{}] ([{}])", other->GetCleanName(), RangeWeapon->Name, RangeWeapon->ID, Ammo->Name, Ammo->ID); - if (!IsAttackAllowed(other) || IsCasting() || DivineAura() || IsStunned() || IsMezzed() || (GetAppearance() == eaDead)) + LogCombatDetail("Ranged attacking [{}] with {} [{}] ([{}]){}{}{}", + other->GetCleanName(), + (RangeWeapon->ItemType == EQ::item::ItemTypeBow ? "bow" : "throwing"), + RangeWeapon->Name, + RangeWeapon->ID, + (Ammo && Ammo->ItemType == EQ::item::ItemTypeArrow ? " and arrow " : ""), + (Ammo && Ammo->ItemType == EQ::item::ItemTypeArrow ? Ammo->Name : ""), + (Ammo && Ammo->ItemType == EQ::item::ItemTypeArrow ? std::to_string(Ammo->ID) : "") + ); + + if (!IsAttackAllowed(other) || IsCasting() || DivineAura() || IsStunned() || IsMezzed() || (GetAppearance() == eaDead)) { return; - - SendItemAnimation(other, Ammo, EQ::skills::SkillArchery); - DoArcheryAttackDmg(other, rangedItem, ammoItem); // watch - - //break invis when you attack - if (invisible) { - LogCombatDetail("Removing invisibility due to melee attack"); - BuffFadeByEffect(SE_Invisibility); - BuffFadeByEffect(SE_Invisibility2); - invisible = false; } - if (invisible_undead) { - LogCombatDetail("Removing invisibility vs. undead due to melee attack"); - BuffFadeByEffect(SE_InvisVsUndead); - BuffFadeByEffect(SE_InvisVsUndead2); - invisible_undead = false; + SendItemAnimation(other, Ammo, (RangeWeapon->ItemType == EQ::item::ItemTypeBow ? EQ::skills::SkillArchery : EQ::skills::SkillThrowing)); + if (RangeWeapon->ItemType == EQ::item::ItemTypeBow) { + DoArcheryAttackDmg(other, rangedItem, ammoItem); // watch + //EndlessQuiver AA base1 = 100% Chance to avoid consumption arrow. + int ChanceAvoidConsume = aabonuses.ConsumeProjectile + itembonuses.ConsumeProjectile + spellbonuses.ConsumeProjectile; + + // Consume Ammo, unless Ammo Consumption is disabled or player has Endless Quiver + bool consumes_ammo = RuleB(Bots, BotArcheryConsumesAmmo); + if ( + consumes_ammo && + ( + RangeWeapon->ExpendableArrow || + !ChanceAvoidConsume || + (ChanceAvoidConsume < 100 && zone->random.Int(0, 99) > ChanceAvoidConsume) + ) + ) { + ammoItem->SetCharges((ammoItem->GetCharges() - 1)); + LogCombat("Consumed Archery Ammo from slot {}.", EQ::invslot::slotAmmo); + + if (ammoItem->GetCharges() < 1) { + RemoveBotItemBySlot(EQ::invslot::slotAmmo); + BotRemoveEquipItem(EQ::invslot::slotAmmo); + } + } + else if (!consumes_ammo) { + LogCombat("Archery Ammo Consumption is disabled."); + } + else { + LogCombat("Endless Quiver prevented Ammo Consumption."); + } + } + else { + DoThrowingAttackDmg(other, rangedItem); // watch + // Consume Ammo, unless Ammo Consumption is disabled + if (RuleB(Bots, BotThrowingConsumesAmmo)) { + ammoItem->SetCharges((ammoItem->GetCharges() - 1)); + LogCombat("Consumed Throwing Ammo from slot {}.", EQ::invslot::slotAmmo); + + if (ammoItem->GetCharges() < 1) { + RemoveBotItemBySlot(EQ::invslot::slotAmmo); + BotRemoveEquipItem(EQ::invslot::slotAmmo); + } + } + else { + LogCombat("Throwing Ammo Consumption is disabled."); + } } - if (invisible_animals) { - LogCombatDetail("Removing invisibility vs. animals due to melee attack"); - BuffFadeByEffect(SE_InvisVsAnimals); - invisible_animals = false; - } - - if (spellbonuses.NegateIfCombat) - BuffFadeByEffect(SE_NegateIfCombat); - - if (hidden || improved_hidden) { - hidden = false; - improved_hidden = false; - auto outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); - auto sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; - sa_out->spawn_id = GetID(); - sa_out->type = 0x03; - sa_out->parameter = 0; - entity_list.QueueClients(this, outapp, true); - safe_delete(outapp); - } + CommonBreakInvisibleFromCombat(); } bool Bot::CheckBotDoubleAttack(bool tripleAttack) { @@ -1829,6 +1899,53 @@ bool Bot::CheckBotDoubleAttack(bool tripleAttack) { return false; } +bool Bot::CheckTripleAttack() +{ + int chance; + + if (RuleB(Combat, ClassicTripleAttack)) { + if ( + GetLevel() >= 60 && + ( + GetClass() == Class::Warrior || + GetClass() == Class::Ranger || + GetClass() == Class::Monk || + GetClass() == Class::Berserker + ) + ) { + switch (GetClass()) { + case Class::Warrior: + chance = RuleI(Combat, ClassicTripleAttackChanceWarrior); + break; + case Class::Ranger: + chance = RuleI(Combat, ClassicTripleAttackChanceRanger); + break; + case Class::Monk: + chance = RuleI(Combat, ClassicTripleAttackChanceMonk); + break; + case Class::Berserker: + chance = RuleI(Combat, ClassicTripleAttackChanceBerserker); + break; + default: + break; + } + } + } + else { + chance = GetSkill(EQ::skills::SkillTripleAttack); + } + + if (chance < 1) { + return false; + } + + int inc = aabonuses.TripleAttackChance + spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance; + chance = static_cast(chance * (1 + inc / 100.0f)); + chance = (chance * 100) / (chance + 800); + + return zone->random.Int(1, 100) <= chance; +} + void Bot::SetTarget(Mob *mob) { if (mob != this) { @@ -1836,13 +1953,6 @@ void Bot::SetTarget(Mob *mob) } } -void Bot::SetStopMeleeLevel(uint8 level) { - if (IsCasterClass(GetClass()) || IsHybridClass(GetClass())) - _stopMeleeLevel = level; - else - _stopMeleeLevel = 255; -} - void Bot::SetGuardMode() { StopMoving(); @@ -1861,25 +1971,6 @@ void Bot::SetHoldMode() { // AI Processing for the Bot object -constexpr float MAX_CASTER_DISTANCE[Class::PLAYER_CLASS_COUNT] = { - 0, - (34 * 34), - (24 * 24), - (28 * 28), - (26 * 26), - (42 * 42), - 0, - (30 * 30), - 0, - (38 * 38), - (54 * 54), - (48 * 48), - (52 * 52), - (50 * 50), - (32 * 32), - 0 -}; - void Bot::AI_Process() { @@ -1937,6 +2028,13 @@ void Bot::AI_Process() return; } + if (raid && r_group == RAID_GROUPLESS) { + glm::vec3 Goal(0, 0, 0); + TryNonCombatMovementChecks(bot_owner, follow_mob, Goal); + + return; + } + float fm_distance = DistanceSquared(m_Position, follow_mob->GetPosition()); float lo_distance = DistanceSquared(m_Position, leash_owner->GetPosition()); float leash_distance = RuleR(Bots, LeashDistance); @@ -1948,7 +2046,6 @@ void Bot::AI_Process() } // HEAL ROTATION CASTING CHECKS - HealRotationChecks(); if (GetAttackFlag()) { // Push owner's target onto our hate list @@ -1959,12 +2056,10 @@ void Bot::AI_Process() } //ALT COMBAT (ACQUIRE HATE) - glm::vec3 Goal(0, 0, 0); // We have aggro to choose from if (IsEngaged()) { - if (rest_timer.Enabled()) { rest_timer.Disable(); } @@ -1988,7 +2083,6 @@ void Bot::AI_Process() // DEFAULT (ACQUIRE TARGET) // VERIFY TARGET AND STANCE - auto tar = GetBotTarget(bot_owner); if (!tar) { return; @@ -2003,7 +2097,6 @@ void Bot::AI_Process() float tar_distance = DistanceSquared(m_Position, tar->GetPosition()); // TARGET VALIDATION - if (!IsValidTarget(bot_owner, leash_owner, lo_distance, leash_distance, tar, tar_distance)) { return; } @@ -2023,76 +2116,136 @@ void Bot::AI_Process() SendAddPlayerState(PlayerState::Aggressive); } +// COMBAT RANGE CALCS + + bool atCombatRange = false; + bool behindMob = false; + uint8 stopMeleeLevel = GetStopMeleeLevel(); + const EQ::ItemInstance* p_item; + const EQ::ItemInstance* s_item; + float melee_distance_min = 0.0f; + float melee_distance_max = 0.0f; + float melee_distance = 0.0f; + + CheckCombatRange(tar, sqrt(tar_distance), atCombatRange, behindMob, p_item, s_item, melee_distance_min, melee_distance_max, melee_distance, stopMeleeLevel); + // PULLING FLAG (ACTIONABLE RANGE) if (GetPullingFlag()) { - - constexpr size_t PULL_AGGRO = 5225; // spells[5225]: 'Throw Stone' - 0 cast time - - if (tar_distance <= (spells[PULL_AGGRO].range * spells[PULL_AGGRO].range)) { - - StopMoving(); - CastSpell(PULL_AGGRO, tar->GetID()); + //TODO bot rewrite - add ways to here to determine if throw stone is allowed, then check for ranged/spell/melee + if (!IsBotNonSpellFighter() && !HOLDING && AI_HasSpells() && AI_EngagedCastCheck()) { return; } + + if (RuleB(Bots, AllowSpellPulling)) { + uint16 pullSpell = RuleI(Bots, PullSpellID); + + if (tar_distance <= (spells[pullSpell].range * spells[pullSpell].range)) { + StopMoving(); + + if (!TargetValidation(tar)) { return; } + + CastSpell(pullSpell, tar->GetID()); + + return; + } + } + else { + if (atCombatRange && IsBotRanged()){ + StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); + + TryRangedAttack(tar); + + if (!TargetValidation(tar)) { return; } + + if (CheckDoubleRangedAttack()) { + BotRangedAttack(tar, true); + } + + return; + } + } } -// COMBAT RANGE CALCS - - bool atCombatRange; - const EQ::ItemInstance* p_item; - const EQ::ItemInstance* s_item; - CheckCombatRange(tar, tar_distance, atCombatRange, p_item, s_item); - // ENGAGED AT COMBAT RANGE // We can fight if (atCombatRange) { + bool jitterCooldown = false; + + if (m_combat_jitter_timer.GetRemainingTime() > 1 && m_combat_jitter_timer.Enabled()) { + jitterCooldown = true; + } + + if (IsMoving() || GetCombatJitterFlag() || GetCombatOutOfRangeJitterFlag()) { + if (!GetCombatJitterFlag() || !IsMoving() || GetCombatOutOfRangeJitterFlag()) { + StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); + } + + return; + } + + if (!jitterCooldown && AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) { + DoCombatPositioning(tar, Goal, stopMeleeLevel, tar_distance, melee_distance_min, melee_distance, melee_distance_max, behindMob); + return; + } + else { + if (!IsSitting() && !IsFacingMob(tar)) { + FaceTarget(tar); + return; + } + } + + if (!IsBotNonSpellFighter() && !HOLDING && AI_HasSpells() && AI_EngagedCastCheck()) { + return; + } if (IsMoving()) { StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); return; } - if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) { + if (IsBotRanged() && ranged_timer.Check(false)) { // Can shoot mezzed, stunned and dead!? + TryRangedAttack(tar); - if (TryEvade(tar)) { - return; - } + if (!TargetValidation(tar)) { return; } - if (TryFacingTarget(tar)) { - return; + if (CheckDoubleRangedAttack()) { + BotRangedAttack(tar, true); } } - - if (!IsBotNonSpellFighter() && AI_EngagedCastCheck()) { - return; - } - - // Up to this point, GetTarget() has been safe to dereference since the initial - // if (!GetTarget() || GetAppearance() == eaDead) { return false; } call. Due to the chance of the target dying and our pointer - // being nullified, we need to test it before dereferencing to avoid crashes - - if (IsBotArcher() && TryRangedAttack(tar)) { - return; - } - - if (!IsBotArcher() && GetLevel() < GetStopMeleeLevel()) { - if (!TryClassAttacks(tar)) { + else if (!IsBotRanged() && GetLevel() < stopMeleeLevel) { + if (tar->IsEnraged() && !BehindMob(tar, GetX(), GetY())) { return; } - if (!TryPrimaryWeaponAttacks(tar, p_item)) { - return; + if (!GetMaxMeleeRange() || !RuleB(Bots, DisableSpecialAbilitiesAtMaxMelee)) { + DoClassAttacks(tar); } - if (!TrySecondaryWeaponAttacks(tar, s_item)) { - return; - } - } + if (!TargetValidation(tar)) { return; } + + if (attack_timer.Check()) { + TryCombatProcs(p_item, tar, EQ::invslot::slotPrimary); + TriggerDefensiveProcs(tar, EQ::invslot::slotPrimary, false); + DoAttackRounds(tar, EQ::invslot::slotPrimary); + + if (TryDoubleMeleeRoundEffect()) { + DoAttackRounds(tar, EQ::invslot::slotPrimary); + } + } + + if (!TargetValidation(tar)) { return; } + + if (attack_dw_timer.Check()) { + if (CanThisClassDualWield() && CastToClient()->CheckDualWield()) { + TryCombatProcs(s_item, tar, EQ::invslot::slotSecondary); + DoAttackRounds(tar, EQ::invslot::slotSecondary); + } + } + + if (!TargetValidation(tar)) { return; } - if (GetAppearance() == eaDead) { - return; } } @@ -2104,12 +2257,9 @@ void Bot::AI_Process() // End not in combat range - if (TryMeditate()) { - return; - } + TryMeditate(); } else { // Out-of-combat behavior - SetAttackFlag(false); SetAttackingFlag(false); if (!bot_owner->GetBotPulling()) { @@ -2142,16 +2292,19 @@ void Bot::AI_Process() if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) { return; } - if (TryIdleChecks(fm_distance)) { + if (!HOLDING && AI_HasSpells() && TryIdleChecks(fm_distance)) { return; } - if (TryBardMovementCasts()) { + if (!HOLDING && AI_HasSpells() && TryBardMovementCasts()) { return; } } } bool Bot::TryBardMovementCasts() {// Basically, bard bots get a chance to cast idle spells while moving + if (HOLDING) { + return false; + } if (GetClass() == Class::Bard && IsMoving() && NOT_PASSIVE && !spellend_timer.Enabled() && AI_think_timer->Check()) { @@ -2162,7 +2315,6 @@ bool Bot::TryBardMovementCasts() {// Basically, bard bots get a chance to cast i } bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal) {// Non-engaged movement checks - if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == Class::Bard)) { if (GUARDING) { Goal = GetGuardPoint(); @@ -2193,6 +2345,7 @@ bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, g } } } + return false; } @@ -2207,18 +2360,10 @@ bool Bot::TryIdleChecks(float fm_distance) { !spellend_timer.Enabled() ) { - if (NOT_PASSIVE) { - - if (!AI_IdleCastCheck() && !IsCasting() && GetClass() != Class::Bard) { - BotMeditate(true); - } - - } else { - if (GetClass() != Class::Bard) { - BotMeditate(true); - } - + if (!AI_IdleCastCheck() && !IsCasting()) { + BotMeditate(IsSitting()); } + return true; } return false; @@ -2280,30 +2425,35 @@ bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) { bool Bot::TryMeditate() { if (!IsMoving() && !spellend_timer.Enabled()) { - if (GetTarget() && AI_EngagedCastCheck()) { - BotMeditate(false); - } else if (GetArchetype() == Archetype::Caster) { - BotMeditate(true); + + if (IsEngaged() && HasOrMayGetAggro(IsSitting())) { + if (IsSitting()) { + Stand(); + return false; + } } + BotMeditate(IsSitting()); + if (!(GetPlayerState() & static_cast(PlayerState::Aggressive))) { SendAddPlayerState(PlayerState::Aggressive); } return true; } + return false; } // This code actually gets processed when we are too far away from target and have not engaged yet bool Bot::TryPursueTarget(float leash_distance, glm::vec3& Goal) { - if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) { if (GetTarget() && !IsRooted()) { LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName()); Goal = GetTarget()->GetPosition(); + if (DistanceSquared(m_Position, Goal) <= leash_distance) { RunTo(Goal.x, Goal.y, Goal.z); - + SetCombatOutOfRangeJitter(); } else { WipeHateList(); SetTarget(nullptr); @@ -2313,8 +2463,8 @@ bool Bot::TryPursueTarget(float leash_distance, glm::vec3& Goal) { GetPet()->SetTarget(nullptr); } } - return true; + return true; } else { if (IsMoving()) { StopMoving(); @@ -2330,156 +2480,108 @@ bool Bot::TryPursueTarget(float leash_distance, glm::vec3& Goal) { } // This is a mob that is fleeing either because it has been feared or is low on hitpoints - AI_PursueCastCheck(); + if (!HOLDING && AI_HasSpells()) { + AI_PursueCastCheck(); + } return true; } + return false; } -bool Bot::TrySecondaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* s_item) { +void Bot::DoAttackRounds(Mob* target, int hand) { + if (!target || (target && target->IsCorpse())) { + return; + } - if (!GetTarget() || GetAppearance() == eaDead) { return false; } - if (attack_dw_timer.Check() && CanThisClassDualWield()) { - const EQ::ItemData* s_itemdata = nullptr; + Attack(target, hand, false, false); - // Can only dual wield without a weapon if you're a monk - if (s_item || (GetClass() == Class::Monk)) { + bool candouble = CanThisClassDoubleAttack(); + // extra off hand non-sense, can only double with skill of 150 or above + // or you have any amount of GiveDoubleAttack + if (candouble && hand == EQ::invslot::slotSecondary) + candouble = + GetSkill(EQ::skills::SkillDoubleAttack) > 149 || + (aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack) > 0; - if (s_item) { - s_itemdata = s_item->GetItem(); + if (candouble) { + if (CastToClient()->CheckDoubleAttack()) { + Attack(target, hand, false, false); + + if (hand == EQ::invslot::slotPrimary) { + + if (HasTwoHanderEquipped()) { + auto extraattackchance = aabonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] + spellbonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] + + itembonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE]; + if (extraattackchance && zone->random.Roll(extraattackchance)) { + auto extraattackamt = std::max({ aabonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS], spellbonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS], itembonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS] }); + for (int i = 0; i < extraattackamt; i++) { + Attack(target, hand, false, false); + } + } + } + else { + auto extraattackchance_primary = aabonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] + spellbonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] + + itembonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE]; + if (extraattackchance_primary && zone->random.Roll(extraattackchance_primary)) { + auto extraattackamt_primary = std::max({ aabonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS], spellbonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS], itembonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS] }); + for (int i = 0; i < extraattackamt_primary; i++) { + Attack(target, hand, false, false); + } + } + } } - if (!s_itemdata) { - return false; + if (hand == EQ::invslot::slotSecondary) { + auto extraattackchance_secondary = aabonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] + spellbonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] + + itembonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE]; + if (extraattackchance_secondary && zone->random.Roll(extraattackchance_secondary)) { + auto extraattackamt_secondary = std::max({ aabonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_NUM_ATKS], spellbonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_NUM_ATKS], itembonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_NUM_ATKS] }); + for (int i = 0; i < extraattackamt_secondary; i++) { + Attack(target, hand, false, false); + } + } } - bool use_fist = true; - if (s_itemdata) { - use_fist = false; - } + // you can only triple from the main hand + if (hand == EQ::invslot::slotPrimary && CanThisClassTripleAttack()) { + if (CheckTripleAttack()) { + Attack(target, hand, false, false); + int flurry_chance = aabonuses.FlurryChance + spellbonuses.FlurryChance + + itembonuses.FlurryChance; - if (use_fist || !s_itemdata->IsType2HWeapon()) { + if (flurry_chance && zone->random.Roll(flurry_chance)) { + Attack(target, hand, false, false); - float DualWieldProbability = 0.0f; - - int32 Ambidexterity = (aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity); - DualWieldProbability = ((GetSkill(EQ::skills::SkillDualWield) + GetLevel() + Ambidexterity) / 400.0f); // 78.0 max chance - - int32 DWBonus = (spellbonuses.DualWieldChance + itembonuses.DualWieldChance); - DualWieldProbability += (DualWieldProbability * float(DWBonus) / 100.0f); - - float random = zone->random.Real(0, 1); - if (random < DualWieldProbability) { // Max 78% for DW chance - Attack(tar, EQ::invslot::slotSecondary); // Single attack with offhand - - if (GetAppearance() == eaDead) { return false; } - TryCombatProcs(s_item, tar, EQ::invslot::slotSecondary); - - if (GetAppearance() == eaDead) { return false; } - if (CanThisClassDoubleAttack() && CheckBotDoubleAttack() && tar->GetHP() > -10) { - Attack(tar, EQ::invslot::slotSecondary); // Single attack with offhand + if (zone->random.Roll(flurry_chance)) { + Attack(target, hand, false, false); + } + //MessageString(Chat::NPCFlurry, YOU_FLURRY); //TODO bot rewrite - add output to others hits with flurry message } } } } } - return true; -} - -bool Bot::TryPrimaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* p_item) { - - if (!GetTarget() || GetAppearance() == eaDead) { return false; } - if (attack_timer.Check()) { // Process primary weapon attacks - - Attack(tar, EQ::invslot::slotPrimary); - - if (GetAppearance() == eaDead) { return false; } - TriggerDefensiveProcs(tar, EQ::invslot::slotPrimary, false); - - if (GetAppearance() == eaDead) { return false; } - TryCombatProcs(p_item, tar, EQ::invslot::slotPrimary); - - if (GetAppearance() == eaDead) { return false; } - if (CanThisClassDoubleAttack()) { - - if (CheckBotDoubleAttack()) { - Attack(tar, EQ::invslot::slotPrimary, true); - } - - if (GetAppearance() == eaDead) { return false; } - if (GetSpecialAbility(SpecialAbility::TripleAttack) && CheckBotDoubleAttack(true)) { - - Attack(tar, EQ::invslot::slotPrimary, true); - } - - if (GetAppearance() == eaDead) { return false; } - // quad attack, does this belong here?? - if (GetSpecialAbility(SpecialAbility::QuadrupleAttack) && CheckBotDoubleAttack(true)) { - Attack(tar, EQ::invslot::slotPrimary, true); - } - } - - if (GetAppearance() == eaDead) { return false; } - // Live AA - Flurry, Rapid Strikes ect (Flurry does not require Triple Attack). - if (int32 flurrychance = (aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance)) { - - if (zone->random.Int(0, 100) < flurrychance) { - - MessageString(Chat::NPCFlurry, YOU_FLURRY); - Attack(tar, EQ::invslot::slotPrimary, false); - - if (GetAppearance() == eaDead) { return false; } - Attack(tar, EQ::invslot::slotPrimary, false); - } - } - - if (GetAppearance() == eaDead) { return false; } - auto ExtraAttackChanceBonus = - (spellbonuses.ExtraAttackChance[0] + itembonuses.ExtraAttackChance[0] + - aabonuses.ExtraAttackChance[0]); - - if ( - ExtraAttackChanceBonus && - p_item && - p_item->GetItem()->IsType2HWeapon() && - zone->random.Int(0, 100) < ExtraAttackChanceBonus - ) { - Attack(tar, EQ::invslot::slotPrimary, false); - } - } - return true; -} - -// We can't fight if we don't have a target, are stun/mezzed or dead.. -bool Bot::TryClassAttacks(Mob* tar) { - -// Stop attacking if the target is enraged - if (!GetTarget() || GetAppearance() == eaDead) { return false; } - if (tar->IsEnraged() && !BehindMob(tar, GetX(), GetY())) { - return false; - } - - // First, special attack per class (kick, backstab etc..) - DoClassAttacks(tar); - return true; } bool Bot::TryRangedAttack(Mob* tar) { - if (IsBotArcher() && ranged_timer.Check(false)) { + if (IsBotRanged() && ranged_timer.Check(false)) { - if (!GetTarget() || GetAppearance() == eaDead) { return false; } - if (GetTarget()->GetHPRatio() <= 99.0f) { + if (!TargetValidation(tar)) { return false; } + + if (GetPullingFlag() || GetTarget()->GetHPRatio() <= 99.0f) { BotRangedAttack(tar); } + return true; } + return false; } bool Bot::TryFacingTarget(Mob* tar) { - if (!IsSitting() && !IsFacingMob(tar)) { FaceTarget(tar); return true; @@ -2489,93 +2591,95 @@ bool Bot::TryFacingTarget(Mob* tar) { bool Bot::TryEvade(Mob* tar) { + if (HasTargetReflection() && !tar->IsFeared() && !tar->IsStunned()) { + if (GetClass() == Class::Rogue && !GetSpellHold(BotSpellTypes::Escape)) { + if (m_rogue_evade_timer.Check(false)) { + int timer_duration = (HideReuseTime - GetSkillReuseTime(EQ::skills::SkillHide)) * 1000; - if ( - !IsRooted() && - HasTargetReflection() && - !tar->IsFeared() && - !tar->IsStunned() && - GetClass() == Class::Rogue && - m_evade_timer.Check(false) - ) { - int timer_duration = (HideReuseTime - GetSkillReuseTime(EQ::skills::SkillHide)) * 1000; + if (timer_duration < 0) { + timer_duration = 0; + } - if (timer_duration < 0) { - timer_duration = 0; + m_rogue_evade_timer.Start(timer_duration); + BotGroupSay(this, "Attempting to evade %s", tar->GetCleanName()); + + if (zone->random.Int(0, 260) < (int)GetSkill(EQ::skills::SkillHide)) { + //SendAppearancePacket(AT_Invis, Invisibility::Invisible); + RogueEvade(tar); + } + + //SendAppearancePacket(AT_Invis, Invisibility::Visible); + return true; + } } + else if (GetClass() == Class::Monk && GetLevel() >= 17 && !GetSpellHold(BotSpellTypes::Escape)) { + if (m_monk_evade_timer.Check(false)) { + int timer_duration = (FeignDeathReuseTime - GetSkillReuseTime(EQ::skills::SkillFeignDeath)) * 1000; - m_evade_timer.Start(timer_duration); - if (zone->random.Int(0, 260) < (int) GetSkill(EQ::skills::SkillHide)) { - RogueEvade(tar); + if (timer_duration < 0) { + timer_duration = 0; + } + + m_monk_evade_timer.Start(timer_duration); + BotGroupSay(this, "Attempting to evade %s", tar->GetCleanName()); + + if (zone->random.Int(0, 260) < (int)GetSkill(EQ::skills::SkillFeignDeath)) { + //SendAppearancePacket(AT_Anim, ANIM_DEATH); + entity_list.MessageCloseString(this, false, 200, 10, STRING_FEIGNFAILED, GetName()); + } + else { + SetFeigned(true); + //SendAppearancePacket(AT_Anim, ANIM_DEATH); + } + + //SendAppearancePacket(AT_Anim, ANIM_STAND); + SetFeigned(false); + return true; + } } - return true; } return false; } -void Bot::CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item) { - +void Bot::CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, bool& behindMob, const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item, float& melee_distance_min, float& melee_distance_max, float& melee_distance, uint8 stopMeleeLevel) { atCombatRange= false; - p_item= GetBotItem(EQ::invslot::slotPrimary); - s_item= GetBotItem(EQ::invslot::slotSecondary); - bool behind_mob = false; - bool backstab_weapon = false; - if (GetClass() == Class::Rogue) { - behind_mob = BehindMob(tar, GetX(), GetY()); // Can be separated for other future use - backstab_weapon = p_item && p_item->GetItemBackstabDamage(); + p_item = GetBotItem(EQ::invslot::slotPrimary); + s_item = GetBotItem(EQ::invslot::slotSecondary); + + bool backstab_weapon = false; + + if (GetBehindMob()) { + behindMob = BehindMob(tar, GetX(), GetY()); // Can be separated for other future use + if (GetClass() == Class::Rogue) { + backstab_weapon = p_item && p_item->GetItemBackstabDamage(); + } } // Calculate melee distances - float melee_distance_max = 0.0f; - float melee_distance = 0.0f; + CalcMeleeDistances(tar, p_item, s_item, behindMob, backstab_weapon, melee_distance_max, melee_distance, melee_distance_min, stopMeleeLevel); - CalcMeleeDistances(tar, p_item, s_item, behind_mob, backstab_weapon, melee_distance_max, melee_distance); + //LogTestDebugDetail("{} is {} {}. They are currently {} away {} to be {} [{} - {}] away." + // , GetCleanName() + // , (tar_distance <= melee_distance ? "within range of" : "too far away from") + // , tar->GetCleanName() + // , tar_distance + // , (tar_distance <= melee_distance ? "but only needed" : "but need to be") + // , melee_distance + // , melee_distance_min + // , melee_distance_max + //); //deleteme - float caster_distance_max = GetBotCasterMaxRange(melee_distance_max); - - bool atArcheryRange = IsArcheryRange(tar); - - SetRangerCombatWeapon(atArcheryRange); - - bool stop_melee_level = GetLevel() >= GetStopMeleeLevel(); - if (IsBotArcher() && atArcheryRange) { - atCombatRange = true; - } - else if (caster_distance_max && tar_distance <= caster_distance_max && stop_melee_level) { - atCombatRange = true; - } - else if (tar_distance <= melee_distance) { + if (tar_distance <= melee_distance) { atCombatRange = true; } } -void Bot::SetRangerCombatWeapon(bool atArcheryRange) { - - if (GetRangerAutoWeaponSelect()) { - bool changeWeapons = false; - - if (atArcheryRange && !IsBotArcher()) { - SetBotArcherySetting(true); - changeWeapons = true; - } - - else if (!atArcheryRange && IsBotArcher()) { - SetBotArcherySetting(false); - changeWeapons = true; - } - - if (changeWeapons) { - ChangeBotArcherWeapons(IsBotArcher()); - } - } -} - -void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_item, const EQ::ItemInstance* const& s_item, bool behind_mob, bool backstab_weapon, float& melee_distance_max, float& melee_distance) const { - +void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_item, const EQ::ItemInstance* const& s_item, bool behindMob, bool backstab_weapon, float& melee_distance_max, float& melee_distance, float& melee_distance_min, uint8 stopMeleeLevel) { float size_mod = GetSize(); float other_size_mod = tar->GetSize(); + bool resquareDistance = false; // For races with a fixed size if (GetRace() == Race::LavaDragon || GetRace() == Race::Wurm || GetRace() == Race::GhostDragon) { @@ -2611,58 +2715,115 @@ void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_it size_mod *= (size_mod * 4.0f); } + if (tar->GetRace() == Race::VeliousDragon) // Lord Vyemm and other velious dragons + { + size_mod *= 1.75; + } + if (tar->GetRace() == Race::DragonSkeleton) // Dracoliche in Fear. Skeletal Dragon + { + size_mod *= 2.25; + } + + size_mod *= RuleR(Combat, HitBoxMod); // used for testing sizemods on different races. + // Prevention of ridiculously sized hit boxes if (size_mod > 10000.0f) { size_mod = (size_mod / 7.0f); } melee_distance_max = size_mod; + if (!RuleB(Bots, UseFlatNormalMeleeRange)) { + switch (GetClass()) { + case Class::Warrior: + case Class::Paladin: + case Class::ShadowKnight: + if (p_item && p_item->GetItem()->IsType2HWeapon()) { + melee_distance = melee_distance_max * 0.45f; + } + else if ((s_item && s_item->GetItem()->IsTypeShield()) || (!p_item && !s_item)) { + melee_distance = melee_distance_max * 0.35f; + } + else { + melee_distance = melee_distance_max * 0.40f; + } + break; + case Class::Necromancer: + case Class::Wizard: + case Class::Magician: + case Class::Enchanter: + if (p_item && p_item->GetItem()->IsType2HWeapon()) { + melee_distance = melee_distance_max * 0.95f; + } + else { + melee_distance = melee_distance_max * 0.75f; + } + break; + case Class::Rogue: + if (behindMob && backstab_weapon) { + if (p_item->GetItem()->IsType2HWeapon()) { + melee_distance = melee_distance_max * 0.30f; + } + else { + melee_distance = melee_distance_max * 0.25f; + } + break; + } + // Fall-through + default: + if (p_item && p_item->GetItem()->IsType2HWeapon()) { + melee_distance = melee_distance_max * 0.70f; + } + else { + melee_distance = melee_distance_max * 0.50f; + } - switch (GetClass()) { - case Class::Warrior: - case Class::Paladin: - case Class::ShadowKnight: - if (p_item && p_item->GetItem()->IsType2HWeapon()) { - melee_distance = melee_distance_max * 0.45f; - } - else if ((s_item && s_item->GetItem()->IsTypeShield()) || (!p_item && !s_item)) { - melee_distance = melee_distance_max * 0.35f; - } - else { - melee_distance = melee_distance_max * 0.40f; - } - break; - case Class::Necromancer: - case Class::Wizard: - case Class::Magician: - case Class::Enchanter: - if (p_item && p_item->GetItem()->IsType2HWeapon()) { - melee_distance = melee_distance_max * 0.95f; - } - else { - melee_distance = melee_distance_max * 0.75f; - } - break; - case Class::Rogue: - if (behind_mob && backstab_weapon) { - if (p_item->GetItem()->IsType2HWeapon()) { - melee_distance = melee_distance_max * 0.30f; - } - else { - melee_distance = melee_distance_max * 0.25f; - } - break; - } - // Fall-through - default: - if (p_item && p_item->GetItem()->IsType2HWeapon()) { - melee_distance = melee_distance_max * 0.70f; - } - else { - melee_distance = melee_distance_max * 0.50f; + break; } - break; + melee_distance = sqrt(melee_distance); + melee_distance_max = sqrt(melee_distance_max); + } + else { + melee_distance_max = sqrt(melee_distance_max); + melee_distance = melee_distance_max * RuleR(Bots, NormalMeleeRangeDistance); + } + + if (melee_distance > RuleR(Bots, MaxDistanceForMelee)) { + melee_distance = RuleR(Bots, MaxDistanceForMelee); + } + + melee_distance_min = melee_distance_max * RuleR(Bots, PercentMinMeleeDistance); + + if (taunting) { + melee_distance_min = melee_distance_max * RuleR(Bots, PercentTauntMinMeleeDistance); + melee_distance = melee_distance_max * RuleR(Bots, TauntNormalMeleeRangeDistance); + } + + if (!taunting && !IsBotRanged() && GetMaxMeleeRange()) { + melee_distance = melee_distance_max * RuleR(Bots, PercentMaxMeleeRangeDistance); + melee_distance_min = melee_distance_max * RuleR(Bots, PercentMinMaxMeleeRangeDistance); + } + + + /* Caster Range Checks */ + bool isStopMeleeLevel = GetLevel() >= stopMeleeLevel; + if (isStopMeleeLevel) { + melee_distance = GetBotCasterMaxRange(melee_distance_max); + if (RuleB(Bots, CastersStayJustOutOfMeleeRange)) { + melee_distance_min = melee_distance_max + 1; + } + else { + melee_distance_min = melee_distance_max * RuleR(Bots, PercentMinCasterRangeDistance); + } + } + + /* Archer Checks*/ + if (IsBotRanged()) { + float archeryRange = GetBotRangedValue(); + float casterRange = GetBotCasterRange(); + float minArcheryRange = RuleI(Combat, MinRangedAttackDist); + melee_distance = std::min(archeryRange, (casterRange * 2)); + melee_distance_min = std::max(std::max(minArcheryRange, (melee_distance_max + 1)), std::min(casterRange, archeryRange)); } } @@ -2682,10 +2843,11 @@ bool Bot::IsValidTarget( bool invalid_target_state = false; if (HOLDING || !tar->IsNPC() || - tar->IsMezzed() || + (tar->IsMezzed() && !HasBotAttackFlag(tar)) || + (!Charmed() && tar->GetUltimateOwner()->IsOfClientBotMerc()) || lo_distance > leash_distance || tar_distance > leash_distance || - (!GetAttackingFlag() && !CheckLosFN(tar) && !leash_owner->CheckLosFN(tar)) || + (!GetAttackingFlag() && !CheckLosCheat(this, tar) && !CheckLosCheat(leash_owner, tar)) || !IsAttackAllowed(tar) ) { invalid_target_state = true; @@ -2750,9 +2912,7 @@ Mob* Bot::GetBotTarget(Client* bot_owner) } } - if (GetArchetype() == Archetype::Caster) { - BotMeditate(true); - } + BotMeditate(IsSitting()); } return t; @@ -2912,13 +3072,11 @@ bool Bot::CheckIfIncapacitated() { void Bot::SetBerserkState() {// Berserk updates should occur if primary AI criteria are met if (GetClass() == Class::Warrior || GetClass() == Class::Berserker) { - - if (!berserk && GetHP() > 0 && GetHPRatio() < 30.0f) { + if (!berserk && GetHPRatio() < RuleI(Combat, BerserkerFrenzyStart)) { entity_list.MessageCloseString(this, false, 200, 0, BERSERK_START, GetName()); berserk = true; } - - if (berserk && GetHPRatio() >= 30.0f) { + if (berserk && GetHPRatio() > RuleI(Combat, BerserkerFrenzyEnd)) { entity_list.MessageCloseString(this, false, 200, 0, BERSERK_END, GetName()); berserk = false; } @@ -2966,10 +3124,9 @@ void Bot::SetOwnerTarget(Client* bot_owner) { bot_owner->SetBotPulling(false); if (NOT_HOLDING && NOT_PASSIVE) { - auto attack_target = bot_owner->GetTarget(); - if (attack_target && HasBotAttackFlag(attack_target)) { + if (attack_target && HasBotAttackFlag(attack_target)) { InterruptSpell(); WipeHateList(); AddToHateList(attack_target, 1); @@ -2996,7 +3153,7 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) { auto pull_target = bot_owner->GetTarget(); if (pull_target) { if (raid) { - const auto msg = fmt::format("Pulling {} to the group..", pull_target->GetCleanName()); + const auto msg = fmt::format("Pulling {}.", pull_target->GetCleanName()); raid->RaidSay(msg.c_str(), GetCleanName(), 0, 100); } else { BotGroupSay( @@ -3017,8 +3174,9 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) { if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) { GetPet()->WipeHateList(); - GetPet()->SetTarget(nullptr); + GetPet()->SetTarget(nullptr); m_previous_pet_order = GetPet()->GetPetOrder(); + GetPet()->CastToNPC()->SaveGuardSpot(GetPosition()); GetPet()->SetPetOrder(SPO_Guard); } } @@ -3104,6 +3262,22 @@ bool Bot::Spawn(Client* botCharacterOwner) { SentPositionPacket(0.0f, 0.0f, 0.0f, 0.0f, 0); ping_timer.Start(8000); auto_save_timer.Start(RuleI(Bots, AutosaveIntervalSeconds) * 1000); + + pDontHealMeBefore = 0; + pDontGroupHealMeBefore = 0; + pDontGroupHoTHealMeBefore = 0; + pDontBuffMeBefore = Timer::GetCurrentTime() + 400; + pDontDotMeBefore = 0; + pDontRootMeBefore = 0; + pDontSnareMeBefore = 0; + pDontCureMeBefore = 0; + pDontRegularHealMeBefore = 0; + pDontVeryFastHealMeBefore = 0; + pDontFastHealMeBefore = 0; + pDontCompleteHealMeBefore = 0; + pDontGroupCompleteHealMeBefore = 0; + pDontHotHealMeBefore = 0; + // there is something askew with spawn struct appearance fields... // I re-enabled this until I can sort it out const auto& m = GetBotItemSlots(); @@ -3140,6 +3314,11 @@ bool Bot::Spawn(Client* botCharacterOwner) { } } + if (RuleB(Bots, RunSpellTypeChecksOnSpawn)) { + OwnerMessage("Running SpellType checks. There may be some spells that are flagged as incorrect but actually are correct. Use this as a guideline."); + CheckBotSpells(); //This runs through a serious of checks and outputs any spells that are set to the wrong spell type in the database + } + return true; } @@ -3423,7 +3602,6 @@ void Bot::LevelBotWithClient(Client* c, uint8 new_level, bool send_appearance) { parse->EventBot(EVENT_LEVEL_UP, e, nullptr, std::to_string(levels_change), 0); } - e->SetPetChooser(false); // not sure what this does, but was in bot 'update' code e->CalcBotStats(c->GetBotOption(Client::booStatsUpdate)); if (send_appearance) { @@ -3820,23 +3998,37 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* return; } - if (trade_instance->IsStackable() && trade_instance->GetCharges() < trade_instance->GetItem()->StackSize) { // temp until partial stacks are implemented - if (trade_event_exists) { - event_trade.push_back(ClientTrade(trade_instance, trade_index)); - continue; - } - else { - client->Message( - Chat::Yellow, - fmt::format( - "{} is only a partially stacked item, the trade has been cancelled!", - item_link - ).c_str() - ); - client->ResetTrade(); - return; + if (RuleI(Bots, StackSizeMin) != -1) { + if (trade_instance->IsStackable() && trade_instance->GetCharges() < RuleI(Bots, StackSizeMin)) { // temp until partial stacks are implemented + if (trade_event_exists) { + event_trade.push_back(ClientTrade(trade_instance, trade_index)); + continue; + } + else { + client->Message( + Chat::Yellow, + fmt::format( + "{} is too small of a stack, you need atleast {}, the trade has been cancelled!", + item_link, + RuleI(Bots, StackSizeMin) + ).c_str() + ); + client->ResetTrade(); + return; + } } } + else if (trade_instance->IsStackable() && trade_instance->GetCharges() < trade_instance->GetItem()->StackSize) { + client->Message( + Chat::Yellow, + fmt::format( + "{} is only a partially stacked item, the trade has been cancelled!", + item_link + ).c_str() + ); + client->ResetTrade(); + return; + } for (int m = EQ::invaug::SOCKET_BEGIN; m <= EQ::invaug::SOCKET_END; ++m) { const auto augment = trade_instance->GetAugment(m); @@ -4775,8 +4967,9 @@ void Bot::RogueAssassinate(Mob* other) { } void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { - if (!target || spellend_timer.Enabled() || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0 || !IsAttackAllowed(target)) + if (!target || GetAppearance() == eaDead || spellend_timer.Enabled() || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0 || !IsAttackAllowed(target)) { return; + } bool taunt_time = taunt_timer.Check(); bool ca_time = classattack_timer.Check(false); @@ -4844,58 +5037,58 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if (ma_time) { switch (GetClass()) { - case Class::Monk: { - int reuse = (MonkSpecialAttack(target, EQ::skills::SkillTigerClaw) - 1); + case Class::Monk: { + int reuse = (MonkSpecialAttack(target, EQ::skills::SkillTigerClaw) - 1); - // Live AA - Technique of Master Wu - int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; + // Live AA - Technique of Master Wu + int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; - if (wuchance) { - const int MonkSPA[5] = { - EQ::skills::SkillFlyingKick, - EQ::skills::SkillDragonPunch, - EQ::skills::SkillEagleStrike, - EQ::skills::SkillTigerClaw, - EQ::skills::SkillRoundKick - }; - int extra = 0; - // always 1/4 of the double attack chance, 25% at rank 5 (100/4) - while (wuchance > 0) { - if (zone->random.Roll(wuchance)) { - ++extra; + if (wuchance) { + const int MonkSPA[5] = { + EQ::skills::SkillFlyingKick, + EQ::skills::SkillDragonPunch, + EQ::skills::SkillEagleStrike, + EQ::skills::SkillTigerClaw, + EQ::skills::SkillRoundKick + }; + int extra = 0; + // always 1/4 of the double attack chance, 25% at rank 5 (100/4) + while (wuchance > 0) { + if (zone->random.Roll(wuchance)) { + ++extra; + } + else { + break; + } + wuchance /= 4; } - else { - break; + + Mob* bo = GetBotOwner(); + if (bo && bo->IsClient() && bo->CastToClient()->GetBotOption(Client::booMonkWuMessage)) { + + bo->Message( + GENERIC_EMOTE, + "The spirit of Master Wu fills %s! %s gains %d additional attack(s).", + GetCleanName(), + GetCleanName(), + extra + ); + } + + auto classic = RuleB(Combat, ClassicMasterWu); + while (extra) { + MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : EQ::skills::SkillTigerClaw)); + --extra; } - wuchance /= 4; } - Mob* bo = GetBotOwner(); - if (bo && bo->IsClient() && bo->CastToClient()->GetBotOption(Client::booMonkWuMessage)) { + float HasteModifier = (GetHaste() * 0.01f); + monkattack_timer.Start((reuse * 1000) / HasteModifier); - bo->Message( - GENERIC_EMOTE, - "The spirit of Master Wu fills %s! %s gains %d additional attack(s).", - GetCleanName(), - GetCleanName(), - extra - ); - } - - auto classic = RuleB(Combat, ClassicMasterWu); - while (extra) { - MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : EQ::skills::SkillTigerClaw)); - --extra; - } + break; } - - float HasteModifier = (GetHaste() * 0.01f); - monkattack_timer.Start((reuse * 1000) / HasteModifier); - - break; - } - default: - break; + default: + break; } } @@ -5347,7 +5540,7 @@ bool Bot::CastSpell( ) { bool Result = false; if (zone && !zone->IsSpellBlocked(spell_id, glm::vec3(GetPosition()))) { - // LogSpells("CastSpell called for spell [{}] ([{}]) on entity [{}], slot [{}], time [{}], mana [{}], from item slot [{}]", spells[spell_id].name, spell_id, target_id, slot, cast_time, mana_cost, (item_slot==0xFFFFFFFF)?999:item_slot); + // LogSpells("CastSpell called for spell [{}] ([{}]) on entity [{}], slot [{}], time [{}], mana [{}], from item slot [{}]", GetSpellName(spell_id), spell_id, target_id, slot, cast_time, mana_cost, (item_slot==0xFFFFFFFF)?999:item_slot); if (casting_spell_id == spell_id) { ZeroCastingVars(); @@ -5632,12 +5825,10 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe if ( spellTarget && - IsGrouped() && - ( - spellTarget->IsBot() || - spellTarget->IsClient() - ) && - RuleB(Bots, GroupBuffing) + GetClass() != Class::Bard && + (IsGrouped() || (IsRaidGrouped() && GetRaid()->GetGroup(GetCleanName()) != RAID_GROUPLESS)) && + (spellTarget->IsBot() || spellTarget->IsClient()) && + (RuleB(Bots, GroupBuffing) || RuleB(Bots, CrossRaidBuffingAndHealing)) ) { bool noGroupSpell = false; uint16 thespell = spell_id; @@ -5645,9 +5836,9 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe int j = BotGetSpells(i); int spelltype = BotGetSpellType(i); bool spellequal = (j == thespell); - bool spelltypeequal = ((spelltype == 2) || (spelltype == 16) || (spelltype == 32)); - bool spelltypetargetequal = ((spelltype == 8) && (spells[thespell].target_type == ST_Self)); - bool spelltypeclassequal = ((spelltype == 1024) && (GetClass() == Class::Shaman)); + bool spelltypeequal = ((spelltype == BotSpellTypes::RegularHeal) || (spelltype == BotSpellTypes::Escape) || (spelltype == BotSpellTypes::Pet)); + bool spelltypetargetequal = ((spelltype == BotSpellTypes::Buff) && (spells[thespell].target_type == ST_Self)); + bool spelltypeclassequal = ((spelltype == BotSpellTypes::InCombatBuff) && (GetClass() == Class::Shaman)); bool slotequal = (slot == EQ::spells::CastingSlot::Item); if (spellequal || slotequal) { if ((spelltypeequal || spelltypetargetequal) || spelltypeclassequal || slotequal) { @@ -5665,24 +5856,42 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe } if (!noGroupSpell) { - Group *g = GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i]) { - if ((g->members[i]->GetClass() == Class::Necromancer) && (IsEffectInSpell(thespell, SE_AbsorbMagicAtt) || IsEffectInSpell(thespell, SE_Rune))) { - } - else - SpellOnTarget(thespell, g->members[i]); + std::vector v; + if (RuleB(Bots, CrossRaidBuffingAndHealing)) { + v = GatherSpellTargets(true); + } + else { + v = GatherGroupSpellTargets(); + } - if (g->members[i] && g->members[i]->GetPetID()) - SpellOnTarget(thespell, g->members[i]->GetPet()); + for (Mob* m : v) { + if (IsEffectInSpell(thespell, SE_AbsorbMagicAtt) || IsEffectInSpell(thespell, SE_Rune)) { + for (int i = 0; i < m->GetMaxTotalSlots(); i++) { + uint32 buff_count = m->GetMaxTotalSlots(); + + for (unsigned int j = 0; j < buff_count; j++) { + if (IsValidSpell(m->GetBuffs()[j].spellid)) { + if (IsLichSpell(m->GetBuffs()[j].spellid)) { + continue; + } + } + } } } - SetMana(GetMana() - (GetActSpellCost(thespell, spells[thespell].mana) * (g->GroupCount() - 1))); + + SpellOnTarget(thespell, m); + + if (m->GetPetID() && (!RuleB(Bots, RequirePetAffinity) || m->HasPetAffinity())) { + SpellOnTarget(thespell, m->GetPet()); + } + + SetMana(GetMana() - (GetActSpellCost(thespell, spells[thespell].mana) * (v.size() - 1))); } } + stopLogic = true; } + return true; } @@ -5701,30 +5910,26 @@ bool Bot::DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, EQ::spel SpellOnTarget(spell_id, this); entity_list.AESpell(this, this, spell_id, true); } - else if (raid) - { - std::vector raid_group_members = raid->GetRaidGroupMembers(raid->GetGroup(GetName())); - for (auto iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { - if (iter->member) { - SpellOnTarget(spell_id, iter->member); - if (iter->member && iter->member->GetPetID()) - SpellOnTarget(spell_id, iter->member ->GetPet()); - } + else { + if (spellTarget != this) { + SpellOnTarget(spell_id, this); } - } - else - { - Group *g = GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; ++i) { - if (g->members[i]) { - SpellOnTarget(spell_id, g->members[i]); - if (g->members[i] && g->members[i]->GetPetID()) - SpellOnTarget(spell_id, g->members[i]->GetPet()); + + if (spellTarget->IsOfClientBotMerc()) { + const std::vector v = GatherGroupSpellTargets(spellTarget); + for (Mob* m : v) { + SpellOnTarget(spell_id, m); + + if (m->GetPetID() && (!RuleB(Bots, RequirePetAffinity) || m->HasPetAffinity())) { + SpellOnTarget(spell_id, m->GetPet()); } } } + else if (spellTarget->IsPet() && !spellTarget->IsFamiliar() && spellTarget->GetOwner() && (!RuleB(Bots, RequirePetAffinity) || spellTarget->GetOwner()->HasPetAffinity())) { + SpellOnTarget(spell_id, spellTarget); + } } + stopLogic = true; return true; } @@ -5793,9 +5998,9 @@ int32 Bot::GetMaxStat() { int32 Bot::GetMaxResist() { int level = GetLevel(); int32 base = 500; - if (level > 60) - base += ((level - 60) * 5); - + if (level > 65) { + base += ((level - 65) * 5); + } return base; } @@ -6172,24 +6377,52 @@ int64 Bot::CalcManaRegen() { uint8 level = GetLevel(); uint8 botclass = GetClass(); int32 regen = 0; - if (IsSitting()) { - BuffFadeBySitModifier(); - if (botclass != Class::Warrior && botclass != Class::Monk && botclass != Class::Rogue && botclass != Class::Berserker) { - regen = ((((GetSkill(EQ::skills::SkillMeditate) / 10) + (level - (level / 4))) / 4) + 4); - regen += (spellbonuses.ManaRegen + itembonuses.ManaRegen); - } else + + if (GetClass() == Class::Bard) { + if (IsSitting()) { + BuffFadeBySitModifier(); + regen = 2; + regen += (itembonuses.ManaRegen + aabonuses.ManaRegen); + } + else { + regen = 1; + regen += (itembonuses.ManaRegen + aabonuses.ManaRegen); + } + } + else { + if (IsSitting()) { + BuffFadeBySitModifier(); + if (GetArchetype() != Archetype::Melee) { + regen = ((((GetSkill(EQ::skills::SkillMeditate) / 10) + (level - (level / 4))) / 4) + 4); + regen += (spellbonuses.ManaRegen + itembonuses.ManaRegen); + } + else + regen = (2 + spellbonuses.ManaRegen + itembonuses.ManaRegen); + } + else regen = (2 + spellbonuses.ManaRegen + itembonuses.ManaRegen); - } else - regen = (2 + spellbonuses.ManaRegen + itembonuses.ManaRegen); - regen += aabonuses.ManaRegen + itembonuses.heroic_mana_regen; + if (IsHeroicINTCasterClass(GetClass())) { + regen += itembonuses.HeroicINT * RuleR(Character, HeroicIntelligenceMultiplier) / 25; + } + else if (IsHeroicWISCasterClass(GetClass())) { + regen += itembonuses.HeroicWIS * RuleR(Character, HeroicWisdomMultiplier) / 25; + } + else { + regen = 0; + } - regen = ((regen * RuleI(Character, ManaRegenMultiplier)) / 100); - float mana_regen_rate = RuleR(Bots, ManaRegen); - if (mana_regen_rate < 0.0f) - mana_regen_rate = 0.0f; + regen += aabonuses.ManaRegen; + regen = ((regen * RuleI(Character, ManaRegenMultiplier)) / 100); + float mana_regen_rate = RuleR(Bots, ManaRegen); + + if (mana_regen_rate < 0.0f) { + mana_regen_rate = 0.0f; + } + + regen = (regen * mana_regen_rate); + } - regen = (regen * mana_regen_rate); return regen; } @@ -6373,16 +6606,39 @@ void Bot::DoEnduranceUpkeep() { void Bot::Camp(bool save_to_database) { - if (IsEngaged() || GetBotOwner()->IsEngaged()) { - GetBotOwner()->Message( - Chat::White, - fmt::format( - "You cannot camp your bots while in combat" - ).c_str() - ); + if (RuleB(Bots, PreventBotCampOnFD) && GetBotOwner()->GetFeigned()) { + GetBotOwner()->Message(Chat::White, "You cannot camp bots while feigned."); return; } + if (RuleB(Bots, PreventBotCampOnEngaged)) { + Raid* raid = entity_list.GetRaidByClient(GetBotOwner()->CastToClient()); + if (raid && raid->IsEngaged()) { + GetBotOwner()->Message(Chat::White, "You cannot spawn bots while your raid is engaged."); + return; + } + + auto* owner_group = GetBotOwner()->GetGroup(); + if (owner_group) { + std::list member_list; + owner_group->GetClientList(member_list); + member_list.remove(nullptr); + + for (auto member_iter : member_list) { + if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) { + GetBotOwner()->Message(Chat::White, "You cannot spawn bots while your group is engaged,"); + return; + } + } + } + else { + if (GetBotOwner()->CastToClient()->GetAggroCount() > 0) { + GetBotOwner()->Message(Chat::White, "You cannot spawn bots while you are engaged,"); + return; + } + } + } + Sit(); LeaveHealRotationMemberPool(); @@ -6410,10 +6666,10 @@ void Bot::Zone() { Depop(); } -bool Bot::IsArcheryRange(Mob *target) { +bool Bot::IsAtRange(Mob *target) { bool result = false; if (target) { - float range = (GetBotArcheryRange() + 5.0); + float range = (GetBotRangedValue() + 5.0); range *= range; float targetDistance = DistanceSquaredNoZ(m_Position, target->GetPosition()); float minRuleDistance = (RuleI(Combat, MinRangedAttackDist) * RuleI(Combat, MinRangedAttackDist)); @@ -6809,349 +7065,64 @@ bool Bot::CheckLoreConflict(const EQ::ItemData* item) { return (m_inv.HasItemByLoreGroup(item->LoreGroup, invWhereWorn) != INVALID_INDEX); } -bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { +bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint16 spellType) { - if ((iSpellTypes & SPELL_TYPES_DETRIMENTAL) != 0) { + if (BOT_SPELL_TYPES_DETRIMENTAL(spellType, caster->GetClass())) { LogError("[EntityList::Bot_AICheckCloseBeneficialSpells] detrimental spells requested"); return false; } - if (!caster || !caster->AI_HasSpells()) { - return false; + std::vector v; + + if (IsGroupTargetOnlyBotSpellType(spellType)) { + v = caster->GatherGroupSpellTargets(); + } + else { + if (RuleB(Bots, CrossRaidBuffingAndHealing)) { + v = caster->GatherSpellTargets(true); + } + else { + v = caster->GatherGroupSpellTargets(); + } + v = caster->GatherSpellTargets(); } - if (iChance < 100) { - uint8 tmp = zone->random.Int(1, 100); - if (tmp > iChance) - return false; - } + Mob* tar = nullptr; - uint8 botCasterClass = caster->GetClass(); + for (Mob* m : v) { + tar = m; - if (iSpellTypes == SpellType_Heal) { - if (botCasterClass == Class::Cleric || botCasterClass == Class::Druid || botCasterClass == Class::Shaman) { - if (caster->IsRaidGrouped()) { - Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); - uint32 gid = raid->GetGroup(caster->GetName()); - if (gid < MAX_RAID_GROUPS) { - std::vector raid_group_members = raid->GetRaidGroupMembers(gid); - for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { - if (iter->member && !iter->member->qglobal) { - if (iter->member->IsClient() && iter->member->GetHPRatio() < 90) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } - else if ((iter->member->GetClass() == Class::Warrior || iter->member->GetClass() == Class::Paladin || iter->member->GetClass() == Class::ShadowKnight) && iter->member->GetHPRatio() < 95) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } - else if (iter->member->GetClass() == Class::Enchanter && iter->member->GetHPRatio() < 80) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } - else if (iter->member->GetHPRatio() < 70) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } - - } - - if (iter->member && !iter->member->qglobal && iter->member->HasPet() && iter->member->GetPet()->GetHPRatio() < 50) { - if (iter->member->GetPet()->GetOwner() != caster && caster->IsEngaged() && iter->member->IsCasting() && iter->member->GetClass() != Class::Enchanter) - continue; - - if (caster->AICastSpell(iter->member->GetPet(), 100, SpellType_Heal)) - return true; - } - } - } - } - - else if (caster->HasGroup()) { - Group *g = caster->GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i] && !g->members[i]->qglobal) { - if (g->members[i]->IsClient() && g->members[i]->GetHPRatio() < 90) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if ((g->members[i]->GetClass() == Class::Warrior || g->members[i]->GetClass() == Class::Paladin || g->members[i]->GetClass() == Class::ShadowKnight) && g->members[i]->GetHPRatio() < 95) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if (g->members[i]->GetClass() == Class::Enchanter && g->members[i]->GetHPRatio() < 80) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if (g->members[i]->GetHPRatio() < 70) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } - } - - if (g->members[i] && !g->members[i]->qglobal && g->members[i]->HasPet() && g->members[i]->GetPet()->GetHPRatio() < 50) { - if (g->members[i]->GetPet()->GetOwner() != caster && caster->IsEngaged() && g->members[i]->IsCasting() && g->members[i]->GetClass() != Class::Enchanter ) - continue; - - if (caster->AICastSpell(g->members[i]->GetPet(), 100, SpellType_Heal)) - return true; - } - } - } - } + if (!tar) { + continue; } - if ((botCasterClass == Class::Paladin || botCasterClass == Class::Beastlord || botCasterClass == Class::Ranger) && (caster->HasGroup() || caster->IsRaidGrouped())) { - float hpRatioToHeal = 25.0f; - switch(caster->GetBotStance()) { - case Stance::Reactive: - case Stance::Balanced: - hpRatioToHeal = 50.0f; - break; - case Stance::Burn: - case Stance::AEBurn: - hpRatioToHeal = 20.0f; - break; - case Stance::Aggressive: - case Stance::Efficient: - default: - hpRatioToHeal = 25.0f; - break; - } - if (caster->IsRaidGrouped()) { - if (auto raid = entity_list.GetRaidByBotName(caster->GetName())) { - uint32 gid = raid->GetGroup(caster->GetName()); - if (gid < MAX_RAID_GROUPS) { - std::vector raid_group_members = raid->GetRaidGroupMembers(gid); - for (std::vector::iterator iter = raid_group_members.begin(); - iter != raid_group_members.end(); ++iter) { - if (iter->member && !iter->member->qglobal) { - if (iter->member->IsClient() && iter->member->GetHPRatio() < hpRatioToHeal) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } else if ( - (iter->member->GetClass() == Class::Warrior || iter->member->GetClass() == Class::Paladin || - iter->member->GetClass() == Class::ShadowKnight) && - iter->member->GetHPRatio() < hpRatioToHeal) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } else if (iter->member->GetClass() == Class::Enchanter && - iter->member->GetHPRatio() < hpRatioToHeal) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } else if (iter->member->GetHPRatio() < hpRatioToHeal / 2) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } - } + uint8 chanceToCast = caster->IsEngaged() ? caster->GetChanceToCastBySpellType(spellType) : 100; - if (iter->member && !iter->member->qglobal && iter->member->HasPet() && - iter->member->GetPet()->GetHPRatio() < 25) { - if (iter->member->GetPet()->GetOwner() != caster && caster->IsEngaged() && - iter->member->IsCasting() && iter->member->GetClass() != Class::Enchanter) - continue; - - if (caster->AICastSpell(iter->member->GetPet(), 100, SpellType_Heal)) - return true; - } - } - } - } - } - else if (caster->HasGroup()) { - if (auto g = caster->GetGroup()) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i] && !g->members[i]->qglobal) { - if (g->members[i]->IsClient() && g->members[i]->GetHPRatio() < hpRatioToHeal) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if ((g->members[i]->GetClass() == Class::Warrior || g->members[i]->GetClass() == Class::Paladin || - g->members[i]->GetClass() == Class::ShadowKnight) && - g->members[i]->GetHPRatio() < hpRatioToHeal) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if (g->members[i]->GetClass() == Class::Enchanter && - g->members[i]->GetHPRatio() < hpRatioToHeal) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if (g->members[i]->GetHPRatio() < hpRatioToHeal / 2) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } - } - - if (g->members[i] && !g->members[i]->qglobal && g->members[i]->HasPet() && - g->members[i]->GetPet()->GetHPRatio() < 25) { - if (g->members[i]->GetPet()->GetOwner() != caster && caster->IsEngaged() && - g->members[i]->IsCasting() && g->members[i]->GetClass() != Class::Enchanter) - continue; - - if (caster->AICastSpell(g->members[i]->GetPet(), 100, SpellType_Heal)) - return true; - } - } - } - } + if (!caster->PrecastChecks(tar, spellType)) { + continue; } - } - if (iSpellTypes == SpellType_Buff) { - uint8 chanceToCast = caster->IsEngaged() ? caster->GetChanceToCastBySpellType(SpellType_Buff) : 100; - if (botCasterClass == Class::Bard) { - if (caster->AICastSpell(caster, chanceToCast, SpellType_Buff)) { + if (caster->AICastSpell(tar, chanceToCast, spellType)) { + return true; + } + + if (tar->HasPet() && (!m->GetPet()->IsFamiliar() || RuleB(Bots, AllowBuffingHealingFamiliars))) { + tar = m->GetPet(); + + if (!tar) { + continue; + } + + if (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) { + continue; + } + + if (!caster->PrecastChecks(tar, spellType)) { + continue; + } + + if (caster->AICastSpell(tar, chanceToCast, spellType)) { return true; - } else - return false; - } - - if (caster->IsRaidGrouped()) { - Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); - uint32 g = raid->GetGroup(caster->GetName()); - if (g < MAX_RAID_GROUPS) { - std::vector raid_group_members = raid->GetRaidGroupMembers(g); - for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { - if (iter->member) { - if (caster->AICastSpell(iter->member, chanceToCast, SpellType_Buff) || caster->AICastSpell(iter->member->GetPet(), chanceToCast, SpellType_Buff)) - return true; - } - } - } - } - if (caster->HasGroup()) { - Group *g = caster->GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i]) { - if (caster->AICastSpell(g->members[i], chanceToCast, SpellType_Buff) || caster->AICastSpell(g->members[i]->GetPet(), chanceToCast, SpellType_Buff)) - return true; - } - } - } - } - } - - if (iSpellTypes == SpellType_Cure) { - if (caster->IsRaidGrouped()) { - Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); - uint32 gid = raid->GetGroup(caster->GetName()); - if (gid < MAX_RAID_GROUPS) { - std::vector raid_group_members = raid->GetRaidGroupMembers(gid); - for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { - if (iter->member && caster->GetNeedsCured(iter->member)) { - if (caster->AICastSpell(iter->member, caster->GetChanceToCastBySpellType(SpellType_Cure), SpellType_Cure)) - return true; - else if (botCasterClass == Class::Bard) { - return false; - } - } - - if (iter->member && iter->member->GetPet() && caster->GetNeedsCured(iter->member->GetPet())) { - if (caster->AICastSpell(iter->member->GetPet(), (int)caster->GetChanceToCastBySpellType(SpellType_Cure) / 4, SpellType_Cure)) - return true; - } - } - } - } - else if (caster->HasGroup()) { - Group *g = caster->GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i] && caster->GetNeedsCured(g->members[i])) { - if (caster->AICastSpell(g->members[i], caster->GetChanceToCastBySpellType(SpellType_Cure), SpellType_Cure)) - return true; - else if (botCasterClass == Class::Bard) - return false; - } - - if (g->members[i] && g->members[i]->GetPet() && caster->GetNeedsCured(g->members[i]->GetPet())) { - if (caster->AICastSpell(g->members[i]->GetPet(), (int)caster->GetChanceToCastBySpellType(SpellType_Cure)/4, SpellType_Cure)) - return true; - } - } - } - } - } - - if (iSpellTypes == SpellType_HateRedux) { - if (!caster->IsEngaged()) - return false; - - if (caster->IsRaidGrouped()) { - Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); - uint32 gid = raid->GetGroup(caster->GetName()); - if (gid < MAX_RAID_GROUPS) { - std::vector raid_group_members = raid->GetRaidGroupMembers(gid); - for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { - if (iter->member && caster->GetNeedsHateRedux(iter->member)) { - if (caster->AICastSpell(iter->member, caster->GetChanceToCastBySpellType(SpellType_HateRedux), SpellType_HateRedux)) - return true; - } - } - } - } - else if (caster->HasGroup()) { - Group *g = caster->GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i] && caster->GetNeedsHateRedux(g->members[i])) { - if (caster->AICastSpell(g->members[i], caster->GetChanceToCastBySpellType(SpellType_HateRedux), SpellType_HateRedux)) - return true; - } - } - } - } - } - - if (iSpellTypes == SpellType_PreCombatBuff) { - if (botCasterClass == Class::Bard || caster->IsEngaged()) - return false; - - //added raid check - if (caster->IsRaidGrouped()) { - Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); - uint32 g = raid->GetGroup(caster->GetName()); - if (g < MAX_RAID_GROUPS) { - std::vector raid_group_members = raid->GetRaidGroupMembers(g); - for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { - if (iter->member && - (caster->AICastSpell(iter->member, iChance, SpellType_PreCombatBuff) || - caster->AICastSpell(iter->member->GetPet(), iChance, SpellType_PreCombatBuff)) - ) { - return true; - } - } - } - } else if (caster->HasGroup()) { - const auto g = caster->GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i]) { - if (caster->AICastSpell(g->members[i], iChance, SpellType_PreCombatBuff) || caster->AICastSpell(g->members[i]->GetPet(), iChance, SpellType_PreCombatBuff)) - return true; - } - } - } - } - } - - if (iSpellTypes == SpellType_InCombatBuff) { - if (botCasterClass == Class::Bard) { - if (caster->AICastSpell(caster, iChance, SpellType_InCombatBuff)) { - return true; - } - else { - return false; - } - } - - if (caster->HasGroup()) { - Group* g = caster->GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i]) { - if (caster->AICastSpell(g->members[i], iChance, SpellType_InCombatBuff) || caster->AICastSpell(g->members[i]->GetPet(), iChance, SpellType_InCombatBuff)) { - return true; - } - } - } } } } @@ -7448,7 +7419,7 @@ uint8 Bot::GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets, Raid* raid need_healed++; } - if (includePets && member->GetPet() && member->GetPet()->GetHPRatio() <= hpr) { + if (includePets && member->GetPet() && !member->GetPet()->IsFamiliar() && member->GetPet()->GetHPRatio() <= hpr) { need_healed++; } } @@ -7578,69 +7549,74 @@ int Bot::GroupLeadershipAAOffenseEnhancement() { bool Bot::GetNeedsCured(Mob *tar) { bool needCured = false; + if (tar) { if (tar->FindType(SE_PoisonCounter) || tar->FindType(SE_DiseaseCounter) || tar->FindType(SE_CurseCounter) || tar->FindType(SE_CorruptionCounter)) { uint32 buff_count = tar->GetMaxTotalSlots(); - int buffsWithCounters = 0; - needCured = true; + for (unsigned int j = 0; j < buff_count; j++) { if (IsValidSpell(tar->GetBuffs()[j].spellid)) { if (CalculateCounters(tar->GetBuffs()[j].spellid) > 0) { - buffsWithCounters++; - if (buffsWithCounters == 1 && (tar->GetBuffs()[j].ticsremaining < 2 || (int32)((tar->GetBuffs()[j].ticsremaining * 6) / tar->GetBuffs()[j].counters) < 2)) { - needCured = false; - break; + if (tar->GetBuffs()[j].ticsremaining < 1) { + continue; } + + needCured = true; } } } } } + return needCured; } bool Bot::GetNeedsHateRedux(Mob *tar) { - // This really should be a scalar function based in class Mob that returns 'this' state..but, is inline with current Bot coding... - // TODO: Good starting point..but, can be refined.. - // TODO: Still awaiting bot spell rework.. - if (!tar || !tar->IsEngaged() || !tar->HasTargetReflection() || !tar->GetTarget()->IsNPC()) + if (!tar || !tar->IsEngaged() || !tar->HasTargetReflection() || !tar->GetTarget()->IsNPC() || (tar->IsBot() && tar->CastToBot()->IsTaunting())) { return false; + } if (tar->IsBot()) { - switch (tar->GetClass()) { - case Class::Rogue: - if (tar->CanFacestab() || tar->CastToBot()->m_evade_timer.Check(false)) - return false; - case Class::Cleric: - case Class::Druid: - case Class::Shaman: - case Class::Necromancer: - case Class::Wizard: - case Class::Magician: - case Class::Enchanter: + if (tar->GetHPRatio() > GetUltimateSpellMinThreshold(BotSpellTypes::HateRedux, tar) && tar->GetHPRatio() < GetUltimateSpellMaxThreshold(BotSpellTypes::HateRedux, tar)) { return true; - default: - return false; } } return false; } -bool Bot::HasOrMayGetAggro() { +bool Bot::HasOrMayGetAggro(bool SitAggro, uint32 spell_id) { bool mayGetAggro = false; + if (GetTarget() && GetTarget()->GetHateTop()) { - Mob *topHate = GetTarget()->GetHateTop(); - if (topHate == this) + Mob* topHate = GetTarget()->GetHateTop(); + if (topHate == this) { mayGetAggro = true; + } else { uint32 myHateAmt = GetTarget()->GetHateAmount(this); uint32 topHateAmt = GetTarget()->GetHateAmount(topHate); - if (myHateAmt > 0 && topHateAmt > 0 && (uint8)((myHateAmt / topHateAmt) * 100) > 90) + if (SitAggro && !spell_id) { + myHateAmt *= (1 + (RuleI(Aggro, SittingAggroMod) / 100)); + } + + if (spell_id && IsValidSpell(spell_id) && GetTarget()) { + myHateAmt += CheckAggroAmount(spell_id, GetTarget()); + } + + if ( + topHateAmt < 1 || + ( + myHateAmt > 0 && + (uint8)((myHateAmt / topHateAmt) * 100) > RuleI(Bots, HasOrMayGetAggroThreshold) + ) + ) { mayGetAggro = true; + } } } + return mayGetAggro; } @@ -8000,51 +7976,24 @@ bool Bot::CheckDataBucket(std::string bucket_name, const std::string& bucket_val int Bot::GetExpansionBitmask() { - if (m_expansion_bitmask >= 0) { - return m_expansion_bitmask; + if (_expansionBitmask >= 0) { + return _expansionBitmask; } return RuleI(Bots, BotExpansionSettings); } -void Bot::SetExpansionBitmask(int expansion_bitmask, bool save) +void Bot::SetExpansionBitmask(int expansionBitmask) { - m_expansion_bitmask = expansion_bitmask; - - if (save) { - if (!database.botdb.SaveExpansionBitmask(GetBotID(), expansion_bitmask)) { - if (GetBotOwner() && GetBotOwner()->IsClient()) { - GetBotOwner()->CastToClient()->Message( - Chat::White, - fmt::format( - "Failed to save expansion bitmask for {}.", - GetCleanName() - ).c_str() - ); - } - } - } + _expansionBitmask = expansionBitmask; LoadAAs(); } -void Bot::SetBotEnforceSpellSetting(bool enforce_spell_settings, bool save) +void Bot::SetBotEnforceSpellSetting(bool enforceSpellSettings) { - m_enforce_spell_settings = enforce_spell_settings; + _enforceSpellSettings = enforceSpellSettings; - if (save) { - if (!database.botdb.SaveEnforceSpellSetting(GetBotID(), enforce_spell_settings)) { - if (GetBotOwner() && GetBotOwner()->IsClient()) { - GetBotOwner()->CastToClient()->Message( - Chat::White, - fmt::format( - "Failed to save enforce spell settings for {}.", - GetCleanName() - ).c_str() - ); - } - } - } LoadBotSpellSettings(); AI_AddBotSpells(GetBotSpellID()); } @@ -8294,24 +8243,6 @@ std::string Bot::GetHPString(int8 min_hp, int8 max_hp) return hp_string; } -void Bot::SetBotArcherySetting(bool bot_archer_setting, bool save) -{ - m_bot_archery_setting = bot_archer_setting; - if (save) { - if (!database.botdb.SaveBotArcherSetting(GetBotID(), bot_archer_setting)) { - if (GetBotOwner() && GetBotOwner()->IsClient()) { - GetBotOwner()->CastToClient()->Message( - Chat::White, - fmt::format( - "Failed to save archery settings for {}.", - GetCleanName() - ).c_str() - ); - } - } - } -} - std::vector Bot::GetApplySpellList( ApplySpellType apply_type, bool allow_pets, @@ -8489,16 +8420,20 @@ float Bot::GetBotCasterMaxRange(float melee_distance_max) {// Calculate caster d float caster_distance_min = 0.0f; float caster_distance = 0.0f; - caster_distance_max = GetBotCasterRange() * GetBotCasterRange(); + caster_distance_max = GetBotCasterRange(); + if (!GetBotCasterRange() && GetLevel() >= GetStopMeleeLevel() && GetClass() >= Class::Warrior && GetClass() <= Class::Berserker) { - caster_distance_max = MAX_CASTER_DISTANCE[GetClass() - 1]; + caster_distance_max = GetDefaultBotBaseSetting(BotBaseSettings::CasterRange); } + if (caster_distance_max) { caster_distance_min = melee_distance_max; + if (caster_distance_max <= caster_distance_min) { caster_distance_max = caster_distance_min * 1.25f; } } + return caster_distance_max; } @@ -8510,33 +8445,36 @@ int32 Bot::CalcItemATKCap() bool Bot::CheckSpawnConditions(Client* c) { - if (c->GetFeigned()) { - c->Message(Chat::White, "You cannot spawn a bot-group while feigned."); + if (RuleB(Bots, PreventBotSpawnOnFD) && c->GetFeigned()) { + c->Message(Chat::White, "You cannot spawn bots while feigned."); return false; } - Raid* raid = entity_list.GetRaidByClient(c); - if (raid && raid->IsEngaged()) { - c->Message(Chat::White, "You cannot spawn bots while your raid is engaged."); - return false; - } + if (RuleB(Bots, PreventBotSpawnOnEngaged)) { + Raid* raid = entity_list.GetRaidByClient(c); + if (raid && raid->IsEngaged()) { + c->Message(Chat::White, "You cannot spawn bots while your raid is engaged."); + return false; + } - auto* owner_group = c->GetGroup(); - if (owner_group) { - std::list member_list; - owner_group->GetClientList(member_list); - member_list.remove(nullptr); + auto* owner_group = c->GetGroup(); + if (owner_group) { + std::list member_list; + owner_group->GetClientList(member_list); + member_list.remove(nullptr); - for (auto member_iter : member_list) { - if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) { - c->Message(Chat::White, "You cannot spawn bots while your group is engaged,"); - return false; + for (auto member_iter : member_list) { + if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) { + c->Message(Chat::White, "You cannot spawn bots while your group is engaged,"); + return false; + } } } - } else { - if (c->GetAggroCount() > 0) { - c->Message(Chat::White, "You cannot spawn bots while you are engaged,"); - return false; + else { + if (c->GetAggroCount() > 0) { + c->Message(Chat::White, "You cannot spawn bots while you are engaged,"); + return false; + } } } @@ -8609,12 +8547,12 @@ void Bot::SetSpellRecastTimer(uint16 spell_id, int32 recast_delay) { if (CheckSpellRecastTimer(spell_id)) { BotTimer_Struct t; - t.timer_id = spells[ spell_id ].timer_id; + t.timer_id = spells[spell_id].timer_id; t.timer_value = (Timer::GetCurrentTime() + recast_delay); t.recast_time = recast_delay; t.is_spell = true; t.is_disc = false; - t.spell_id = spells[ spell_id ].id; + t.spell_id = spells[spell_id].id; t.is_item = false; t.item_id = 0; @@ -8627,7 +8565,7 @@ void Bot::SetSpellRecastTimer(uint16 spell_id, int32 recast_delay) { ( ( spells[spell_id].timer_id != 0 && - spells[spell_id].timer_id == bot_timers[ i ].timer_id + spells[spell_id].timer_id == bot_timers[i].timer_id ) || bot_timers[i].spell_id == spell_id ) @@ -8715,12 +8653,12 @@ void Bot::SetDisciplineReuseTimer(uint16 spell_id, int32 reuse_timer) if (CheckDisciplineReuseTimer(spell_id)) { BotTimer_Struct t; - t.timer_id = spells[ spell_id ].timer_id; + t.timer_id = spells[spell_id].timer_id; t.timer_value = (Timer::GetCurrentTime() + reuse_timer); t.recast_time = reuse_timer; t.is_spell = false; t.is_disc = true; - t.spell_id = spells[ spell_id ].id; + t.spell_id = spells[spell_id].id; t.is_item = false; t.item_id = 0; @@ -9220,3 +9158,2275 @@ void Bot::DoItemClick(const EQ::ItemData *item, uint16 slot_id) } uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][Class::PLAYER_CLASS_COUNT][Stance::AEBurn][cntHSND] = { 0 }; + +std::vector Bot::GatherGroupSpellTargets(Mob* target, bool noClients, bool noBots) { + std::vector valid_spell_targets; + + if (IsRaidGrouped()) { + if (auto raid = entity_list.GetRaidByBotName(GetName())) { + std::vector raidGroupMembers; + if (target) { + auto raidGroup = raid->GetGroup(target->GetName()); + + if (raidGroup != RAID_GROUPLESS) { + raidGroupMembers = raid->GetRaidGroupMembers(raidGroup); + } + else { + return valid_spell_targets; + } + } + else { + auto raidGroup = raid->GetGroup(GetName()); + + if (raidGroup != RAID_GROUPLESS) { + raidGroupMembers = raid->GetRaidGroupMembers(raidGroup); + } + else { + return valid_spell_targets; + } + } + + for (const auto& m : raidGroupMembers) { + if ( + m.member && m.group_number != RAID_GROUPLESS && + ( + (m.member->IsClient() && !noClients) || + (m.member->IsBot() && !noBots) + ) + ) { + valid_spell_targets.emplace_back(m.member); + } + } + } + } + else if (IsGrouped()) { + Group* group = GetGroup(); + if (group) { + for (const auto& m : group->members) { + if ( + m && + ( + (m->IsClient() && !noClients) || + (m->IsBot() && !noBots) + ) + ) { + valid_spell_targets.emplace_back(m); + } + } + } + } + else { + valid_spell_targets.emplace_back(this); + } + + return valid_spell_targets; +} + +std::vector Bot::GatherSpellTargets(bool entireRaid, bool noClients, bool noBots, bool noPets) { + std::vector valid_spell_targets; + + if (IsRaidGrouped()) { + if (auto raid = entity_list.GetRaidByBotName(GetName())) { + if (entireRaid) { + for (const auto& m : raid->members) { + if (m.member && m.group_number != RAID_GROUPLESS && ((m.member->IsClient() && !noClients) || (m.member->IsBot() && !noBots))) { + valid_spell_targets.emplace_back(m.member); + } + } + } + else { + std::vector raidGroup = raid->GetRaidGroupMembers(raid->GetGroup(GetName())); + + for (const auto& m : raidGroup) { + if (m.member && m.group_number != RAID_GROUPLESS && ((m.member->IsClient() && !noClients) || (m.member->IsBot() && !noBots))) { + valid_spell_targets.emplace_back(m.member); + } + } + } + } + } + else if (IsGrouped()) { + Group* group = GetGroup(); + if (group) { + for (const auto& m : group->members) { + if (m && ((m->IsClient() && !noClients) || (m->IsBot() && !noBots))) { + valid_spell_targets.emplace_back(m); + } + } + } + } + else { + valid_spell_targets.emplace_back(this); + } + + return valid_spell_targets; +} + +bool Bot::PrecastChecks(Mob* tar, uint16 spellType) { + if (!tar) { + LogBotPreChecksDetail("{} says, 'Cancelling cast due to PrecastChecks !tar.'", GetCleanName()); //deleteme + return false; + } + + LogBotPreChecksDetail("{} says, 'Running [{}] PreChecks on [{}].'", GetCleanName(), GetSpellTypeNameByID(spellType), tar->GetCleanName()); //deleteme + + if (GetUltimateSpellHold(spellType, tar)) { + LogBotHoldChecksDetail("{} says, 'Cancelling cast of [{}] on [{}] due to GetUltimateSpellHold.'", GetCleanName(), GetSpellTypeNameByID(spellType), tar->GetCleanName()); //deleteme + return false; + } + + if (GetManaRatio() < GetSpellTypeMinManaLimit(spellType) || GetManaRatio() > GetSpellTypeMaxManaLimit(spellType)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of [{}] on [{}] due to GetSpellTypeMinManaLimit or GetSpellTypeMaxManaLimit.'", GetCleanName(), GetSpellTypeNameByID(spellType), tar->GetCleanName()); //deleteme + return false; + } + + if (GetHPRatio() < GetSpellTypeMinHPLimit(spellType) || GetHPRatio() > GetSpellTypeMaxHPLimit(spellType)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of [{}] on [{}] due to GetSpellTypeMinHPLimit or GetSpellTypeMaxHPLimit.'", GetCleanName(), GetSpellTypeNameByID(spellType), tar->GetCleanName()); //deleteme + return false; + } + + if (!GetUltimateSpellDelayCheck(spellType, tar)) { + LogBotDelayChecksDetail("{} says, 'Cancelling cast of [{}] on [{}] due to GetUltimateSpellDelayCheck.'", GetCleanName(), GetSpellTypeNameByID(spellType), tar->GetCleanName()); //deleteme + return false; + } + + switch (spellType) { //This will skip Threshold Checks during Precast for specific SpellTypes that are checked when acquiring new targets + case BotSpellTypes::Mez: + case BotSpellTypes::AEMez: + return true; + default: + if (GetHPRatioForSpellType(spellType, tar) < GetUltimateSpellMinThreshold(spellType, tar) || GetHPRatioForSpellType(spellType, tar) > GetUltimateSpellMaxThreshold(spellType, tar)) { + LogBotThresholdChecksDetail("{} says, 'Cancelling cast of [{}] on [{}] due to GetUltimateSpellMinThreshold or GetUltimateSpellMaxThreshold.'", GetCleanName(), GetSpellTypeNameByID(spellType), tar->GetCleanName()); //deleteme + return false; + } + } + + return true; +} + +bool Bot::CastChecks(uint16 spellid, Mob* tar, uint16 spellType, bool doPrechecks, bool AECheck) { + if (!tar) { + LogBotPreChecksDetail("{} says, 'Cancelling cast due to CastChecks !tar.'", GetCleanName()); //deleteme + return false; + } + + if (doPrechecks) { + if (spells[spellid].target_type == ST_Self && tar != this) { + tar = this; + } + + if (!PrecastChecks(tar, spellType)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast due to !PrecastChecks.'", GetCleanName()); //deleteme + return false; + } + } + + LogBotPreChecksDetail("{} says, 'Running [{}] CastChecks on [{}].'", GetCleanName(), GetSpellTypeNameByID(spellType), tar->GetCleanName()); //deleteme + + if (!IsValidSpell(spellid)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast due to !IsValidSpell.'", GetCleanName()); //deleteme + return false; + } + + if (spells[spellid].target_type == ST_Self && tar != this) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to ST_Self.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if (!CheckSpellRecastTimer(spellid)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} due to !CheckSpellRecastTimer.'", GetCleanName(), GetSpellName(spellid)); //deleteme + return false; + } + + if (!BotHasEnoughMana(spellid)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} due to !BotHasEnoughMana.'", GetCleanName(), GetSpellName(spellid)); //deleteme + return false; + } + + if (zone->IsSpellBlocked(spellid, glm::vec3(GetPosition()))) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to IsSpellBlocked.'", GetCleanName(), GetSpellName(spellid)); //deleteme + return false; + } + + if (!zone->CanLevitate() && IsEffectInSpell(spellid, SE_Levitate)) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to !CanLevitate.'", GetCleanName(), GetSpellName(spellid)); //deleteme + return false; + } + + if (spells[spellid].time_of_day == SpellTimeRestrictions::Day && !zone->zone_time.IsDayTime()) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to !IsDayTime.'", GetCleanName(), GetSpellName(spellid)); //deleteme + return false; + } + + if (spells[spellid].time_of_day == SpellTimeRestrictions::Night && !zone->zone_time.IsNightTime()) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to !IsNightTime.'", GetCleanName(), GetSpellName(spellid)); //deleteme + return false; + } + + if (spells[spellid].zone_type == 1 && !zone->CanCastOutdoor()) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to !CanCastOutdoor.'", GetCleanName(), GetSpellName(spellid)); //deleteme + return false; + } + + if (!AECheck && !IsValidSpellRange(spellid, tar)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsValidSpellRange.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if (!IsValidTargetType(spellid, GetSpellTargetType(spellid), tar->GetBodyType())) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsValidTargetType.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if (tar->GetSpecialAbility(SpecialAbility::CastingFromRangeImmunity) && !CombatRange(tar)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IMMUNE_CASTING_FROM_RANGE.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if (tar->IsImmuneToBotSpell(spellid, this)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsImmuneToBotSpell.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if (!DoResistCheckBySpellType(tar, spellid, spellType)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to DoResistCheckBySpellType.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if (!IsCommandedSpell() && !taunting && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting(), spellid) && !tar->IsFleeing()) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to HasOrMayGetAggro.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if ( + (RequiresStackCheck(spellType) || (!RequiresStackCheck(spellType) && CalcBuffDuration(this, tar, spellid) != 0)) + && + tar->CanBuffStack(spellid, GetLevel(), true) < 0 + ) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanBuffStack.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + LogBotPreChecksDetail("{} says, 'Doing CanCastSpellType checks of {} on {}.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if (!CanCastSpellType(spellType, spellid, tar)) { + return false; + } + + return true; +} + +bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { + if (!spellid || !tar) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to failsafe checks.'", GetCleanName(), (spellid ? GetSpellName(spellid) : (spellType ? GetSpellTypeNameByID(spellType) : "Unknown")), (tar ? tar->GetCleanName() : "Unknown")); //deleteme + return false; + } + + uint8 botClass = GetClass(); + //uint8 botLevel = GetLevel(); + + switch (spellType) { + case BotSpellTypes::Buff: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::DamageShields: + case BotSpellTypes::ResistBuffs: + if ( + !( + spells[spellid].target_type == ST_Target || + spells[spellid].target_type == ST_Pet || + (tar == this && spells[spellid].target_type != ST_TargetsTarget) || + spells[spellid].target_type == ST_Group || + spells[spellid].target_type == ST_GroupTeleport //|| + //(botClass == Class::Bard && spells[spellid].target_type == ST_AEBard) //TODO bot rewrite - is this needed? + ) + ) { + LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to target_type checks.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if (tar->IsBlockedBuff(spellid)) { + LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to IsBlockedBuff.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if (IsEffectInSpell(spellid, SE_Teleport) || IsEffectInSpell(spellid, SE_Succor)) { + LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to Teleport.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if (tar->IsPet() && !RuleB(Bots, CanCastIllusionsOnPets) && IsEffectInSpell(spellid, SE_Illusion)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to PetSE_Illusion.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if (spells[spellid].target_type == ST_Pet && (!tar->IsPet() || (tar->GetOwner() != this && !RuleB(Bots, CanCastPetOnlyOnOthersPets)))) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to PetOnly.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if ((IsGroupSpell(spellid) && tar->IsPet()) && (!tar->GetOwner() || (RuleB(Bots, RequirePetAffinity) && !tar->GetOwner()->HasPetAffinity()))) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to PetGroupSpellTarget.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if (!IsCommandedSpell() && IsTargetAlreadyReceivingSpell(tar, spellid)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsTargetAlreadyReceivingSpell.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if (!IsCommandedSpell()) { + switch (tar->GetArchetype()) { + case Archetype::Caster: + if ( + tar->IsBot() && tar->GetLevel() > tar->CastToBot()->GetStopMeleeLevel() && + ( + IsEffectInSpell(spellid, SE_AttackSpeed) || IsEffectInSpell(spellid, SE_ReverseDS)) || + (SpellEffectsCount(spellid) == 1 && IsEffectInSpell(spellid, SE_ATK) || IsEffectInSpell(spellid, SE_STR) + ) + ) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Caster.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + break; + case Archetype::Melee: + if ( + IsEffectInSpell(spellid, SE_IncreaseSpellHaste) || IsEffectInSpell(spellid, SE_ManaPool) || + IsEffectInSpell(spellid, SE_CastingLevel) || IsEffectInSpell(spellid, SE_ManaRegen_v2) || + IsEffectInSpell(spellid, SE_CurrentMana) + ) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Melee.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + break; + case Archetype::Hybrid: //Hybrids get all buffs + default: + break; + } + } + + // Differences for each type + if (spellType != BotSpellTypes::InCombatBuff) { + if (IsEffectInSpell(spellid, SE_AbsorbMagicAtt) || IsEffectInSpell(spellid, SE_Rune)) { + for (int i = 0; i < tar->GetMaxTotalSlots(); i++) { + uint32 buff_count = tar->GetMaxTotalSlots(); + + for (unsigned int j = 0; j < buff_count; j++) { + if (IsValidSpell(tar->GetBuffs()[j].spellid)) { + if (IsLichSpell(tar->GetBuffs()[j].spellid)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsLichSpell.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + } + } + } + } + } + + break; + case BotSpellTypes::PreCombatBuffSong: + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::OutOfCombatBuffSong: + //switch (spells[spellid].target_type) { //TODO bot rewrite - is this needed? + // case ST_AEBard: + // case ST_AECaster: + // case ST_GroupTeleport: + // case ST_Group: + // case ST_Self: + // break; + // default: + // LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to target_type checks.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + // return false; + //} + + if (!IsCommandedSpell() && IsTargetAlreadyReceivingSpell(tar, spellid)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsTargetAlreadyReceivingSpell.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + + if (!IsCommandedSpell()) { + switch (tar->GetArchetype()) { + case Archetype::Caster: + if ( + tar->IsBot() && tar->GetLevel() > tar->CastToBot()->GetStopMeleeLevel() && + ( + IsEffectInSpell(spellid, SE_AttackSpeed) || IsEffectInSpell(spellid, SE_ReverseDS)) || + (SpellEffectsCount(spellid) == 1 && IsEffectInSpell(spellid, SE_ATK) || IsEffectInSpell(spellid, SE_STR) + ) + ) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Caster.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + break; + case Archetype::Melee: + if ( + IsEffectInSpell(spellid, SE_IncreaseSpellHaste) || IsEffectInSpell(spellid, SE_ManaPool) || + IsEffectInSpell(spellid, SE_CastingLevel) || IsEffectInSpell(spellid, SE_ManaRegen_v2) || + IsEffectInSpell(spellid, SE_CurrentMana) + ) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Melee.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return false; + } + break; + case Archetype::Hybrid: //Hybrids get all buffs + default: + break; + } + } + + break; + default: + break; + } + + LogBotPreChecksDetail("{} says, {} on {} passed CanCastSpellType.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + return true; +} + +bool Bot::BotHasEnoughMana(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + //int32 manaCost = GetActSpellCost(spell_id, spells[spell_id].mana); + int32 manaCost = spells[spell_id].mana; + + if (GetMana() < manaCost) { + return false; + } + + return true; +} + +bool Bot::IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spellid) { + + if (!tar || !spellid) { + return true; + } + + if (IsNPC() && CastToNPC()->GetSwarmOwner()) { + return true; + } + + const std::vector v = GatherSpellTargets(); + for (Mob* m : v) { + if ( + m->IsBot() && + m->IsCasting() && + m->CastToBot()->casting_spell_targetid && + entity_list.GetMobID(m->CastToBot()->casting_spell_targetid) == entity_list.GetMobID(tar->GetID()) && + m->CastingSpellID() == spellid + ) { + + return true; + } + else { + if (IsGroupSpell(spellid)) { + if ( + m->IsBot() && + m->IsCasting() && + m->CastToBot()->casting_spell_targetid && + m->CastingSpellID() == spellid + ) { + + const std::vector v = GatherGroupSpellTargets(); + for (Mob* m : v) { + if (entity_list.GetMobID(m->CastToBot()->casting_spell_targetid) == entity_list.GetMobID(m->GetID())) { + return true; + } + } + } + } + } + } + + return false; +} + +bool Bot::DoResistCheck(Mob* tar, uint16 spellid, int32 resist_limit) { + + if (!tar || spellid == 0) { + return false; + } + + int32 resist_difficulty = -spells[spellid].resist_difficulty; + int32 level_mod = (tar->GetLevel() - GetLevel()) * (tar->GetLevel() - GetLevel()) / 2; + + if (tar->GetLevel() - GetLevel() < 0) { + level_mod = -level_mod; + } + + int32 targetResist = 0; + + switch (GetSpellResistType(spellid)) { + case RESIST_NONE: + return true; + case RESIST_MAGIC: + targetResist = tar->GetMR(); + break; + case RESIST_COLD: + targetResist = tar->GetCR(); + break; + case RESIST_FIRE: + targetResist = tar->GetFR(); + break; + case RESIST_POISON: + targetResist = tar->GetPR(); + break; + case RESIST_DISEASE: + targetResist = tar->GetDR(); + break; + case RESIST_CORRUPTION: + targetResist = tar->GetCorrup(); + break; + default: + return true; + } + //LogBotPreChecksDetail("DoResistCheck on {} for {} - TarResist [{}] LMod [{}] ResistDiff [{}] - Adjust [{}] > ResistLim [{}]", tar->GetCleanName(), GetSpellName(spellid), targetResist, level_mod, resist_difficulty, (targetResist + level_mod - resist_difficulty), resist_limit); //deleteme) + if ((targetResist + level_mod - resist_difficulty) > resist_limit) { + return false; + } + + return true; +} + +bool Bot::DoResistCheckBySpellType(Mob* tar, uint16 spellid, uint16 spellType) { + if (!tar || !IsValidSpell(spellid)) { + return false; + } + + if (GetSpellTypeResistLimit(spellType) == 0) { + return true; + } + + return DoResistCheck(tar, spellid, GetSpellTypeResistLimit(spellType)); +} + +bool Bot::IsValidTargetType(uint16 spellid, int targetType, uint8 bodyType) { + if (!spellid) { + return false; + } + + switch (targetType) { + case ST_Undead: + if (bodyType == BodyType::Undead || bodyType == BodyType::SummonedUndead || bodyType == BodyType::Vampire) { + return true; + } + + break; + case ST_Summoned: + if (bodyType == BodyType::Summoned || bodyType == BodyType::Summoned2 || bodyType == BodyType::Summoned3) { + return true; + } + + break; + case ST_Animal: + if (bodyType == BodyType::Animal) { + return true; + } + + break; + case ST_Plant: + if (bodyType == BodyType::Plant) { + return true; + } + + break; + case ST_Giant: + if (bodyType == BodyType::Giant || bodyType == BodyType::RaidGiant) { + return true; + } + + break; + case ST_Dragon: + if (bodyType == BodyType::Dragon || bodyType == BodyType::VeliousDragon) { + return true; + } + + break; + default: + return true; + } + + return false; +} + +bool Bot::IsMobEngagedByAnyone(Mob* tar) { + if (!tar) { + return false; + } + + const std::vector v = GatherSpellTargets(true); + + for (Mob* m : v) { + if (m->GetTarget() == tar) { + if ( + m->IsBot() && + !m->CastToBot()->GetHoldFlag() && + m->IsEngaged() && + ( + !m->CastToBot()->IsBotNonSpellFighter() || + ( + m->GetLevel() >= m->CastToBot()->GetStopMeleeLevel() && + !m->IsCasting() + ) + ) + ) { + return true; + } + + if (m->IsCasting() && SpellBreaksMez(m->CastingSpellID())) { + return true; + } + + if (m->IsClient() && (m->CastToClient()->AutoAttackEnabled() || m->CastToClient()->AutoFireEnabled())) { + return true; + } + } + } + + return false; +} + +bool Bot::IsValidMezTarget(Mob* owner, Mob* npc, uint16 spellid) { + if (npc->GetSpecialAbility(SpecialAbility::MesmerizeImmunity)) { + return false; + } + + if (!npc->CastToNPC()->IsOnHatelist(owner)) { + return false; + } + + if (npc->IsMezzed() || HasBotAttackFlag(npc)) { + return false; + } + + if (npc->HasOwner() && npc->GetOwner() && npc->GetOwner()->IsOfClientBotMerc()) { + return false; + } + + if (!IsValidTargetType(spellid, GetSpellTargetType(spellid), npc->GetBodyType())) { + return false; + } + + if (!IsAttackAllowed(GetTarget())) { + return false; + } + + if (!DoLosChecks(this, npc)) { + return false; + } + + if (IsMobEngagedByAnyone(npc)) { + return false; + } + + int buff_count = npc->GetMaxTotalSlots(); + auto npc_buffs = npc->GetBuffs(); + + for (int i = 0; i < buff_count; i++) { + if (IsDetrimentalSpell(npc_buffs[i].spellid) && IsEffectInSpell(npc_buffs[i].spellid, SE_CurrentHP)) { + return false; + } + } + + return true; +} + +void Bot::SetBotSetting(uint8 settingType, uint16 botSetting, int settingValue) { + switch (settingType) { + case BotSettingCategories::BaseSetting: + SetBotBaseSetting(botSetting, settingValue); + break; + case BotSettingCategories::SpellHold: + SetSpellHold(botSetting, settingValue); + break; + case BotSettingCategories::SpellDelay: + SetSpellDelay(botSetting, settingValue); + break; + case BotSettingCategories::SpellMinThreshold: + SetSpellMinThreshold(botSetting, settingValue); + break; + case BotSettingCategories::SpellMaxThreshold: + SetSpellMaxThreshold(botSetting, settingValue); + break; + case BotSettingCategories::SpellTypeAggroCheck: + SetSpellTypeAggroCheck(botSetting, settingValue); + break; + case BotSettingCategories::SpellTypeMinManaPct: + SetSpellTypeMinManaLimit(botSetting, settingValue); + break; + case BotSettingCategories::SpellTypeMaxManaPct: + SetSpellTypeMaxManaLimit(botSetting, settingValue); + break; + case BotSettingCategories::SpellTypeMinHPPct: + SetSpellTypeMinHPLimit(botSetting, settingValue); + break; + case BotSettingCategories::SpellTypeMaxHPPct: + SetSpellTypeMaxHPLimit(botSetting, settingValue); + break; + case BotSettingCategories::SpellTypeIdlePriority: + SetSpellTypePriority(botSetting, BotPriorityCategories::Idle, settingValue); + break; + case BotSettingCategories::SpellTypeEngagedPriority: + SetSpellTypePriority(botSetting, BotPriorityCategories::Engaged, settingValue); + break; + case BotSettingCategories::SpellTypePursuePriority: + SetSpellTypePriority(botSetting, BotPriorityCategories::Pursue, settingValue); + break; + case BotSettingCategories::SpellTypeAEOrGroupTargetCount: + SetSpellTypeAEOrGroupTargetCount(botSetting, settingValue); + break; + } +} + +void Bot::SetBotBaseSetting(uint16 botSetting, int settingValue) { + switch (botSetting) { + case BotBaseSettings::ExpansionBitmask: + SetExpansionBitmask(settingValue); + break; + case BotBaseSettings::ShowHelm: + SetShowHelm(settingValue); + break; + case BotBaseSettings::FollowDistance: + SetFollowDistance(EQ::Clamp(static_cast(settingValue), static_cast(1), BOT_FOLLOW_DISTANCE_DEFAULT_MAX)); + break; + case BotBaseSettings::StopMeleeLevel: + SetStopMeleeLevel(settingValue); + break; + case BotBaseSettings::EnforceSpellSettings: + SetBotEnforceSpellSetting(settingValue); + break; + case BotBaseSettings::RangedSetting: + SetBotRangedSetting(settingValue); + break; + case BotBaseSettings::PetSetTypeSetting: + SetPetChooserID(settingValue); + break; + case BotBaseSettings::BehindMob: + SetBehindMob(settingValue); + break; + case BotBaseSettings::CasterRange: + SetBotCasterRange(settingValue); + break; + case BotBaseSettings::IllusionBlock: + SetIllusionBlock(settingValue); + break; + case BotBaseSettings::MaxMeleeRange: + SetMaxMeleeRange(settingValue); + break; + case BotBaseSettings::MedInCombat: + SetMedInCombat(settingValue); + break; + case BotBaseSettings::HPWhenToMed: + SetHPWhenToMed(settingValue); + break; + case BotBaseSettings::ManaWhenToMed: + SetManaWhenToMed(settingValue); + break; + default: + break; + } +} + +int Bot::GetBotBaseSetting(uint16 botSetting) { + switch (botSetting) { + case BotBaseSettings::ExpansionBitmask: + //LogBotSettingsDetail("Returning current GetExpansionBitmask of [{}] for [{}]", GetExpansionBitmask(), GetCleanName()); //deleteme + return GetExpansionBitmask(); + case BotBaseSettings::ShowHelm: + //LogBotSettingsDetail("Returning current GetShowHelm of [{}] for [{}]", GetShowHelm(), GetCleanName()); //deleteme + return GetShowHelm(); + case BotBaseSettings::FollowDistance: + //LogBotSettingsDetail("Returning current GetFollowDistance of [{}] for [{}]", GetFollowDistance(), GetCleanName()); //deleteme + return GetFollowDistance(); + case BotBaseSettings::StopMeleeLevel: + //LogBotSettingsDetail("Returning current GetStopMeleeLevel of [{}] for [{}]", GetStopMeleeLevel(), GetCleanName()); //deleteme + return GetStopMeleeLevel(); + case BotBaseSettings::EnforceSpellSettings: + //LogBotSettingsDetail("Returning current GetBotEnforceSpellSetting of [{}] for [{}]", GetBotEnforceSpellSetting(), GetCleanName()); //deleteme + return GetBotEnforceSpellSetting(); + case BotBaseSettings::RangedSetting: + //LogBotSettingsDetail("Returning current IsBotRanged of [{}] for [{}]", IsBotRanged(), GetCleanName()); //deleteme + return IsBotRanged(); + case BotBaseSettings::PetSetTypeSetting: + //LogBotSettingsDetail("Returning current GetPetChooserID of [{}] for [{}]", GetPetChooserID(), GetCleanName()); //deleteme + return GetPetChooserID(); + case BotBaseSettings::BehindMob: + //LogBotSettingsDetail("Returning current GetBehindMob of [{}] for [{}]", GetBehindMob(), GetCleanName()); //deleteme + return GetBehindMob(); + case BotBaseSettings::CasterRange: + //LogBotSettingsDetail("Returning current GetBotCasterRange of [{}] for [{}]", GetBotCasterRange(), GetCleanName()); //deleteme + return GetBotCasterRange(); + case BotBaseSettings::IllusionBlock: + //LogBotSettingsDetail("Returning current GetIllusionBlock of [{}] for [{}]", GetIllusionBlock(), GetCleanName()); //deleteme + return GetIllusionBlock(); + case BotBaseSettings::MaxMeleeRange: + //LogBotSettingsDetail("Returning current MaxMeleeRange of [{}] for [{}]", GetMaxMeleeRange(), GetCleanName()); //deleteme + return GetMaxMeleeRange(); + case BotBaseSettings::MedInCombat: + //LogBotSettingsDetail("Returning current GetMedInCombate of [{}] for [{}]", GetMaxMeleeRange(), GetCleanName()); //deleteme + return GetMedInCombat(); + case BotBaseSettings::HPWhenToMed: + //LogBotSettingsDetail("Returning current GetHPWhenToMed of [{}] for [{}]", GetMaxMeleeRange(), GetCleanName()); //deleteme + return GetHPWhenToMed(); + case BotBaseSettings::ManaWhenToMed: + //LogBotSettingsDetail("Returning current GetManaWhenToMed of [{}] for [{}]", GetMaxMeleeRange(), GetCleanName()); //deleteme + return GetManaWhenToMed(); + default: + return true; + } + + return true; +} + +int Bot::GetDefaultBotBaseSetting(uint16 botSetting) { + switch (botSetting) { + case BotBaseSettings::ExpansionBitmask: + return RuleI(Bots, BotExpansionSettings); + case BotBaseSettings::ShowHelm: + return true; + case BotBaseSettings::FollowDistance: + return BOT_FOLLOW_DISTANCE_DEFAULT; + case BotBaseSettings::StopMeleeLevel: + if (IsCasterClass(GetClass())) { + return RuleI(Bots, CasterStopMeleeLevel); + } + else { + return 255; + } + case BotBaseSettings::PetSetTypeSetting: + return 0; + case BotBaseSettings::BehindMob: + if (GetClass() == Class::Rogue) { + return true; + } + else { + return false; + } + case BotBaseSettings::CasterRange: + switch (GetClass()) { + case Class::Warrior: + case Class::Monk: + case Class::Rogue: + case Class::Berserker: + return 0; + case Class::Bard: + return 30; + default: + return 90; + } + case BotBaseSettings::MedInCombat: + if (IsCasterClass(GetClass())) { + return true; + } + + return false; + case BotBaseSettings::HPWhenToMed: + case BotBaseSettings::ManaWhenToMed: + return 80; + case BotBaseSettings::EnforceSpellSettings: + case BotBaseSettings::RangedSetting: + case BotBaseSettings::IllusionBlock: + case BotBaseSettings::MaxMeleeRange: + default: + return false; + } + + return true; +} + + +void Bot::LoadDefaultBotSettings() { + for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { + SetBotBaseSetting(i, GetDefaultSetting(BotSettingCategories::BaseSetting, i)); + LogBotSettingsDetail("{} says, 'Setting default {} [{}] to [{}]'", GetCleanName(), GetBotSettingCategoryName(i), i, GetDefaultBotBaseSetting(i)); //deleteme + } + + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + BotSpellSettings_Struct t; + + t.spellType = i; + t.shortName = GetSpellTypeShortNameByID(i); + t.name = GetSpellTypeNameByID(i); + t.hold = GetDefaultSpellHold(i); + t.delay = GetDefaultSpellDelay(i); + t.minThreshold = GetDefaultSpellMinThreshold(i); + t.maxThreshold = GetDefaultSpellMaxThreshold(i); + t.resistLimit = GetDefaultSpellTypeResistLimit(i); + t.aggroCheck = GetDefaultSpellTypeAggroCheck(i); + t.minManaPct = GetDefaultSpellTypeMinManaLimit(i); + t.maxManaPct = GetDefaultSpellTypeMaxManaLimit(i); + t.minHPPct = GetDefaultSpellTypeMinHPLimit(i); + t.maxHPPct = GetDefaultSpellTypeMaxHPLimit(i); + t.idlePriority = GetDefaultSpellTypePriority(i, BotPriorityCategories::Idle, GetClass()); + t.engagedPriority = GetDefaultSpellTypePriority(i, BotPriorityCategories::Engaged, GetClass()); + t.pursuePriority = GetDefaultSpellTypePriority(i, BotPriorityCategories::Pursue, GetClass()); + t.AEOrGroupTargetCount = GetDefaultSpellTypeAEOrGroupTargetCount(i); + t.recastTimer.Start(); + + _spellSettings.push_back(t); + + LogBotSettingsDetail("{} says, 'Setting defaults for {} ({}) [#{}]'", GetCleanName(), t.name, t.shortName, t.spellType); //deleteme + LogBotSettingsDetail("{} says, 'Hold = [{}] | Delay = [{}ms] | MinThreshold = [{}\%] | MaxThreshold = [{}\%]'", GetCleanName(), GetDefaultSpellHold(i), GetDefaultSpellDelay(i), GetDefaultSpellMinThreshold(i), GetDefaultSpellMaxThreshold(i)); //deleteme + LogBotSettingsDetail("{} says, 'AggroCheck = [{}] | MinManaPCT = [{}\%] | MaxManaPCT = [{}\%] | MinHPPCT = [{}\% | MaxHPPCT = [{}\%]'", GetCleanName(), GetDefaultSpellTypeAggroCheck(i), GetDefaultSpellTypeMinManaLimit(i), GetDefaultSpellTypeMaxManaLimit(i), GetDefaultSpellTypeMinHPLimit(i), GetDefaultSpellTypeMaxHPLimit(i)); //deleteme + LogBotSettingsDetail("{} says, 'IdlePriority = [{}] | EngagedPriority = [{}] | PursuePriority = [{}] | AEOrGroupTargetCount = [{}] | recastTimer = [{}]'", GetCleanName(), GetDefaultSpellTypeIdlePriority(i, GetClass()), GetDefaultSpellTypeEngagedPriority(i, GetClass()), GetDefaultSpellTypePursuePriority(i, GetClass()), GetDefaultSpellTypeAEOrGroupTargetCount(i), t.recastTimer.GetRemainingTime()); //deleteme + } +} + +void Bot::SetBotSpellRecastTimer(uint16 spellType, Mob* tar, bool preCast) { + if (!tar) { + return; + } + + if (!preCast && BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType)) { + return; + } + + uint32 addedDelay = 0; + + switch (spellType) { //Additional delays + case BotSpellTypes::Mez: + addedDelay = RuleI(Bots, MezSuccessDelay); + break; + case BotSpellTypes::AEMez: + addedDelay = RuleI(Bots, AEMezSuccessDelay); + break; + } + + if (tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot()) { + return tar->GetOwner()->SetSpellTypeRecastTimer(spellType, (GetUltimateSpellDelay(spellType, tar) + addedDelay)); + } + else if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType)) { + tar->SetSpellTypeRecastTimer(spellType, (GetUltimateSpellDelay(spellType, tar) + addedDelay)); + } + else { + SetSpellTypeRecastTimer(spellType, (GetUltimateSpellDelay(spellType, tar) + addedDelay)); + } +} + +BotSpell Bot::GetSpellByHealType(uint16 spellType, Mob* tar) { + switch (spellType) { + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::PetVeryFastHeals: + return GetBestBotSpellForVeryFastHeal(this, tar, spellType); + case BotSpellTypes::FastHeals: + case BotSpellTypes::PetFastHeals: + return GetBestBotSpellForFastHeal(this, tar, spellType); + case BotSpellTypes::RegularHeal: + case BotSpellTypes::PetRegularHeals: + return GetBestBotSpellForRegularSingleTargetHeal(this, tar, spellType); + case BotSpellTypes::GroupHeals: + return GetBestBotSpellForGroupHeal(this, tar, spellType); + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::PetCompleteHeals: + return GetBestBotSpellForPercentageHeal(this, tar, spellType); + case BotSpellTypes::GroupCompleteHeals: + return GetBestBotSpellForGroupCompleteHeal(this, tar, spellType); + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetHoTHeals: + return GetBestBotSpellForHealOverTime(this, tar, spellType); + case BotSpellTypes::GroupHoTHeals: + return GetBestBotSpellForGroupHealOverTime(this, tar, spellType); + } +} + +uint16 Bot::GetSpellTypePriority(uint16 spellType, uint8 priorityType) { + switch (priorityType) { + case BotPriorityCategories::Idle: + return _spellSettings[spellType].idlePriority; + case BotPriorityCategories::Engaged: + return _spellSettings[spellType].engagedPriority; + case BotPriorityCategories::Pursue: + return _spellSettings[spellType].pursuePriority; + default: + return 0; + } +} + +int Bot::GetDefaultSetting(uint16 settingCategory, uint16 settingType) { + switch (settingCategory) { + case BotSettingCategories::BaseSetting: + return GetDefaultBotBaseSetting(settingType); + case BotSettingCategories::SpellHold: + return GetDefaultSpellHold(settingType); + case BotSettingCategories::SpellDelay: + return GetDefaultSpellDelay(settingType); + case BotSettingCategories::SpellMinThreshold: + return GetDefaultSpellMinThreshold(settingType); + case BotSettingCategories::SpellMaxThreshold: + return GetDefaultSpellMinThreshold(settingType); + case BotSettingCategories::SpellTypeAggroCheck: + return GetDefaultSpellTypeAggroCheck(settingType); + case BotSettingCategories::SpellTypeMinManaPct: + return GetDefaultSpellTypeMinManaLimit(settingType); + case BotSettingCategories::SpellTypeMaxManaPct: + return GetDefaultSpellTypeMaxManaLimit(settingType); + case BotSettingCategories::SpellTypeMinHPPct: + return GetDefaultSpellTypeMinHPLimit(settingType); + case BotSettingCategories::SpellTypeMaxHPPct: + return GetDefaultSpellTypeMaxHPLimit(settingType); + case BotSettingCategories::SpellTypeIdlePriority: + return GetDefaultSpellTypePriority(settingType, BotPriorityCategories::Idle, GetClass()); + case BotSettingCategories::SpellTypeEngagedPriority: + return GetDefaultSpellTypePriority(settingType, BotPriorityCategories::Engaged, GetClass()); + case BotSettingCategories::SpellTypePursuePriority: + return GetDefaultSpellTypePriority(settingType, BotPriorityCategories::Pursue, GetClass()); + case BotSettingCategories::SpellTypeAEOrGroupTargetCount: + return GetDefaultSpellTypeAEOrGroupTargetCount(settingType); + default: + break; + } +} + +int Bot::GetSetting(uint16 settingCategory, uint16 settingType) { + switch (settingCategory) { + case BotSettingCategories::BaseSetting: + return GetBotBaseSetting(settingType); + case BotSettingCategories::SpellHold: + return GetSpellHold(settingType); + case BotSettingCategories::SpellDelay: + return GetSpellDelay(settingType); + case BotSettingCategories::SpellMinThreshold: + return GetSpellMinThreshold(settingType); + case BotSettingCategories::SpellMaxThreshold: + return GetSpellMinThreshold(settingType); + case BotSettingCategories::SpellTypeAggroCheck: + return GetSpellTypeAggroCheck(settingType); + case BotSettingCategories::SpellTypeMinManaPct: + return GetSpellTypeMinManaLimit(settingType); + case BotSettingCategories::SpellTypeMaxManaPct: + return GetSpellTypeMaxManaLimit(settingType); + case BotSettingCategories::SpellTypeMinHPPct: + return GetSpellTypeMinHPLimit(settingType); + case BotSettingCategories::SpellTypeMaxHPPct: + return GetSpellTypeMaxHPLimit(settingType); + case BotSettingCategories::SpellTypeIdlePriority: + return GetSpellTypePriority(settingType, BotPriorityCategories::Idle); + case BotSettingCategories::SpellTypeEngagedPriority: + return GetSpellTypePriority(settingType, BotPriorityCategories::Engaged); + case BotSettingCategories::SpellTypePursuePriority: + return GetSpellTypePriority(settingType, BotPriorityCategories::Pursue); + case BotSettingCategories::SpellTypeAEOrGroupTargetCount: + return GetSpellTypeAEOrGroupTargetCount(settingType); + default: + break; + } +} + +uint16 Bot::GetDefaultSpellTypePriority(uint16 spellType, uint8 priorityType, uint8 botClass) { + switch (priorityType) { + case BotPriorityCategories::Idle: + return GetDefaultSpellTypeIdlePriority(spellType, botClass); + case BotPriorityCategories::Engaged: + return GetDefaultSpellTypeEngagedPriority(spellType, botClass); + case BotPriorityCategories::Pursue: + return GetDefaultSpellTypePursuePriority(spellType, botClass); + default: + return 0; + } +} + +uint16 Bot::GetDefaultSpellTypeIdlePriority(uint16 spellType, uint8 botClass) { + if (!BOT_SPELL_TYPES_BENEFICIAL(spellType, botClass)) { + return 0; + } + + uint16 priority = 0; + + switch (spellType) { + case BotSpellTypes::VeryFastHeals: + priority = 1; + + break; + case BotSpellTypes::FastHeals: + priority = 2; + + break; + case BotSpellTypes::GroupHeals: + priority = 3; + + break; + case BotSpellTypes::RegularHeal: + priority = 4; + + break; + case BotSpellTypes::GroupCompleteHeals: + priority = 5; + + break; + case BotSpellTypes::CompleteHeal: + priority = 6; + + break; + case BotSpellTypes::GroupHoTHeals: + priority = 7; + + break; + case BotSpellTypes::HoTHeals: + priority = 8; + + break; + case BotSpellTypes::GroupCures: + priority = 9; + + break; + case BotSpellTypes::Cure: + priority = 10; + + break; + case BotSpellTypes::InCombatBuff: // this has a check at the end to decrement everything below if it's not a SK (SK use InCombatBuffs as it is their hate line so it's not use when Idle) + priority = 11; + + break; + case BotSpellTypes::PetVeryFastHeals: + priority = 12; + + break; + case BotSpellTypes::PetFastHeals: + priority = 13; + + break; + case BotSpellTypes::PetRegularHeals: + priority = 14; + + break; + case BotSpellTypes::PetCompleteHeals: + priority = 15; + + break; + case BotSpellTypes::PetHoTHeals: + priority = 16; + + break; + case BotSpellTypes::Pet: + priority = 17; + + break; + case BotSpellTypes::Buff: + priority = 18; + + break; + case BotSpellTypes::OutOfCombatBuffSong: + priority = 19; + + break; + case BotSpellTypes::ResistBuffs: + priority = 20; + + break; + case BotSpellTypes::DamageShields: + priority = 21; + + break; + case BotSpellTypes::PetBuffs: + priority = 22; + + break; + case BotSpellTypes::PreCombatBuff: + priority = 23; + + break; + case BotSpellTypes::PreCombatBuffSong: + priority = 24; + + break; + default: + priority = 0; //unused + + break; + } + + if ( + priority >= 11 && + botClass && botClass == Class::ShadowKnight && + spellType != BotSpellTypes::InCombatBuff + ) { + --priority; + } + + return priority; +} + +uint16 Bot::GetDefaultSpellTypeEngagedPriority(uint16 spellType, uint8 botClass) { + switch (spellType) { + case BotSpellTypes::Escape: + return 1; + case BotSpellTypes::VeryFastHeals: + return 2; + case BotSpellTypes::FastHeals: + return 3; + case BotSpellTypes::GroupHeals: + return 4; + case BotSpellTypes::RegularHeal: + return 5; + case BotSpellTypes::GroupCompleteHeals: + return 6; + case BotSpellTypes::CompleteHeal: + return 7; + case BotSpellTypes::GroupHoTHeals: + return 8; + case BotSpellTypes::HoTHeals: + return 9; + case BotSpellTypes::GroupCures: + return 10; + case BotSpellTypes::Cure: + return 11; + case BotSpellTypes::PetVeryFastHeals: + return 12; + case BotSpellTypes::PetFastHeals: + return 13; + case BotSpellTypes::PetRegularHeals: + return 14; + case BotSpellTypes::PetCompleteHeals: + return 15; + case BotSpellTypes::PetHoTHeals: + return 16; + case BotSpellTypes::AELifetap: + return 17; + case BotSpellTypes::Lifetap: + return 18; + case BotSpellTypes::HateRedux: + return 19; + case BotSpellTypes::AEMez: + return 20; + case BotSpellTypes::Mez: + return 21; + case BotSpellTypes::AEDispel: + return 22; + case BotSpellTypes::Dispel: + return 23; + case BotSpellTypes::AEDebuff: + return 24; + case BotSpellTypes::Debuff: + return 25; + case BotSpellTypes::AESnare: + return 26; + case BotSpellTypes::Snare: + return 27; + case BotSpellTypes::AEFear: + return 28; + case BotSpellTypes::Fear: + return 29; + case BotSpellTypes::AESlow: + return 30; + case BotSpellTypes::Slow: + return 31; + case BotSpellTypes::AERoot: + return 32; + case BotSpellTypes::Root: + return 33; + case BotSpellTypes::AEDoT: + return 34; + case BotSpellTypes::DOT: + return 35; + case BotSpellTypes::AEStun: + return 36; + case BotSpellTypes::PBAENuke: + return 37; + case BotSpellTypes::AENukes: + return 38; + case BotSpellTypes::AERains: + return 39; + case BotSpellTypes::Stun: + return 40; + case BotSpellTypes::Nuke: + return 41; + case BotSpellTypes::InCombatBuff: + return 42; + case BotSpellTypes::InCombatBuffSong: + return 43; + case BotSpellTypes::Pet: + return 44; + default: + return 0; + } +} + +uint16 Bot::GetDefaultSpellTypePursuePriority(uint16 spellType, uint8 botClass) { + switch (spellType) { + case BotSpellTypes::Escape: + return 1; + case BotSpellTypes::VeryFastHeals: + return 2; + case BotSpellTypes::FastHeals: + return 3; + case BotSpellTypes::GroupHeals: + return 4; + case BotSpellTypes::RegularHeal: + return 5; + case BotSpellTypes::GroupCompleteHeals: + return 6; + case BotSpellTypes::CompleteHeal: + return 7; + case BotSpellTypes::GroupHoTHeals: + return 8; + case BotSpellTypes::HoTHeals: + return 9; + case BotSpellTypes::GroupCures: + return 10; + case BotSpellTypes::Cure: + return 11; + case BotSpellTypes::Snare: + return 12; + case BotSpellTypes::Lifetap: + return 13; + case BotSpellTypes::Dispel: + return 14; + case BotSpellTypes::Stun: + return 15; + case BotSpellTypes::Nuke: + return 16; + case BotSpellTypes::DOT: + return 17; + case BotSpellTypes::PetVeryFastHeals: + return 18; + case BotSpellTypes::PetFastHeals: + return 19; + case BotSpellTypes::PetRegularHeals: + return 20; + case BotSpellTypes::PetCompleteHeals: + return 21; + case BotSpellTypes::PetHoTHeals: + return 22; + default: + return 0; + } +} + +uint16 Bot::GetDefaultSpellTypeResistLimit(uint16 spellType) { + + if (!BOT_SPELL_TYPES_BENEFICIAL(spellType, GetClass())) { + return RuleI(Bots, SpellResistLimit); + } + else { + return 0; + } +} + +bool Bot::GetDefaultSpellTypeAggroCheck(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Nuke: + case BotSpellTypes::AESlow: + case BotSpellTypes::Slow: + case BotSpellTypes::AESnare: + case BotSpellTypes::Snare: + case BotSpellTypes::AEDispel: + case BotSpellTypes::Dispel: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::Debuff: + case BotSpellTypes::AEDoT: + case BotSpellTypes::DOT: + case BotSpellTypes::AEStun: + case BotSpellTypes::Stun: + return true; + default: + return false; + } +} + +uint8 Bot::GetDefaultSpellTypeMinManaLimit(uint16 spellType) { + return 0; +} + +uint8 Bot::GetDefaultSpellTypeMaxManaLimit(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::InCombatBuff: + if (GetClass() == Class::Shaman) { + return 75; + } + + break; + default: + break; + } + + return 100; +} + +uint8 Bot::GetDefaultSpellTypeMinHPLimit(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::InCombatBuff: + if (GetClass() == Class::Shaman) { + return 40; + } + + break; + default: + break; + } + + return 0; +} + +uint8 Bot::GetDefaultSpellTypeMaxHPLimit(uint16 spellType) { + return 100; +} + +uint16 Bot::GetDefaultSpellTypeAEOrGroupTargetCount(uint16 spellType) { + if (IsAEBotSpellType(spellType)) { + return RuleI(Bots, MinTargetsForAESpell); + } + + if (IsGroupBotSpellType(spellType)) { + return RuleI(Bots, MinTargetsForGroupSpell); + } + + return 0; +} + +void Bot::SetSpellTypePriority(uint16 spellType, uint8 priorityType, uint16 priority) { + switch (priorityType) { + case BotPriorityCategories::Idle: + _spellSettings[spellType].idlePriority = priority; + break; + case BotPriorityCategories::Engaged: + _spellSettings[spellType].engagedPriority = priority; + break; + case BotPriorityCategories::Pursue: + _spellSettings[spellType].pursuePriority = priority; + break; + default: + return; + } +} + +void Bot::SetSpellTypeResistLimit(uint16 spellType, uint16 resistLimit) { + _spellSettings[spellType].resistLimit = resistLimit; +} + +void Bot::SetSpellTypeAggroCheck(uint16 spellType, bool aggroCheck) { + _spellSettings[spellType].aggroCheck = aggroCheck; +} + +void Bot::SetSpellTypeMinManaLimit(uint16 spellType, uint8 manaLimit) { + _spellSettings[spellType].minManaPct = manaLimit; +} + +void Bot::SetSpellTypeMaxManaLimit(uint16 spellType, uint8 manaLimit) { + _spellSettings[spellType].maxManaPct = manaLimit; +} + +void Bot::SetSpellTypeMinHPLimit(uint16 spellType, uint8 hpLimit) { + _spellSettings[spellType].minHPPct = hpLimit; +} + +void Bot::SetSpellTypeMaxHPLimit(uint16 spellType, uint8 hpLimit) { + _spellSettings[spellType].maxHPPct = hpLimit; +} + +void Bot::SetSpellTypeAEOrGroupTargetCount(uint16 spellType, uint16 targetCount) { + _spellSettings[spellType].AEOrGroupTargetCount = targetCount; +} + +std::string Bot::GetBotSettingCategoryName(uint8 setting_type) { + switch (setting_type) { + case BotBaseSettings::ExpansionBitmask: + return "ExpansionBitmask"; + case BotBaseSettings::ShowHelm: + return "ShowHelm"; + case BotBaseSettings::FollowDistance: + return "FollowDistance"; + case BotBaseSettings::StopMeleeLevel: + return "StopMeleeLevel"; + case BotBaseSettings::EnforceSpellSettings: + return "EnforceSpellSettings"; + case BotBaseSettings::RangedSetting: + return "RangedSetting"; + case BotBaseSettings::PetSetTypeSetting: + return "PetSetTypeSetting"; + case BotBaseSettings::BehindMob: + return "BehindMob"; + case BotBaseSettings::CasterRange: + return "CasterRange"; + case BotBaseSettings::IllusionBlock: + return "IllusionBlock"; + case BotBaseSettings::MaxMeleeRange: + return "MaxMeleeRange"; + case BotBaseSettings::MedInCombat: + return "MedInCombat"; + case BotBaseSettings::HPWhenToMed: + return "HPWhenToMed"; + case BotBaseSettings::ManaWhenToMed: + return "ManaWhenToMed"; + default: + return "Null"; + } + + return "Null"; +} + +std::string Bot::GetBotSpellCategoryName(uint8 setting_type) { + switch (setting_type) { + case BotSettingCategories::BaseSetting: + return "BaseSetting"; + case BotSettingCategories::SpellHold: + return "SpellHold"; + case BotSettingCategories::SpellDelay: + return "SpellDelay"; + case BotSettingCategories::SpellMinThreshold: + return "SpellMinThreshold"; + case BotSettingCategories::SpellMaxThreshold: + return "SpellMaxThreshold"; + case BotSettingCategories::SpellTypeAggroCheck: + return "SpellTypeAggroCheck"; + case BotSettingCategories::SpellTypeMinManaPct: + return "SpellTypeMinManaPct"; + case BotSettingCategories::SpellTypeMaxManaPct: + return "SpellTypeMaxManaPct"; + case BotSettingCategories::SpellTypeMinHPPct: + return "SpellTypeMinHPPct"; + case BotSettingCategories::SpellTypeMaxHPPct: + return "SpellTypeMaxHPPct"; + case BotSettingCategories::SpellTypeIdlePriority: + return "SpellTypeIdlePriority"; + case BotSettingCategories::SpellTypeEngagedPriority: + return "SpellTypeEngagedPriority"; + case BotSettingCategories::SpellTypePursuePriority: + return "SpellTypePursuePriority"; + case BotSettingCategories::SpellTypeAEOrGroupTargetCount: + return "SpellTypeAEOrGroupTargetCount"; + case BotSettingCategories::SpellTypeRecastDelay: + return "SpellTypeRecastDelay"; + default: + return "Null"; + } + + return "Null"; +} + +std::list Bot::GetSpellTypesPrioritized(uint8 priorityType) { + std::list castOrder; + std::list tempCastOrder; + + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; i++) { + BotSpellTypeOrder typeSettings = { + .spellType = i, + .priority = GetSpellTypePriority(i, priorityType) + }; + + castOrder.emplace_back(typeSettings); + } + + for (auto& currentType : castOrder) { + if (currentType.priority != 0) { + tempCastOrder.emplace_back(currentType); + } + } + + castOrder = tempCastOrder; + + if (castOrder.size() > 1) { + castOrder.sort( + [](BotSpellTypeOrder const& l, BotSpellTypeOrder const& r) { + return l.priority < r.priority; + } + ); + } + + return castOrder; +} + +bool Bot::AttemptAICastSpell(uint16 spellType) { + bool result = false; + + Mob* tar = GetTarget(); + + if (!taunting && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting())) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of [{}] due to GetSpellTypeAggroCheck and HasOrMayGetAggro.'", GetCleanName(), GetSpellTypeNameByID(spellType)); //deleteme + return result; + } + + if (BOT_SPELL_TYPES_BENEFICIAL(spellType, GetClass())) { + if (!PrecastChecks(this, spellType) || !AICastSpell(this, GetChanceToCastBySpellType(spellType), spellType)) { + if (GetClass() == Class::Bard) { + return result; + } + + if (!tar || !PrecastChecks(tar, spellType) || !AICastSpell(tar, GetChanceToCastBySpellType(spellType), spellType)) { + if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(spellType), BotAISpellRange, spellType)) { + return result; + } + } + } + } + else { + if (!tar || !PrecastChecks(tar, spellType) || !AICastSpell(tar, GetChanceToCastBySpellType(spellType), spellType)) { + return result; + } + } + + result = true; + + return result; +} + +uint16 Bot::GetSpellListSpellType(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::AEStun: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Nuke: + case BotSpellTypes::Stun: + return BotSpellTypes::Nuke; + case BotSpellTypes::RegularHeal: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + return BotSpellTypes::RegularHeal; + case BotSpellTypes::Buff: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::DamageShields: + case BotSpellTypes::ResistBuffs: + return BotSpellTypes::Buff; + case BotSpellTypes::AEMez: + case BotSpellTypes::Mez: + return BotSpellTypes::Mez; + case BotSpellTypes::AEDebuff: + case BotSpellTypes::Debuff: + return BotSpellTypes::Debuff; + case BotSpellTypes::AESlow: + case BotSpellTypes::Slow: + return BotSpellTypes::Slow; + case BotSpellTypes::AESnare: + case BotSpellTypes::Snare: + return BotSpellTypes::Snare; + case BotSpellTypes::AEFear: + case BotSpellTypes::Fear: + return BotSpellTypes::Fear; + case BotSpellTypes::GroupCures: + case BotSpellTypes::Cure: + return BotSpellTypes::Cure; + case BotSpellTypes::AERoot: + case BotSpellTypes::Root: + return BotSpellTypes::Root; + case BotSpellTypes::AEDispel: + case BotSpellTypes::Dispel: + return BotSpellTypes::Dispel; + case BotSpellTypes::AEDoT: + case BotSpellTypes::DOT: + return BotSpellTypes::DOT; + case BotSpellTypes::AELifetap: + case BotSpellTypes::Lifetap: + return BotSpellTypes::Lifetap; + case BotSpellTypes::Charm: + case BotSpellTypes::Escape: + case BotSpellTypes::HateRedux: + case BotSpellTypes::InCombatBuff: + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::OutOfCombatBuffSong: + case BotSpellTypes::Pet: + case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::PreCombatBuffSong: + case BotSpellTypes::Resurrect: + default: + return spellType; + } + + return spellType; +} + +bool Bot::IsValidSpellTypeBySpellID(uint16 spellType, uint16 spellid) { + if (IsAEBotSpellType(spellType) && !IsAnyAESpell(spellid)) { + return false; + } + + if (IsGroupBotSpellType(spellType) && !IsGroupSpell(spellid)) { + return false; + } + + switch (spellType) { //TODO bot rewrite - fix Buff/ResistBuff + case BotSpellTypes::Buff: + if (IsEffectInSpell(spellid, SE_DamageShield)) { + return false; + } + + if ( + IsEffectInSpell(spellid, SE_ResistMagic) || + IsEffectInSpell(spellid, SE_ResistFire) || + IsEffectInSpell(spellid, SE_ResistCold) || + IsEffectInSpell(spellid, SE_ResistPoison) || + IsEffectInSpell(spellid, SE_ResistDisease) || + IsEffectInSpell(spellid, SE_ResistCorruption) || + IsEffectInSpell(spellid, SE_ResistAll) + ) { + return false; + } + + return true; + case BotSpellTypes::ResistBuffs: + if ( + IsEffectInSpell(spellid, SE_ResistMagic) || + IsEffectInSpell(spellid, SE_ResistFire) || + IsEffectInSpell(spellid, SE_ResistCold) || + IsEffectInSpell(spellid, SE_ResistPoison) || + IsEffectInSpell(spellid, SE_ResistDisease) || + IsEffectInSpell(spellid, SE_ResistCorruption) || + IsEffectInSpell(spellid, SE_ResistAll) + ) { + return true; + } + + return false; + case BotSpellTypes::DamageShields: + if (IsEffectInSpell(spellid, SE_DamageShield)) { + return true; + } + + return false; + case BotSpellTypes::PBAENuke: + if (IsPBAENukeSpell(spellid) && !IsStunSpell(spellid)) { + return true; + } + + return false; + case BotSpellTypes::AERains: + if (IsAERainNukeSpell(spellid) && !IsStunSpell(spellid)) { + return true; + } + return false; + case BotSpellTypes::AEStun: + case BotSpellTypes::Stun: + if (IsStunSpell(spellid)) { + return true; + } + + return false; + case BotSpellTypes::AENukes: + case BotSpellTypes::Nuke: + if (!IsStunSpell(spellid)) { + return true; + } + + return false; + default: + return true; + } + + return true; +} + +void Bot::SetCastedSpellType(uint16 spellType) { + _castedSpellType = spellType; +} + +void Bot::DoFaceCheckWithJitter(Mob* tar) { + if (!tar) { + return; + } + + if (IsMoving()) { + return; + } + + SetCombatJitter(); + if (!IsFacingMob(tar)) { + FaceTarget(tar); + return; + } + return; +} + +void Bot::DoFaceCheckNoJitter(Mob* tar) { + if (!tar) { + return; + } + + if (IsMoving()) { + return; + } + + if (!IsFacingMob(tar)) { + FaceTarget(tar); + return; + } + return; +} + +void Bot::RunToGoalWithJitter(glm::vec3 Goal) { + RunTo(Goal.x, Goal.y, Goal.z); + SetCombatJitter(); +} + +void Bot::SetCombatOutOfRangeJitter() { + SetCombatOutOfRangeJitterFlag(); + + if (RuleI(Bots, MaxJitterTimer) > 0) { + m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true); + } +} + +void Bot::SetCombatJitter() { + SetCombatJitterFlag(); + + if (RuleI(Bots, MaxJitterTimer) > 0) { + m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true); + } +} + +void Bot::DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stopMeleeLevel, float tar_distance, float melee_distance_min, float melee_distance, float melee_distance_max, bool behindMob) { + if (HasTargetReflection()) { + if (!tar->IsFeared() && !tar->IsStunned()) { + if (TryEvade(tar)) { + return; + } + } + + if (tar->IsRooted() && !taunting) { // Move non-taunters out of range - Above already checks if bot is targeted, otherwise they would stay + if (tar_distance <= melee_distance_max) { + if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 2), false, false, true)) { + RunToGoalWithJitter(Goal); + return; + } + } + } + + if (taunting && tar_distance < melee_distance_min) { // Back up any taunting bots that are too close + if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, taunting)) { + RunToGoalWithJitter(Goal); + return; + } + } + } + else { + if (!tar->IsFeared()) { + if (taunting) { // Taunting adjustments + Mob* mobTar = tar->GetTarget(); + if (!mobTar || mobTar == nullptr) { + DoFaceCheckNoJitter(tar); + return; + } + + if (RuleB(Bots, TauntingBotsFollowTopHate)) { // If enabled, taunting bots will stick to top hate + if ((DistanceSquared(m_Position, mobTar->GetPosition()) > pow(RuleR(Bots, DistanceTauntingBotsStickMainHate), 2))) { + Goal = mobTar->GetPosition(); + RunToGoalWithJitter(Goal); + return; + } + } + else { // Otherwise, stick to any other bots that are taunting + if (mobTar->IsBot() && mobTar->CastToBot()->taunting && (DistanceSquared(m_Position, mobTar->GetPosition()) > pow(RuleR(Bots, DistanceTauntingBotsStickMainHate), 2))) { + Goal = mobTar->GetPosition(); + RunToGoalWithJitter(Goal); + return; + } + } + } + else if (tar_distance < melee_distance_min || (GetBehindMob() && !behindMob) || !HasRequiredLoSForPositioning(tar)) { // Regular adjustment + if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), taunting)) { + RunToGoalWithJitter(Goal); + return; + } + //else { + // if (stopMeleeLevel || IsBotArcher()) { + // if (IsBotArcher()) { + // float minArcheryRange = RuleI(Combat, MinRangedAttackDist) * RuleI(Combat, MinRangedAttackDist); + // if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, minArcheryRange, melee_distance, false, taunting)) { + // RunToGoalWithJitter(Goal); + // return; + // } + // } + // else { + // if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_max + 1, melee_distance, false, taunting)) { + // RunToGoalWithJitter(Goal); + // return; + // } + // } + // } + // DoFaceCheckWithJitter(tar); + // return; + //} + } + } + } + + DoFaceCheckNoJitter(tar); + return; +} + +bool Bot::CheckDoubleRangedAttack() { + int32 chance = spellbonuses.DoubleRangedAttack + itembonuses.DoubleRangedAttack + aabonuses.DoubleRangedAttack; + if (chance && zone->random.Roll(chance)) + return true; + + return false; +} + +bool Bot::RequiresLoSForPositioning() { + if (GetLevel() < GetStopMeleeLevel()) { + return true; + } + else if (GetClass() == Class::Bard) { + return false; + } + else if (GetClass() == Class::Cleric) { //TODO bot rewrite - add check to see if spell requires los + return false; + } + + return true; +} + +bool Bot::HasRequiredLoSForPositioning(Mob* tar) { + if (!tar) { + return true; + } + + if (GetClass() == Class::Cleric) { //add check to see if spell requires los + return true; + } + else if (GetClass() == Class::Bard && GetLevel() >= GetStopMeleeLevel()) { + return true; + } + if (!DoLosChecks(this, tar)) { + return false; + } + + return true; +} + +bool Bot::IsInGroupOrRaid(bool announce) { + if (!GetOwner()) { + return false; + } + + Mob* c = GetOwner(); + + if ( + (!GetRaid() && !GetGroup()) || + (!c->GetRaid() && !c->GetGroup()) + ) { + return false; + } + + if ( + c->GetRaid() && + ( + !GetRaid() || + c->GetRaid() != GetRaid() || + GetRaid()->GetGroup(GetCleanName()) == RAID_GROUPLESS + ) + ) { + return false; + } + + if ( + c->GetGroup() && + ( + !GetGroup() || + c->GetGroup() != GetGroup() + ) + ) { + return false; + } + + + if (announce) { + c->Message( + Chat::Yellow, + fmt::format( + "{} says, 'I am not currently in your group or raid.", + GetCleanName() + ).c_str() + ); + } + + return true; +} + +bool Bot::HasValidAETarget(Bot* botCaster, uint16 spellid, uint16 spellType, Mob* tar) { + int spellRange = botCaster->GetActSpellRange(spellid, spells[spellid].range); + int spellAERange = botCaster->GetActSpellRange(spellid, spells[spellid].aoe_range); + int targetCount = 0; + + for (auto& close_mob : botCaster->m_close_mobs) { + Mob* m = close_mob.second; + + if (tar == m) { + continue; + } + + switch (spellType) { + case BotSpellTypes::AEDispel: + if (m->GetSpecialAbility(SpecialAbility::DispellImmunity)) { + continue; + } + + break; + case BotSpellTypes::AEFear: + if (m->GetSpecialAbility(SpecialAbility::FearImmunity)) { + continue; + } + + break; + case BotSpellTypes::AESnare: + if (m->GetSpecialAbility(SpecialAbility::SnareImmunity)) { + continue; + } + + break; + case BotSpellTypes::AESlow: + if (m->GetSpecialAbility(SpecialAbility::SlowImmunity)) { + continue; + } + + break; + default: + break; + } + + if (!m->IsNPC() || !m->CastToNPC()->IsOnHatelist(botCaster->GetOwner())) { + continue; + } + + if (SpellBreaksMez(spellid) && m->IsMezzed()) { + continue; + } + + if (IsPBAESpell(spellid)) { + if ( + spellAERange >= Distance(botCaster->GetPosition(), m->GetPosition()) && + botCaster->CastChecks(spellid, m, spellType, true, true) + ) { + ++targetCount; + } + } + else { + if (!tar || spellRange < Distance(botCaster->GetPosition(), tar->GetPosition()) || !DoLosChecks(this, m)) { + continue; + } + + if ( + spellAERange >= Distance(tar->GetPosition(), m->GetPosition()) && + botCaster->CastChecks(spellid, m, spellType, true, true) + ) { + ++targetCount; + } + } + } + + if (targetCount < botCaster->GetSpellTypeAEOrGroupTargetCount(spellType)) { + return false; + } + + SetHasLoS(true); + + return true; +} + +void Bot::CopySettings(Bot* to, uint8 settingType, uint16 spellType) { + switch (settingType) { + case BotSettingCategories::BaseSetting: + for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { + to->SetBotBaseSetting(i, GetBotBaseSetting(i)); + } + + break; + case BotSettingCategories::SpellHold: + if (spellType != UINT16_MAX) { + to->SetSpellHold(spellType, GetSpellHold(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetSpellHold(i, GetSpellHold(i)); + } + } + + break; + case BotSettingCategories::SpellDelay: + if (spellType != UINT16_MAX) { + to->SetSpellDelay(spellType, GetSpellDelay(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetSpellDelay(i, GetSpellDelay(i)); + } + } + + break; + case BotSettingCategories::SpellMinThreshold: + if (spellType != UINT16_MAX) { + to->SetSpellMinThreshold(spellType, GetSpellMinThreshold(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetSpellMinThreshold(i, GetSpellMinThreshold(i)); + } + } + + break; + case BotSettingCategories::SpellMaxThreshold: + if (spellType != UINT16_MAX) { + to->SetSpellMaxThreshold(spellType, GetSpellMaxThreshold(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetSpellMaxThreshold(i, GetSpellMaxThreshold(i)); + } + } + + break; + case BotSettingCategories::SpellTypeAggroCheck: + if (spellType != UINT16_MAX) { + to->SetSpellTypeAggroCheck(spellType, GetSpellTypeAggroCheck(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetSpellTypeAggroCheck(i, GetSpellTypeAggroCheck(i)); + } + } + + break; + case BotSettingCategories::SpellTypeMinManaPct: + if (spellType != UINT16_MAX) { + to->SetSpellTypeMinManaLimit(spellType, GetSpellTypeMinManaLimit(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetSpellTypeMinManaLimit(i, GetSpellTypeMinManaLimit(i)); + } + } + + break; + case BotSettingCategories::SpellTypeMaxManaPct: + if (spellType != UINT16_MAX) { + to->SetSpellTypeMaxManaLimit(spellType, GetSpellTypeMaxManaLimit(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetSpellTypeMaxManaLimit(i, GetSpellTypeMaxManaLimit(i)); + } + } + + break; + case BotSettingCategories::SpellTypeMinHPPct: + if (spellType != UINT16_MAX) { + to->SetSpellTypeMinHPLimit(spellType, GetSpellTypeMinHPLimit(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetSpellTypeMinHPLimit(i, GetSpellTypeMinHPLimit(i)); + } + } + + break; + case BotSettingCategories::SpellTypeMaxHPPct: + if (spellType != UINT16_MAX) { + to->SetSpellTypeMaxHPLimit(spellType, GetSpellTypeMaxHPLimit(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetSpellTypeMaxHPLimit(i, GetSpellTypeMaxHPLimit(i)); + } + } + + break; + case BotSettingCategories::SpellTypeIdlePriority: + if (spellType != UINT16_MAX) { + to->SetSpellTypePriority(spellType, BotPriorityCategories::Idle, GetSpellTypePriority(spellType, BotPriorityCategories::Idle)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetSpellTypePriority(i, BotPriorityCategories::Idle, GetSpellTypePriority(i, BotPriorityCategories::Idle)); + } + } + + break; + case BotSettingCategories::SpellTypeEngagedPriority: + if (spellType != UINT16_MAX) { + to->SetSpellTypePriority(spellType, BotPriorityCategories::Engaged, GetSpellTypePriority(spellType, BotPriorityCategories::Engaged)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetSpellTypePriority(i, BotPriorityCategories::Engaged, GetSpellTypePriority(i, BotPriorityCategories::Engaged)); + } + } + + break; + case BotSettingCategories::SpellTypePursuePriority: + if (spellType != UINT16_MAX) { + to->SetSpellTypePriority(spellType, BotPriorityCategories::Pursue, GetSpellTypePriority(spellType, BotPriorityCategories::Pursue)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetSpellTypePriority(i, BotPriorityCategories::Pursue, GetSpellTypePriority(i, BotPriorityCategories::Pursue)); + } + } + + break; + case BotSettingCategories::SpellTypeAEOrGroupTargetCount: + if (spellType != UINT16_MAX) { + to->SetSpellTypeAEOrGroupTargetCount(spellType, GetSpellTypeAEOrGroupTargetCount(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetSpellTypeAEOrGroupTargetCount(i, GetSpellTypeAEOrGroupTargetCount(i)); + } + } + + break; + } +} + +void Bot::CopyBotSpellSettings(Bot* to) +{ + to->ResetBotSpellSettings(); + to->bot_spell_settings.clear(); + + auto s = BotSpellSettingsRepository::GetWhere(content_db, fmt::format("bot_id = {}", GetBotID())); + if (s.empty()) { + return; + } + + auto* spell_list = content_db.GetBotSpells(to->GetBotSpellID()); + + for (const auto& e : s) { + BotSpellSetting b; + + b.priority = e.priority; + b.min_hp = e.min_hp; + b.max_hp = e.max_hp; + b.is_enabled = e.is_enabled; + + if (IsSpellInBotList(spell_list, e.spell_id)) { + for (auto& se : spell_list->entries) { + if (se.spellid == e.spell_id) { + if (EQ::ValueWithin(to->GetLevel(), se.minlevel, se.maxlevel) && se.spellid) { + to->AddBotSpellSetting(e.spell_id, &b); + } + } + } + } + } + + to->LoadBotSpellSettings(); + to->AI_AddBotSpells(to->GetBotSpellID()); + to->SetBotEnforceSpellSetting(GetBotEnforceSpellSetting()); +} + +void Bot::ResetBotSpellSettings() +{ + auto s = BotSpellSettingsRepository::GetWhere(content_db, fmt::format("bot_id = {}", GetBotID())); + if (s.empty()) { + return; + } + + for (const auto& e : s) { + DeleteBotSpellSetting(e.spell_id); + } + + LoadBotSpellSettings(); + AI_AddBotSpells(GetBotSpellID()); + SetBotEnforceSpellSetting(false); +} diff --git a/zone/bot.h b/zone/bot.h index 68b027f05..97d987e56 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -42,6 +42,9 @@ constexpr uint32 BOT_FOLLOW_DISTANCE_DEFAULT_MAX = 2500; // as DSq value (50 uni constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds +constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MIN = 1500; // 1.5 seconds +constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MAX = 3000; // 3 seconds + constexpr uint32 MAG_EPIC_1_0 = 28034; extern WorldServer worldserver; @@ -49,6 +52,13 @@ extern WorldServer worldserver; constexpr int BotAISpellRange = 100; // TODO: Write a method that calcs what the bot's spell range is based on spell, equipment, AA, whatever and replace this constexpr int NegativeItemReuse = -1; // Unlinked timer for items +constexpr uint8 SumWater = 1; +constexpr uint8 SumFire = 2; +constexpr uint8 SumAir = 3; +constexpr uint8 SumEarth = 4; +constexpr uint8 MonsterSum = 5; +constexpr uint8 SumMageMultiElement = 6; + // nHSND negative Healer/Slower/Nuker/Doter // pH positive Healer // pS positive Slower @@ -87,6 +97,58 @@ enum BotCastingChanceConditional : uint8 cntHSND = 16 }; +namespace BotSettingCategories { // Update GetBotSpellCategoryName as needed + constexpr uint8 BaseSetting = 0; + constexpr uint8 SpellHold = 1; + constexpr uint8 SpellDelay = 2; + constexpr uint8 SpellMinThreshold = 3; + constexpr uint8 SpellMaxThreshold = 4; + constexpr uint8 SpellTypeAggroCheck = 5; + constexpr uint8 SpellTypeMinManaPct = 6; + constexpr uint8 SpellTypeMaxManaPct = 7; + constexpr uint8 SpellTypeMinHPPct = 8; + constexpr uint8 SpellTypeMaxHPPct = 9; + constexpr uint8 SpellTypeIdlePriority = 10; + constexpr uint8 SpellTypeEngagedPriority = 11; + constexpr uint8 SpellTypePursuePriority = 12; + constexpr uint8 SpellTypeAEOrGroupTargetCount = 13; + constexpr uint8 SpellTypeRecastDelay = 14; + + constexpr uint16 START = BotSettingCategories::BaseSetting; + constexpr uint16 START_NO_BASE = BotSettingCategories::SpellHold; + constexpr uint16 START_CLIENT = BotSettingCategories::SpellHold; + constexpr uint16 END_CLIENT = BotSettingCategories::SpellMaxThreshold; + constexpr uint16 END = BotSettingCategories::SpellTypeAEOrGroupTargetCount; // Increment as needed +}; + +namespace BotPriorityCategories { // Update GetBotSpellCategoryName as needed + constexpr uint8 Idle = 0; + constexpr uint8 Engaged = 1; + constexpr uint8 Pursue = 2; + + constexpr uint16 START = BotPriorityCategories::Idle; + constexpr uint16 END = BotPriorityCategories::Pursue; // Increment as needed +}; + +namespace BotBaseSettings { + constexpr uint16 ExpansionBitmask = 0; + constexpr uint16 ShowHelm = 1; + constexpr uint16 FollowDistance = 2; + constexpr uint16 StopMeleeLevel = 3; + constexpr uint16 EnforceSpellSettings = 4; + constexpr uint16 RangedSetting = 5; + constexpr uint16 PetSetTypeSetting = 6; + constexpr uint16 BehindMob = 7; + constexpr uint16 CasterRange = 8; + constexpr uint16 IllusionBlock = 9; + constexpr uint16 MaxMeleeRange = 10; + constexpr uint16 MedInCombat = 11; + constexpr uint16 HPWhenToMed = 12; + constexpr uint16 ManaWhenToMed = 13; + + constexpr uint16 START = BotBaseSettings::ShowHelm; // Everything above this cannot be copied, changed or viewed by players + constexpr uint16 END = BotBaseSettings::ManaWhenToMed; // Increment as needed +}; class Bot : public NPC { friend class Mob; @@ -128,7 +190,7 @@ public: // Class Constructors Bot(NPCType *npcTypeData, Client* botOwner); - Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double totalPlayTime, uint32 lastZoneId, NPCType *npcTypeData, int32 expansion_bitmask); + Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double totalPlayTime, uint32 lastZoneId, NPCType *npcTypeData); //abstract virtual override function implementations requird by base abstract class bool Death(Mob* killer_mob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, KilledByTypes killed_by = KilledByTypes::Killed_NPC, bool is_buff_tic = false) override; @@ -199,8 +261,8 @@ public: void Camp(bool save_to_database = true); void SetTarget(Mob* mob) override; void Zone(); - bool IsArcheryRange(Mob* target); - void ChangeBotArcherWeapons(bool isArcher); + bool IsAtRange(Mob* target); + void ChangeBotRangedWeapons(bool isRanged); void Sit(); void Stand(); bool IsSitting() const override; @@ -228,7 +290,7 @@ public: uint8 GetNumberNeedingHealedInRaidGroup(uint8& need_healed, uint8 hpr, bool includePets, Raid* raid); bool GetNeedsCured(Mob *tar); bool GetNeedsHateRedux(Mob *tar); - bool HasOrMayGetAggro(); + bool HasOrMayGetAggro(bool SitAggro, uint32 spell_id = 0); void SetDefaultBotStance(); void SetSurname(std::string_view bot_surname); void SetTitle(std::string_view bot_title); @@ -327,18 +389,20 @@ public: void AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot); // AI Methods - bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes); + bool AICastSpell(Mob* tar, uint8 iChance, uint16 spellType); + bool AttemptAICastSpell(uint16 spellType); bool AI_EngagedCastCheck() override; bool AI_PursueCastCheck() override; bool AI_IdleCastCheck() override; bool AIHealRotation(Mob* tar, bool useFastHeals); bool GetPauseAI() const { return _pauseAI; } void SetPauseAI(bool pause_flag) { _pauseAI = pause_flag; } - uint8 GetStopMeleeLevel() const { return _stopMeleeLevel; } - void SetStopMeleeLevel(uint8 level); + bool IsCommandedSpell() const { return _commandedSpell; } + void SetCommandedSpell(bool value) { _commandedSpell = value; } + void SetGuardMode(); void SetHoldMode(); - uint32 GetBotCasterRange() const { return m_bot_caster_range; } + bool IsValidSpellRange(uint16 spell_id, Mob const* tar); // Bot AI Methods @@ -377,6 +441,97 @@ public: inline bool Attack(Mob* other, int Hand = EQ::invslot::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) override { return Mob::Attack(other, Hand, FromRiposte, IsStrikethrough, IsFromSpell, opts); } + void DoAttackRounds(Mob* target, int hand); + + std::vector GatherGroupSpellTargets(Mob* target = nullptr, bool noClients = false, bool noBots = false); + std::vector GatherSpellTargets(bool entireRaid = false, bool noClients = false, bool noBots = false, bool noPets = false); + + bool PrecastChecks(Mob* tar, uint16 spellType); + bool CastChecks(uint16 spellid, Mob* tar, uint16 spellType, bool doPrechecks = false, bool AECheck = false); + bool CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar); + bool BotHasEnoughMana(uint16 spell_id); + bool IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spellid); + bool DoResistCheck(Mob* target, uint16 spellid, int32 resist_limit); + bool DoResistCheckBySpellType(Mob* tar, uint16 spellid, uint16 spellType); + bool IsValidTargetType(uint16 spellid, int targetType, uint8 bodyType); + bool IsMobEngagedByAnyone(Mob* tar); + void SetBotSetting(uint8 settingType, uint16 botSetting, int settingValue); + void CopySettings(Bot* to, uint8 settingType, uint16 spellType = UINT16_MAX); + void CopyBotSpellSettings(Bot* to); + void ResetBotSpellSettings(); + int GetBotBaseSetting(uint16 botSetting); + int GetDefaultBotBaseSetting(uint16 botSetting); + void SetBotBaseSetting(uint16 botSetting, int settingValue); + void LoadDefaultBotSettings(); + void SetBotSpellRecastTimer(uint16 spellType, Mob* spelltar, bool preCast = false); + BotSpell GetSpellByHealType(uint16 spellType, Mob* tar); + + std::string GetBotSpellCategoryName(uint8 setting_type); + std::string GetBotSettingCategoryName(uint8 setting_type); + + int GetDefaultSetting(uint16 settingCategory, uint16 settingType); + uint16 GetDefaultSpellTypePriority(uint16 spellType, uint8 priorityType, uint8 botClass); + uint16 GetDefaultSpellTypeIdlePriority(uint16 spellType, uint8 botClass); + uint16 GetDefaultSpellTypeEngagedPriority(uint16 spellType, uint8 botClass); + uint16 GetDefaultSpellTypePursuePriority(uint16 spellType, uint8 botClass); + uint16 GetDefaultSpellTypeResistLimit(uint16 spellType); + bool GetDefaultSpellTypeAggroCheck(uint16 spellType); + uint8 GetDefaultSpellTypeMinManaLimit(uint16 spellType); + uint8 GetDefaultSpellTypeMaxManaLimit(uint16 spellType); + uint8 GetDefaultSpellTypeMinHPLimit(uint16 spellType); + uint8 GetDefaultSpellTypeMaxHPLimit(uint16 spellType); + uint16 GetDefaultSpellTypeAEOrGroupTargetCount(uint16 spellType); + + int GetSetting(uint16 settingCategory, uint16 settingType); + uint16 GetSpellTypePriority(uint16 spellType, uint8 priorityType); + void SetSpellTypePriority(uint16 spellType, uint8 priorityType, uint16 priority); + inline uint16 GetSpellTypeResistLimit(uint16 spellType) const { return _spellSettings[spellType].resistLimit; } + void SetSpellTypeResistLimit(uint16 spellType, uint16 resistLimit); + inline bool GetSpellTypeAggroCheck(uint16 spellType) const { return _spellSettings[spellType].aggroCheck; } + void SetSpellTypeAggroCheck(uint16 spellType, bool AggroCheck); + inline uint8 GetSpellTypeMinManaLimit(uint16 spellType) const { return _spellSettings[spellType].minManaPct; } + inline uint8 GetSpellTypeMaxManaLimit(uint16 spellType) const { return _spellSettings[spellType].maxManaPct; } + void SetSpellTypeMinManaLimit(uint16 spellType, uint8 manaLimit); + void SetSpellTypeMaxManaLimit(uint16 spellType, uint8 manaLimit); + inline uint8 GetSpellTypeMinHPLimit(uint16 spellType) const { return _spellSettings[spellType].minHPPct; } + inline uint8 GetSpellTypeMaxHPLimit(uint16 spellType) const { return _spellSettings[spellType].maxHPPct; } + void SetSpellTypeMinHPLimit(uint16 spellType, uint8 hpLimit); + void SetSpellTypeMaxHPLimit(uint16 spellType, uint8 hpLimit); + inline uint16 GetSpellTypeAEOrGroupTargetCount(uint16 spellType) const { return _spellSettings[spellType].AEOrGroupTargetCount; } + void SetSpellTypeAEOrGroupTargetCount(uint16 spellType, uint16 targetCount); + + bool GetShowHelm() const { return _showHelm; } + void SetShowHelm(bool showHelm) { _showHelm = showHelm; } + bool GetBehindMob() const { return _behindMobStatus; } + void SetBehindMob(bool value) { _behindMobStatus = value; } + bool GetMaxMeleeRange() const { return _maxMeleeRangeStatus; } + void SetMaxMeleeRange(bool value) { _maxMeleeRangeStatus = value; } + uint8 GetStopMeleeLevel() const { return _stopMeleeLevel; } + void SetStopMeleeLevel(uint8 level) { _stopMeleeLevel = level; } + uint32 GetBotCasterRange() const { return _casterRange; } + void SetBotCasterRange(uint32 casterRange) { _casterRange = casterRange; } + bool GetMedInCombat() const { return _medInCombat; } + void SetMedInCombat(bool value) { _medInCombat = value; } + uint8 GetHPWhenToMed() const { return _HPWhenToMed; } + void SetHPWhenToMed(uint8 value) { _HPWhenToMed = value; } + uint8 GetManaWhenToMed() const { return _ManaWhenToMed; } + void SetManaWhenToMed(uint8 value) { _ManaWhenToMed = value; } + void SetHasLoS(bool hasLoS) { _hasLoS = hasLoS; } + bool HasLoS() const { return _hasLoS; } + + bool IsInGroupOrRaid(bool announce = false); + void SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, std::string arg2, bool helpPrompt = false); + + std::list GetSpellTypesPrioritized(uint8 priorityType); + uint16 GetSpellListSpellType(uint16 spellType); + bool IsValidSpellTypeBySpellID(uint16 spellType, uint16 spellid); + inline uint16 GetCastedSpellType() const { return _castedSpellType; } + void SetCastedSpellType(uint16 spellType); + + bool HasValidAETarget(Bot* botCaster, uint16 spellid, uint16 spellType, Mob* tar); + + void CheckBotSpells(); + [[nodiscard]] int GetMaxBuffSlots() const final { return EQ::spells::LONG_BUFFS; } [[nodiscard]] int GetMaxSongSlots() const final { return EQ::spells::SHORT_BUFFS; } @@ -423,33 +578,36 @@ public: ProcessBotGroupAdd(Group* group, Raid* raid, Client* client = nullptr, bool new_raid = false, bool initial = false); - static std::list GetBotSpellsForSpellEffect(Bot* botCaster, int spellEffect); - static std::list GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, int spellEffect, SpellTargetType targetType); - static std::list GetBotSpellsBySpellType(Bot* botCaster, uint32 spellType); - static std::list GetPrioritizedBotSpellsBySpellType(Bot* botCaster, uint32 spellType); + static std::list GetBotSpellsForSpellEffect(Bot* botCaster, uint16 spellType, int spellEffect); + static std::list GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, uint16 spellType, int spellEffect, SpellTargetType targetType); + static std::list GetBotSpellsBySpellType(Bot* botCaster, uint16 spellType); + static std::list GetPrioritizedBotSpellsBySpellType(Bot* botCaster, uint16 spellType, Mob* tar, bool AE = false); - static BotSpell GetFirstBotSpellBySpellType(Bot* botCaster, uint32 spellType); - static BotSpell GetBestBotSpellForFastHeal(Bot* botCaster); - static BotSpell GetBestBotSpellForHealOverTime(Bot* botCaster); - static BotSpell GetBestBotSpellForPercentageHeal(Bot* botCaster); - static BotSpell GetBestBotSpellForRegularSingleTargetHeal(Bot* botCaster); - static BotSpell GetFirstBotSpellForSingleTargetHeal(Bot* botCaster); - static BotSpell GetBestBotSpellForGroupHealOverTime(Bot* botCaster); - static BotSpell GetBestBotSpellForGroupCompleteHeal(Bot* botCaster); - static BotSpell GetBestBotSpellForGroupHeal(Bot* botCaster); - static BotSpell GetBestBotSpellForMagicBasedSlow(Bot* botCaster); - static BotSpell GetBestBotSpellForDiseaseBasedSlow(Bot* botCaster); + static BotSpell GetFirstBotSpellBySpellType(Bot* botCaster, uint16 spellType); + static BotSpell GetBestBotSpellForVeryFastHeal(Bot* botCaster, Mob* tar, uint16 spellType = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForFastHeal(Bot* botCaster, Mob* tar, uint16 spellType = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForHealOverTime(Bot* botCaster, Mob* tar, uint16 spellType = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForPercentageHeal(Bot* botCaster, Mob* tar, uint16 spellType = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForRegularSingleTargetHeal(Bot* botCaster, Mob* tar, uint16 spellType = BotSpellTypes::RegularHeal); + static BotSpell GetFirstBotSpellForSingleTargetHeal(Bot* botCaster, Mob* tar, uint16 spellType = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForGroupHealOverTime(Bot* botCaster, Mob* tar, uint16 spellType = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForGroupCompleteHeal(Bot* botCaster, Mob* tar, uint16 spellType = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForGroupHeal(Bot* botCaster, Mob* tar, uint16 spellType = BotSpellTypes::RegularHeal); - static Mob* GetFirstIncomingMobToMez(Bot* botCaster, BotSpell botSpell); - static BotSpell GetBestBotSpellForMez(Bot* botCaster); - static BotSpell GetBestBotMagicianPetSpell(Bot* botCaster); + static Mob* GetFirstIncomingMobToMez(Bot* botCaster, int16 spellid, uint16 spellType, bool AE = false); + bool IsValidMezTarget(Mob* owner, Mob* npc, uint16 spellid); + static BotSpell GetBestBotSpellForMez(Bot* botCaster, uint16 spellType = BotSpellTypes::Mez); + static BotSpell GetBestBotMagicianPetSpell(Bot* botCaster, uint16 spellType = BotSpellTypes::Mez); static std::string GetBotMagicianPetType(Bot* botCaster); - static BotSpell GetBestBotSpellForNukeByTargetType(Bot* botCaster, SpellTargetType targetType); - static BotSpell GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType targetType); - static BotSpell GetBestBotWizardNukeSpellByTargetResists(Bot* botCaster, Mob* target); + static BotSpell GetBestBotSpellForNukeByTargetType(Bot* botCaster, SpellTargetType targetType, uint16 spellType, bool AE = false, Mob* tar = nullptr); + static BotSpell GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType targetType, uint16 spellType, bool AE = false, Mob* tar = nullptr); + static BotSpell GetBestBotWizardNukeSpellByTargetResists(Bot* botCaster, Mob* target, uint16 spellType); static BotSpell GetDebuffBotSpell(Bot* botCaster, Mob* target); - static BotSpell GetBestBotSpellForCure(Bot* botCaster, Mob* target); + static BotSpell GetBestBotSpellForCure(Bot* botCaster, Mob* target, uint16 spellType); static BotSpell GetBestBotSpellForResistDebuff(Bot* botCaster, Mob* target); + static BotSpell GetBestBotSpellForNukeByBodyType(Bot* botCaster, uint8 bodyType, uint16 spellType, bool AE = false, Mob* tar = nullptr); + static BotSpell GetBestBotSpellForRez(Bot* botCaster, Mob* target, uint16 spellType); + static BotSpell GetBestBotSpellForCharm(Bot* botCaster, Mob* target, uint16 spellType); static NPCType *CreateDefaultNPCTypeStructForBot( const std::string& botName, @@ -472,12 +630,11 @@ public: uint32 GetBotOwnerCharacterID() const { return _botOwnerCharacterID; } uint32 GetBotSpellID() const { return npc_spells_id; } Mob* GetBotOwner() { return this->_botOwner; } - uint32 GetBotArcheryRange(); + uint32 GetBotRangedValue(); EQ::ItemInstance* GetBotItem(uint16 slot_id); bool GetSpawnStatus() { return _spawnStatus; } uint8 GetPetChooserID() { return _petChooserID; } - bool IsPetChooser() { return _petChooser; } - bool IsBotArcher() { return m_bot_archery_setting; } + bool IsBotRanged() { return _botRangedSetting; } bool IsBotCharmer() { return _botCharmer; } bool IsBot() const override { return true; } bool IsOfClientBot() const override { return true; } @@ -485,8 +642,7 @@ public: bool GetRangerAutoWeaponSelect() { return _rangerAutoWeaponSelect; } uint8 GetBotStance() { return _botStance; } - uint8 GetChanceToCastBySpellType(uint32 spellType); - bool GetBotEnforceSpellSetting() { return m_enforce_spell_settings; } + uint8 GetChanceToCastBySpellType(uint16 spellType); float GetBotCasterMaxRange(float melee_distance_max); bool IsGroupHealer() const { return m_CastingRoles.GroupHealer; } bool IsGroupSlower() const { return m_CastingRoles.GroupSlower; } @@ -527,8 +683,6 @@ public: std::shared_ptr* MemberOfHealRotation() { return &m_member_of_heal_rotation; } - bool GetAltOutOfCombatBehavior() const { return _altoutofcombatbehavior;} - bool GetShowHelm() const { return _showhelm; } inline int32 GetSTR() const override { return STR; } inline int32 GetSTA() const override { return STA; } inline int32 GetDEX() const override { return DEX; } @@ -600,13 +754,11 @@ public: void SetBotSpellID(uint32 newSpellID); void SetSpawnStatus(bool spawnStatus) { _spawnStatus = spawnStatus; } void SetPetChooserID(uint8 id) { _petChooserID = id; } - void SetBotArcherySetting(bool bot_archer_setting, bool save = false); + void SetBotRangedSetting(bool botRangedSetting) { _botRangedSetting = botRangedSetting; } void SetBotCharmer(bool c) { _botCharmer = c; } - void SetPetChooser(bool p) { _petChooser = p; } void SetBotOwner(Mob* botOwner) { this->_botOwner = botOwner; } void SetRangerAutoWeaponSelect(bool enable) { GetClass() == Class::Ranger ? _rangerAutoWeaponSelect = enable : _rangerAutoWeaponSelect = false; } void SetBotStance(uint8 stance_id) { _botStance = Stance::IsValid(stance_id) ? stance_id : Stance::Passive; } - void SetBotCasterRange(uint32 bot_caster_range) { m_bot_caster_range = bot_caster_range; } uint32 GetSpellRecastTimer(uint16 spell_id = 0); bool CheckSpellRecastTimer(uint16 spell_id = 0); uint32 GetSpellRecastRemainingTime(uint16 spell_id = 0); @@ -624,8 +776,6 @@ public: void ClearSpellRecastTimer(uint16 spell_id = 0); uint32 GetItemReuseRemainingTime(uint32 item_id = 0); void ClearExpiredTimers(); - void SetAltOutOfCombatBehavior(bool behavior_flag) { _altoutofcombatbehavior = behavior_flag;} - void SetShowHelm(bool showhelm) { _showhelm = showhelm; } void SetBeardColor(uint8 value) { beardcolor = value; } void SetBeard(uint8 value) { beard = value; } void SetEyeColor1(uint8 value) { eyecolor1 = value; } @@ -639,7 +789,7 @@ public: bool DyeArmor(int16 slot_id, uint32 rgb, bool all_flag = false, bool save_flag = true); int GetExpansionBitmask(); - void SetExpansionBitmask(int expansion_bitmask, bool save = true); + void SetExpansionBitmask(int expansionBitmask); void ListBotSpells(uint8 min_level); @@ -651,15 +801,12 @@ public: void ListBotSpellSettings(); void LoadBotSpellSettings(); bool UpdateBotSpellSetting(uint16 spell_id, BotSpellSetting* bs); - void SetBotEnforceSpellSetting(bool enforcespellsettings, bool save = false); - bool GetBotEnforceSpellSetting() const { return m_enforce_spell_settings; } + void SetBotEnforceSpellSetting(bool enforceSpellSettings); + bool GetBotEnforceSpellSetting() { return _enforceSpellSettings; } // Class Destructors ~Bot() override; - // Publicized protected functions - void BotRangedAttack(Mob* other); - // Publicized private functions static NPCType *FillNPCTypeStruct( uint32 botSpellsID, @@ -750,24 +897,11 @@ public: static uint8 spell_casting_chances[SPELL_TYPE_COUNT][Class::PLAYER_CLASS_COUNT][Stance::AEBurn][cntHSND]; - bool BotCastMez(Mob* tar, uint8 botLevel, bool checked_los, BotSpell& botSpell, Raid* raid); - bool BotCastHeal(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, Raid* raid); - bool BotCastRoot(Mob* tar, uint8 botLevel, uint32 iSpellTypes, BotSpell& botSpell, const bool& checked_los); - bool BotCastBuff(Mob* tar, uint8 botLevel, uint8 botClass); - bool BotCastEscape(Mob*& tar, uint8 botClass, BotSpell& botSpell, uint32 iSpellTypes); - bool BotCastNuke(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los); - bool BotCastDispel(Mob* tar, BotSpell& botSpell, uint32 iSpellTypes, const bool& checked_los); - bool BotCastPet(Mob* tar, uint8 botClass, BotSpell& botSpell); - bool BotCastCombatBuff(Mob* tar, uint8 botLevel, uint8 botClass); - bool BotCastLifetap(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes); - bool BotCastSnare(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes); - bool BotCastDOT(Mob* tar, uint8 botLevel, const BotSpell& botSpell, const bool& checked_los); - bool BotCastSlow(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los, Raid* raid); - bool BotCastDebuff(Mob* tar, uint8 botLevel, BotSpell& botSpell, bool checked_los); - bool BotCastCure(Mob* tar, uint8 botClass, BotSpell& botSpell, Raid* raid); - bool BotCastHateReduction(Mob* tar, uint8 botLevel, const BotSpell& botSpell); - bool BotCastCombatSong(Mob* tar, uint8 botLevel); - bool BotCastSong(Mob* tar, uint8 botLevel); + bool BotCastMez(Mob* tar, uint8 botClass, BotSpell& botSpell, uint16 spellType); + bool BotCastHeal(Mob* tar, uint8 botClass, BotSpell& botSpell, uint16 spellType); + bool BotCastNuke(Mob* tar, uint8 botClass, BotSpell& botSpell, uint16 spellType); + bool BotCastPet(Mob* tar, uint8 botClass, BotSpell& botSpell, uint16 spellType); + bool BotCastCure(Mob* tar, uint8 botClass, BotSpell& botSpell, uint16 spellType); bool CheckIfIncapacitated(); bool IsAIProcessValid(const Client* bot_owner, const Group* bot_group, const Raid* raid); @@ -796,39 +930,65 @@ public: const Mob* tar, const EQ::ItemInstance* const& p_item, const EQ::ItemInstance* const& s_item, - bool behind_mob, + bool behindMob, bool backstab_weapon, float& melee_distance_max, - float& melee_distance - ) const; + float& melee_distance, + float& melee_distance_min, + uint8 stopMeleeLevel + ); // Combat Checks void SetBerserkState(); bool CheckIfCasting(float fm_distance); void HealRotationChecks(); - void CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item); + void CheckCombatRange( + Mob* tar, + float tar_distance, + bool& atCombatRange, + bool& behindMob, + const EQ::ItemInstance*& p_item, + const EQ::ItemInstance*& s_item, + float& melee_distance_min, + float& melee_distance_max, + float& melee_distance, + uint8 stopMeleeLevel + ); + bool GetCombatJitterFlag() { return m_combat_jitter_flag; } + void SetCombatJitterFlag(bool flag = true) { m_combat_jitter_flag = flag; } + bool GetCombatOutOfRangeJitterFlag() { return m_combat_out_of_range_jitter_flag; } + void SetCombatOutOfRangeJitterFlag(bool flag = true) { m_combat_out_of_range_jitter_flag = flag; } + void SetCombatJitter(); + void SetCombatOutOfRangeJitter(); + void DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stopMeleeLevel, float tar_distance, float melee_distance_min, float melee_distance, float melee_distance_max, bool behindMob); + void DoFaceCheckWithJitter(Mob* tar); + void DoFaceCheckNoJitter(Mob* tar); + void RunToGoalWithJitter(glm::vec3 Goal); + bool RequiresLoSForPositioning(); + bool HasRequiredLoSForPositioning(Mob* tar); // Try Combat Methods bool TryEvade(Mob* tar); bool TryFacingTarget(Mob* tar); bool TryRangedAttack(Mob* tar); - bool TryClassAttacks(Mob* tar); - bool TryPrimaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* p_item); - bool TrySecondaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* s_item); bool TryPursueTarget(float leash_distance, glm::vec3& Goal); bool TryMeditate(); bool TryAutoDefend(Client* bot_owner, float leash_distance); bool TryIdleChecks(float fm_distance); bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal); bool TryBardMovementCasts(); - void SetRangerCombatWeapon(bool atArcheryRange); + void BotRangedAttack(Mob* other, bool CanDoubleAttack = false); + bool CheckDoubleRangedAttack(); // Public "Refactor" Methods static bool CheckSpawnConditions(Client* c); + inline bool CommandedDoSpellCast(int32 i, Mob* tar, int32 mana_cost) { return AIDoSpellCast(i, tar, mana_cost); } + protected: void BotMeditate(bool isSitting); bool CheckBotDoubleAttack(bool Triple = false); + bool CheckTripleAttack(); void PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* client); bool AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = nullptr) override; @@ -849,9 +1009,7 @@ private: uint32 _botOwnerCharacterID; bool _spawnStatus; Mob* _botOwner; - bool m_bot_archery_setting; bool _botCharmer; - bool _petChooser; uint8 _petChooserID; bool berserk; EQ::InventoryProfile m_inv; @@ -876,9 +1034,15 @@ private: int32 max_end; int32 end_regen; - Timer m_evade_timer; // can be moved to pTimers at some point + Timer m_rogue_evade_timer; // Rogue evade timer + Timer m_monk_evade_timer; // Monk evade FD timer Timer m_auto_defend_timer; Timer auto_save_timer; + + Timer m_combat_jitter_timer; + bool m_combat_jitter_flag; + bool m_combat_out_of_range_jitter_flag; + bool m_dirtyautohaters; bool m_guard_flag; bool m_hold_flag; @@ -888,7 +1052,7 @@ private: bool m_pulling_flag; bool m_returning_flag; bool is_using_item_click; - uint32 m_bot_caster_range; + BotCastingRoles m_CastingRoles; std::map bot_spell_settings; @@ -896,12 +1060,22 @@ private: std::shared_ptr m_member_of_heal_rotation; InspectMessage_Struct _botInspectMessage; - bool _altoutofcombatbehavior; - bool _showhelm; bool _pauseAI; + + int _expansionBitmask; + bool _enforceSpellSettings; + bool _showHelm; + bool _botRangedSetting; uint8 _stopMeleeLevel; - int m_expansion_bitmask; - bool m_enforce_spell_settings; + uint32 _casterRange; + bool _behindMobStatus; + bool _maxMeleeRangeStatus; + bool _medInCombat; + uint8 _HPWhenToMed; + uint8 _ManaWhenToMed; + uint16 _castedSpellType; + bool _hasLoS; + bool _commandedSpell; // Private "base stats" Members int32 _baseMR; diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index b15531dd7..3f6dfef5b 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1249,8 +1249,8 @@ int bot_command_init(void) bot_command_add("actionable", "Lists actionable command arguments and use descriptions", AccountStatus::Player, bot_command_actionable) || bot_command_add("aggressive", "Orders a bot to use a aggressive discipline", AccountStatus::Player, bot_command_aggressive) || bot_command_add("applypoison", "Applies cursor-held poison to a rogue bot's weapon", AccountStatus::Player, bot_command_apply_poison) || - bot_command_add("applypotion", "Applies cursor-held potion to a bot's effects", AccountStatus::Player, bot_command_apply_potion) || bot_command_add("attack", "Orders bots to attack a designated target", AccountStatus::Player, bot_command_attack) || + bot_command_add("behindmob", "Toggles whether or not your bot tries to stay behind a mob", AccountStatus::Player, bot_command_behind_mob) || bot_command_add("bindaffinity", "Orders a bot to attempt an affinity binding", AccountStatus::Player, bot_command_bind_affinity) || bot_command_add("bot", "Lists the available bot management [subcommands]", AccountStatus::Player, bot_command_bot) || bot_command_add("botappearance", "Lists the available bot appearance [subcommands]", AccountStatus::Player, bot_command_appearance) || @@ -1270,8 +1270,8 @@ int bot_command_init(void) bot_command_add("botheritage", "Changes the Drakkin heritage of a bot", AccountStatus::Player, bot_command_heritage) || bot_command_add("botinspectmessage", "Changes the inspect message of a bot", AccountStatus::Player, bot_command_inspect_message) || bot_command_add("botlist", "Lists the bots that you own", AccountStatus::Player, bot_command_list_bots) || - bot_command_add("botoutofcombat", "Toggles your bot between standard and out-of-combat spell/skill use - if any specialized behaviors exist", AccountStatus::Player, bot_command_out_of_combat) || bot_command_add("botreport", "Orders a bot to report its readiness", AccountStatus::Player, bot_command_report) || + bot_command_add("botsettings", "Lists settings related to spell types and bot combat", AccountStatus::Player, bot_command_bot_settings) || bot_command_add("botspawn", "Spawns a created bot", AccountStatus::Player, bot_command_spawn) || bot_command_add("botstance", "Changes the stance of a bot", AccountStatus::Player, bot_command_stance) || bot_command_add("botstopmeleelevel", "Sets the level a caster or spell-casting fighter bot will stop melee combat", AccountStatus::Player, bot_command_stop_melee_level) || @@ -1279,16 +1279,20 @@ int bot_command_init(void) bot_command_add("botsummon", "Summons bot(s) to your location", AccountStatus::Player, bot_command_summon) || bot_command_add("botsurname", "Sets a bots surname (last name)", AccountStatus::Player, bot_command_surname) || bot_command_add("bottattoo", "Changes the Drakkin tattoo of a bot", AccountStatus::Player, bot_command_tattoo) || - bot_command_add("bottogglearcher", "Toggles a archer bot between melee and ranged weapon use", AccountStatus::Player, bot_command_toggle_archer) || bot_command_add("bottogglehelm", "Toggles the helm visibility of a bot between shown and hidden", AccountStatus::Player, bot_command_toggle_helm) || + bot_command_add("bottoggleranged", "Toggles a ranged bot between melee and ranged weapon use", AccountStatus::Player, bot_command_toggle_ranged) || bot_command_add("bottitle", "Sets a bots title", AccountStatus::Player, bot_command_title) || bot_command_add("botupdate", "Updates a bot to reflect any level changes that you have experienced", AccountStatus::Player, bot_command_update) || bot_command_add("botwoad", "Changes the Barbarian woad of a bot", AccountStatus::Player, bot_command_woad) || + bot_command_add("cast", "Tells the first found specified bot to cast the given spell type", AccountStatus::Player, bot_command_cast) || bot_command_add("casterrange", "Controls the range casters will try to stay away from a mob (if too far, they will skip spells that are out-of-range)", AccountStatus::Player, bot_command_caster_range) || bot_command_add("charm", "Attempts to have a bot charm your target", AccountStatus::Player, bot_command_charm) || bot_command_add("circle", "Orders a Druid bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_circle) || + bot_command_add("classracelist", "Lists the classes and races and their appropriate IDs", AccountStatus::Player, bot_command_class_race_list) || bot_command_add("clickitem", "Orders your targeted bot to click the item in the provided inventory slot.", AccountStatus::Player, bot_command_click_item) || + bot_command_add("copysettings", "Copies settings from one bot to another", AccountStatus::Player, bot_command_copy_settings) || bot_command_add("cure", "Orders a bot to remove any ailments", AccountStatus::Player, bot_command_cure) || + bot_command_add("defaultsettings", "Restores a bot back to default settings", AccountStatus::Player, bot_command_default_settings) || bot_command_add("defensive", "Orders a bot to use a defensive discipline", AccountStatus::Player, bot_command_defensive) || bot_command_add("depart", "Orders a bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_depart) || bot_command_add("enforcespellsettings", "Toggles your Bot to cast only spells in their spell settings list.", AccountStatus::Player, bot_command_enforce_spell_list) || @@ -1320,6 +1324,7 @@ int bot_command_init(void) bot_command_add("help", "List available commands and their description - specify partial command as argument to search", AccountStatus::Player, bot_command_help) || bot_command_add("hold", "Prevents a bot from attacking until released", AccountStatus::Player, bot_command_hold) || bot_command_add("identify", "Orders a bot to cast an item identification spell", AccountStatus::Player, bot_command_identify) || + bot_command_add("illusionblock", "Control whether or not illusion effects will land on the bot if casted by another player or bot", AccountStatus::Player, bot_command_illusion_block) || bot_command_add("inventory", "Lists the available bot inventory [subcommands]", AccountStatus::Player, bot_command_inventory) || bot_command_add("inventorygive", "Gives the item on your cursor to a bot", AccountStatus::Player, bot_command_inventory_give) || bot_command_add("inventorylist", "Lists all items in a bot's inventory", AccountStatus::Player, bot_command_inventory_list) || @@ -1329,6 +1334,7 @@ int bot_command_init(void) bot_command_add("itemuse", "Elicits a report from spawned bots that can use the item on your cursor (option 'empty' yields only empty slots)", AccountStatus::Player, bot_command_item_use) || bot_command_add("levitation", "Orders a bot to cast a levitation spell", AccountStatus::Player, bot_command_levitation) || bot_command_add("lull", "Orders a bot to cast a pacification spell", AccountStatus::Player, bot_command_lull) || + bot_command_add("maxmeleerange", "Toggles whether your bot is at max melee range or not. This will disable all special abilities, including taunt.", AccountStatus::Player, bot_command_max_melee_range) || bot_command_add("mesmerize", "Orders a bot to cast a mesmerization spell", AccountStatus::Player, bot_command_mesmerize) || bot_command_add("movementspeed", "Orders a bot to cast a movement speed enhancement spell", AccountStatus::Player, bot_command_movement_speed) || bot_command_add("owneroption", "Sets options available to bot owners", AccountStatus::Player, bot_command_owner_option) || @@ -1346,7 +1352,23 @@ int bot_command_init(void) bot_command_add("resurrect", "Orders a bot to resurrect a player's (players') corpse(s)", AccountStatus::Player, bot_command_resurrect) || bot_command_add("rune", "Orders a bot to cast a rune of protection", AccountStatus::Player, bot_command_rune) || bot_command_add("sendhome", "Orders a bot to open a magical doorway home", AccountStatus::Player, bot_command_send_home) || - bot_command_add("size", "Orders a bot to change a player's size", AccountStatus::Player, bot_command_size) || + bot_command_add("sithppercent", "HP threshold for a bot to start sitting in combat if allowed", AccountStatus::Player, bot_command_sit_hp_percent) || + bot_command_add("sitincombat", "Toggles whether or a not a bot will attempt to med or sit to heal in combat", AccountStatus::Player, bot_command_sit_in_combat) || + bot_command_add("sitmanapercent", "Mana threshold for a bot to start sitting in combat if allowed", AccountStatus::Player, bot_command_sit_mana_percent) || + bot_command_add("size", "Orders a bot to change a player's size", AccountStatus::Player, bot_command_size) || + bot_command_add("spellaggrochecks", "Toggles whether or not bots will cast a spell type if they think it will get them aggro", AccountStatus::Player, bot_command_spell_aggro_checks) || + bot_command_add("spellengagedpriority", "Controls the order of casts by spell type when engaged in combat", AccountStatus::Player, bot_command_spell_engaged_priority) || + bot_command_add("spelldelays", "Controls the delay between casts for a specific spell type", AccountStatus::Player, bot_command_spell_delays) || + bot_command_add("spellholds", "Controls whether a bot holds the specified spell type or not", AccountStatus::Player, bot_command_spell_holds) || + bot_command_add("spellidlepriority", "Controls the order of casts by spell type when out of combat", AccountStatus::Player, bot_command_spell_idle_priority) || + bot_command_add("spellmaxhppct", "Controls at what HP percent a bot will stop casting different spell types", AccountStatus::Player, bot_command_spell_max_hp_pct) || + bot_command_add("spellmaxmanapct", "Controls at what mana percent a bot will stop casting different spell types", AccountStatus::Player, bot_command_spell_max_mana_pct) || + bot_command_add("spellmaxthresholds", "Controls the minimum target HP threshold for a spell to be cast for a specific type", AccountStatus::Player, bot_command_spell_max_thresholds) || + bot_command_add("spellminhppct", "Controls at what HP percent a bot will start casting different spell types", AccountStatus::Player, bot_command_spell_min_hp_pct) || + bot_command_add("spellminmanapct", "Controls at what mana percent a bot will start casting different spell types", AccountStatus::Player, bot_command_spell_min_mana_pct) || + bot_command_add("spellminthresholds", "Controls the maximum target HP threshold for a spell to be cast for a specific type", AccountStatus::Player, bot_command_spell_min_thresholds) || + bot_command_add("spellpursuepriority", "Controls the order of casts by spell type when pursuing in combat", AccountStatus::Player, bot_command_spell_pursue_priority) || + bot_command_add("spelltargetcount", "Sets the required target amount for group/AE spells by spell type", AccountStatus::Player, bot_command_spell_target_count) || bot_command_add("spellinfo", "Opens a dialogue window with spell info", AccountStatus::Player, bot_spell_info_dialogue_window) || bot_command_add("spells", "Lists all Spells learned by the Bot.", AccountStatus::Player, bot_command_spell_list) || bot_command_add("spellsettings", "Lists a bot's spell setting entries", AccountStatus::Player, bot_command_spell_settings_list) || @@ -1360,7 +1382,7 @@ int bot_command_init(void) bot_command_add("timer", "Checks or clears timers of the chosen type.", AccountStatus::GMMgmt, bot_command_timer) || bot_command_add("track", "Orders a capable bot to track enemies", AccountStatus::Player, bot_command_track) || bot_command_add("viewcombos", "Views bot race class combinations", AccountStatus::Player, bot_command_view_combos) || - bot_command_add("waterbreathing", "Orders a bot to cast a water breathing spell", AccountStatus::Player, bot_command_water_breathing) + bot_command_add("waterbreathing", "Orders a bot to cast a water breathing spell", AccountStatus::Player, bot_command_water_breathing) ) { bot_command_deinit(); return -1; @@ -1613,7 +1635,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas bot_owner->Message( Chat::White, fmt::format( - "'{}' is an invalid name. You may only use characters 'A-Z' or 'a-z'. Mixed case {} allowed.", + "'{}' is an invalid name. You may only use characters 'A-Z' or 'a-z' and it must be between 4 and 15 characters. Mixed case {} allowed.", bot_name, RuleB(Bots, AllowCamelCaseNames) ? "is" : "is not" ).c_str() ); @@ -1625,7 +1647,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas bot_owner->Message( Chat::White, fmt::format( - "Failed to query name availability for '{}'.", + "'{}' is already in use or an invalid name.", bot_name ).c_str() ); @@ -1806,40 +1828,6 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas return bot_id; } -void helper_bot_out_of_combat(Client *bot_owner, Bot *my_bot) -{ - if (!bot_owner || !my_bot) - return; - - switch (my_bot->GetClass()) { - case Class::Warrior: - case Class::Cleric: - case Class::Paladin: - case Class::Ranger: - case Class::ShadowKnight: - case Class::Druid: - case Class::Monk: - bot_owner->Message(Chat::White, "%s has no out-of-combat behavior defined", my_bot->GetCleanName()); - break; - case Class::Bard: - bot_owner->Message(Chat::White, "%s will %s use out-of-combat behavior for bard songs", my_bot->GetCleanName(), ((my_bot->GetAltOutOfCombatBehavior()) ? ("now") : ("no longer"))); - break; - case Class::Rogue: - case Class::Shaman: - case Class::Necromancer: - case Class::Wizard: - case Class::Magician: - case Class::Enchanter: - case Class::Beastlord: - case Class::Berserker: - bot_owner->Message(Chat::White, "%s has no out-of-combat behavior defined", my_bot->GetCleanName()); - break; - default: - break; - bot_owner->Message(Chat::White, "Undefined bot class for %s", my_bot->GetCleanName()); - } -} - int helper_bot_follow_option_chain(Client* bot_owner) { if (!bot_owner) { @@ -2085,8 +2073,11 @@ void helper_send_available_subcommands(Client *bot_owner, const char* command_si bot_owner->Message( Chat::White, fmt::format( - "^{} - {}", - subcommand_iter, + "{} - {}", + Saylink::Silent( + fmt::format("^{} help", subcommand_iter), + fmt::format("^{}", subcommand_iter) + ), find_iter != bot_command_list.end() ? find_iter->second->desc : "No Description" ).c_str() ); @@ -2126,18 +2117,152 @@ bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::Sp return false; } +void Bot::SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, std::string arg2, bool helpPrompt) { + if (helpPrompt) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {}, {}, {} for a list of spell types by ID", + Saylink::Silent( + fmt::format("{} listid 0-19", arg0) + ), + Saylink::Silent( + fmt::format("{} listid 20-39", arg0) + ), + Saylink::Silent( + fmt::format("{} listid 40+", arg0) + ) + ).c_str() + ); + + c->Message( + Chat::Yellow, + fmt::format( + "Use {}, {}, {} for a list of spell types by short name", + Saylink::Silent( + fmt::format("{} listname 0-19", arg0) + ), + Saylink::Silent( + fmt::format("{} listname 20-39", arg0) + ), + Saylink::Silent( + fmt::format("{} listname 40+", arg0) + ) + ).c_str() + ); + + return; + } + + uint8 minCount = 0; + uint8 maxCount = 0; + + if (BotSpellTypes::END <= 19) { + minCount = BotSpellTypes::START; + maxCount = BotSpellTypes::END; + } + else if (!arg2.compare("0-19")) { + minCount = BotSpellTypes::START; + maxCount = 19; + } + else if (!arg2.compare("20-39")) { + minCount = std::min(static_cast(20), static_cast(BotSpellTypes::END)); + maxCount = std::min(static_cast(39), static_cast(BotSpellTypes::END)); + } + else if (!arg2.compare("40+")) { + minCount = std::min(static_cast(40), static_cast(BotSpellTypes::END)); + maxCount = BotSpellTypes::END; + } + else { + c->Message(Chat::Yellow, "You must choose a valid range option"); + + return; + } + + const std::string& indian_red = "indian_red"; + const std::string& gold = "gold"; + const std::string& slate_blue = "slate_blue"; + const std::string& forest_green = "forest_green"; + const std::string& goldenrod = "goldenrod"; + + std::string fillerLine = "-----------"; + std::string spellTypeField = "Spell Type"; + std::string pluralS = "s"; + std::string idField = "ID"; + std::string shortnameField = "Short Name"; + + std::string popup_text = DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(goldenrod, spellTypeField) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + (!arg1.compare("listid") ? DialogueWindow::ColorMessage(goldenrod, idField) : DialogueWindow::ColorMessage(goldenrod, shortnameField)) + ) + ) + ); + + popup_text += DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(gold, fillerLine) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(gold, fillerLine) + ) + ) + ); + + for (int i = minCount; i <= maxCount; ++i) { + popup_text += DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}{}", + DialogueWindow::ColorMessage(forest_green, c->GetSpellTypeNameByID(i)), + DialogueWindow::ColorMessage(forest_green, pluralS) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + (!arg1.compare("listid") ? DialogueWindow::ColorMessage(slate_blue, std::to_string(i)) : DialogueWindow::ColorMessage(slate_blue, c->GetSpellTypeShortNameByID(i))) + ) + ) + ); + } + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient("Spell Types", popup_text.c_str()); +} + + #include "bot_commands/actionable.cpp" #include "bot_commands/aggressive.cpp" #include "bot_commands/appearance.cpp" #include "bot_commands/apply_poison.cpp" #include "bot_commands/apply_potion.cpp" #include "bot_commands/attack.cpp" +#include "bot_commands/behind_mob.cpp" #include "bot_commands/bind_affinity.cpp" #include "bot_commands/bot.cpp" +#include "bot_commands/bot_settings.cpp" +#include "bot_commands/cast.cpp" #include "bot_commands/caster_range.cpp" #include "bot_commands/charm.cpp" +#include "bot_commands/class_race_list.cpp" #include "bot_commands/click_item.cpp" +#include "bot_commands/copy_settings.cpp" #include "bot_commands/cure.cpp" +#include "bot_commands/default_settings.cpp" #include "bot_commands/defensive.cpp" #include "bot_commands/depart.cpp" #include "bot_commands/escape.cpp" @@ -2148,11 +2273,13 @@ bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::Sp #include "bot_commands/help.cpp" #include "bot_commands/hold.cpp" #include "bot_commands/identify.cpp" +#include "bot_commands/illusion_block.cpp" #include "bot_commands/inventory.cpp" #include "bot_commands/invisibility.cpp" #include "bot_commands/item_use.cpp" #include "bot_commands/levitation.cpp" #include "bot_commands/lull.cpp" +#include "bot_commands/max_melee_range.cpp" #include "bot_commands/mesmerize.cpp" #include "bot_commands/movement_speed.cpp" #include "bot_commands/name.cpp" @@ -2167,8 +2294,24 @@ bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::Sp #include "bot_commands/resurrect.cpp" #include "bot_commands/rune.cpp" #include "bot_commands/send_home.cpp" +#include "bot_commands/sit_hp_percent.cpp" +#include "bot_commands/sit_in_combat.cpp" +#include "bot_commands/sit_mana_percent.cpp" #include "bot_commands/size.cpp" #include "bot_commands/spell.cpp" +#include "bot_commands/spell_aggro_checks.cpp" +#include "bot_commands/spell_delays.cpp" +#include "bot_commands/spell_engaged_priority.cpp" +#include "bot_commands/spell_holds.cpp" +#include "bot_commands/spell_idle_priority.cpp" +#include "bot_commands/spell_max_hp_pct.cpp" +#include "bot_commands/spell_max_mana_pct.cpp" +#include "bot_commands/spell_max_thresholds.cpp" +#include "bot_commands/spell_min_hp_pct.cpp" +#include "bot_commands/spell_min_mana_pct.cpp" +#include "bot_commands/spell_min_thresholds.cpp" +#include "bot_commands/spell_pursue_priority.cpp" +#include "bot_commands/spell_target_count.cpp" #include "bot_commands/summon.cpp" #include "bot_commands/summon_corpse.cpp" #include "bot_commands/suspend.cpp" diff --git a/zone/bot_command.h b/zone/bot_command.h index 74dfa1401..53cb2776b 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -666,6 +666,27 @@ namespace MyBots UniquifySBL(sbl); } } + + static void PopulateSBL_ByAtMMR(Client* bot_owner, std::list& sbl, bool clear_list = true) { + if (clear_list) { + sbl.clear(); + } + + if (!bot_owner) { + return; + } + + auto selectable_bot_list = entity_list.GetBotsByBotOwnerCharacterID(bot_owner->CharacterID()); + for (auto bot_iter : selectable_bot_list) { + if (!bot_iter->GetMaxMeleeRange()) { + continue; + } + sbl.push_back(bot_iter); + } + if (!clear_list) { + UniquifySBL(sbl); + } + } }; namespace ActionableTarget @@ -875,6 +896,7 @@ namespace ActionableBots ABT_HealRotation, ABT_HealRotationMembers, ABT_HealRotationTargets, + ABT_MMR, ABT_Class, ABT_Race, ABT_Spawned, @@ -892,6 +914,7 @@ namespace ActionableBots ABM_HealRotation = (1 << (ABT_HealRotation - 1)), ABM_HealRotationMembers = (1 << (ABT_HealRotationMembers - 1)), ABM_HealRotationTargets = (1 << (ABT_HealRotationTargets - 1)), + ABM_MMR = (1 << (ABT_MMR - 1)), ABM_Class = (1 << (ABT_Class - 1)), ABM_Race = (1 << (ABT_Race - 1)), ABM_Spawned = (1 << (ABT_Spawned - 1)), @@ -899,8 +922,8 @@ namespace ActionableBots ABM_Spawned_All = (3 << (ABT_Spawned - 1)), ABM_NoFilter = ~0, // grouped values - ABM_Type1 = (ABM_Target | ABM_ByName | ABM_OwnerGroup | ABM_OwnerRaid | ABM_TargetGroup | ABM_NamesGroup | ABM_HealRotationTargets | ABM_Spawned | ABM_Class | ABM_Race), - ABM_Type2 = (ABM_ByName | ABM_OwnerGroup | ABM_OwnerRaid | ABM_NamesGroup | ABM_HealRotation | ABM_Spawned | ABM_Class | ABM_Race) + ABM_Type1 = (ABM_Target | ABM_ByName | ABM_OwnerGroup | ABM_OwnerRaid | ABM_TargetGroup | ABM_NamesGroup | ABM_HealRotationTargets | ABM_Spawned | ABM_MMR | ABM_Class | ABM_Race), + ABM_Type2 = (ABM_ByName | ABM_OwnerGroup | ABM_OwnerRaid | ABM_NamesGroup | ABM_HealRotation | ABM_Spawned | ABM_MMR | ABM_Class | ABM_Race) }; // Populates 'sbl' @@ -941,6 +964,9 @@ namespace ActionableBots else if (!ab_type_arg.compare("healrotationtargets")) { ab_type = ABT_HealRotationTargets; } + else if (!ab_type_arg.compare("mmr")) { + ab_type = ABT_MMR; + } else if (!ab_type_arg.compare("byclass")) { ab_type = ABT_Class; } @@ -1004,6 +1030,11 @@ namespace ActionableBots MyBots::PopulateSBL_ByHealRotationTargets(bot_owner, sbl, name, clear_list); } break; + case ABT_MMR: + if (ab_mask & ABM_MMR) { + MyBots::PopulateSBL_ByAtMMR(bot_owner, sbl, clear_list); + } + break; case ABT_Class: if (ab_mask & ABM_Class) { MyBots::PopulateSBL_BySpawnedBotsClass(bot_owner, sbl, classrace, clear_list); @@ -1256,9 +1287,9 @@ namespace ActionableBots sbl.remove_if([min_level](const Bot* l) { return (l->GetLevel() < min_level); }); } - static void Filter_ByArcher(Client* bot_owner, std::list& sbl) { + static void Filter_ByRanged(Client* bot_owner, std::list& sbl) { sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); - sbl.remove_if([bot_owner](Bot* l) { return (!l->IsBotArcher()); }); + sbl.remove_if([bot_owner](Bot* l) { return (!l->IsBotRanged()); }); } static void Filter_ByHighestSkill(Client* bot_owner, std::list& sbl, EQ::skills::SkillType skill_type, float& skill_value) { @@ -1637,12 +1668,18 @@ 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_behind_mob(Client* c, const Seperator* sep); void bot_command_bind_affinity(Client *c, const Seperator *sep); void bot_command_bot(Client *c, const Seperator *sep); +void bot_command_bot_settings(Client* c, const Seperator* sep); +void bot_command_cast(Client* c, const Seperator* sep); void bot_command_caster_range(Client* c, const Seperator* sep); void bot_command_charm(Client *c, const Seperator *sep); +void bot_command_class_race_list(Client* c, const Seperator* sep); void bot_command_click_item(Client* c, const Seperator* sep); +void bot_command_copy_settings(Client* c, const Seperator* sep); void bot_command_cure(Client *c, const Seperator *sep); +void bot_command_default_settings(Client* c, const Seperator* sep); void bot_command_defensive(Client *c, const Seperator *sep); void bot_command_depart(Client *c, const Seperator *sep); void bot_command_escape(Client *c, const Seperator *sep); @@ -1653,11 +1690,13 @@ void bot_command_heal_rotation(Client *c, const Seperator *sep); void bot_command_help(Client *c, const Seperator *sep); void bot_command_hold(Client *c, const Seperator *sep); void bot_command_identify(Client *c, const Seperator *sep); +void bot_command_illusion_block(Client* c, const Seperator* sep); void bot_command_inventory(Client *c, const Seperator *sep); void bot_command_invisibility(Client *c, const Seperator *sep); void bot_command_item_use(Client *c, const Seperator *sep); void bot_command_levitation(Client *c, const Seperator *sep); void bot_command_lull(Client *c, const Seperator *sep); +void bot_command_max_melee_range(Client* c, const Seperator* sep); void bot_command_mesmerize(Client *c, const Seperator *sep); void bot_command_movement_speed(Client *c, const Seperator *sep); void bot_command_owner_option(Client *c, const Seperator *sep); @@ -1671,7 +1710,23 @@ void bot_command_resistance(Client *c, const Seperator *sep); void bot_command_resurrect(Client *c, const Seperator *sep); void bot_command_rune(Client *c, const Seperator *sep); void bot_command_send_home(Client *c, const Seperator *sep); +void bot_command_sit_hp_percent(Client* c, const Seperator* sep); +void bot_command_sit_in_combat(Client* c, const Seperator* sep); +void bot_command_sit_mana_percent(Client* c, const Seperator* sep); void bot_command_size(Client *c, const Seperator *sep); +void bot_command_spell_aggro_checks(Client* c, const Seperator* sep); +void bot_command_spell_delays(Client* c, const Seperator* sep); +void bot_command_spell_engaged_priority(Client* c, const Seperator* sep); +void bot_command_spell_holds(Client* c, const Seperator* sep); +void bot_command_spell_idle_priority(Client* c, const Seperator* sep); +void bot_command_spell_max_hp_pct(Client* c, const Seperator* sep); +void bot_command_spell_max_mana_pct(Client* c, const Seperator* sep); +void bot_command_spell_max_thresholds(Client* c, const Seperator* sep); +void bot_command_spell_min_hp_pct(Client* c, const Seperator* sep); +void bot_command_spell_min_mana_pct(Client* c, const Seperator* sep); +void bot_command_spell_min_thresholds(Client* c, const Seperator* sep); +void bot_command_spell_pursue_priority(Client* c, const Seperator* sep); +void bot_command_spell_target_count(Client* c, const Seperator* sep); void bot_command_spell_list(Client* c, const Seperator *sep); void bot_command_spell_settings_add(Client* c, const Seperator *sep); void bot_command_spell_settings_delete(Client* c, const Seperator *sep); @@ -1706,7 +1761,6 @@ void bot_command_hairstyle(Client *c, const Seperator *sep); void bot_command_heritage(Client *c, const Seperator *sep); void bot_command_inspect_message(Client *c, const Seperator *sep); void bot_command_list_bots(Client *c, const Seperator *sep); -void bot_command_out_of_combat(Client *c, const Seperator *sep); void bot_command_report(Client *c, const Seperator *sep); void bot_command_spawn(Client *c, const Seperator *sep); void bot_command_stance(Client *c, const Seperator *sep); @@ -1716,8 +1770,8 @@ void bot_command_summon(Client *c, const Seperator *sep); void bot_command_surname(Client *c, const Seperator *sep); void bot_command_tattoo(Client *c, const Seperator *sep); void bot_command_title(Client *c, const Seperator *sep); -void bot_command_toggle_archer(Client *c, const Seperator *sep); void bot_command_toggle_helm(Client *c, const Seperator *sep); +void bot_command_toggle_ranged(Client* c, const Seperator* sep); void bot_command_update(Client *c, const Seperator *sep); void bot_command_woad(Client *c, const Seperator *sep); @@ -1757,7 +1811,6 @@ bool helper_bot_appearance_fail(Client *bot_owner, Bot *my_bot, BCEnum::AFType f void helper_bot_appearance_form_final(Client *bot_owner, Bot *my_bot); 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); int helper_bot_follow_option_chain(Client *bot_owner); 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); diff --git a/zone/bot_commands/actionable.cpp b/zone/bot_commands/actionable.cpp index 84965b57d..73ec9ef6c 100644 --- a/zone/bot_commands/actionable.cpp +++ b/zone/bot_commands/actionable.cpp @@ -3,22 +3,61 @@ void bot_command_actionable(Client* c, const Seperator* sep) { if (helper_command_alias_fail(c, "bot_command_actionable", sep->arg[0], "actionable")) { + c->Message(Chat::White, "note: Lists actionable command arguments and use descriptions"); return; } - c->Message(Chat::White, "Actionable command arguments:"); - c->Message(Chat::White, "target - selects target as single bot .. use ^command [target] or imply by empty actionable argument"); - c->Message(Chat::White, "byname [name] - selects single bot by name"); - c->Message(Chat::White, "ownergroup - selects all bots in the owner's group"); - c->Message(Chat::White, "ownerraid - selects all bots in the owner's raid"); - c->Message(Chat::White, "targetgroup - selects all bots in target's group"); - c->Message(Chat::White, "namesgroup [name] - selects all bots in name's group"); - c->Message(Chat::White, "healrotation [name] - selects all member and target bots of a heal rotation where name is a member"); - c->Message(Chat::White, "healrotationmembers [name] - selects all member bots of a heal rotation where name is a member"); - c->Message(Chat::White, "healrotationtargets [name] - selects all target bots of a heal rotation where name is a member"); - c->Message(Chat::White, "byclass - selects all bots of the chosen class"); - c->Message(Chat::White, "byrace - selects all bots of the chosen rsce"); - c->Message(Chat::White, "spawned - selects all spawned bots"); - c->Message(Chat::White, "all - selects all spawned bots .. argument use indicates en masse database updating"); - c->Message(Chat::White, "You may only select your bots as actionable"); + std::vector description = + { + "Lists actionable command arguments and use descriptions." + }; + + std::vector notes = + { + "[target] - uses the command on the target. Some commands will default to target if no actionable is selected.", + "[byname] [name] - selects a bot by name their name.", + "[ownergroup] - selects all bots in the owner's group.", + "[ownerraid] - selects all bots in the owner's raid.", + "[targetgroup] - selects all bots in the target's group.", + "[namesgroup] [name] - selects all bots in [name]'s group.", + "[healrotation] [name] - selects all member and target bots of a heal rotation where [name] is a member.", + "[healrotationmembers] [name] - selects all member bots of a heal rotation where [name] is a member.", + "[healrotationtargets] [name] - selects all target bots of a heal rotation where [name] is a member.", + "[mmr] - selects all bots that are currently at max melee range.", + "[byclass] - selects all bots of the chosen class.", + "[byrace] - selects all bots of the chosen race.", + "[spawned] - selects all spawned bots.", + "[all] - selects all spawned bots.", + "
", + "You may only select your bots as actionable" + }; + + std::vector example_format = { }; + std::vector examples_one = { }; + std::vector examples_two = { }; + std::vector examples_three = { }; + + std::vector actionables = { }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; } diff --git a/zone/bot_commands/aggressive.cpp b/zone/bot_commands/aggressive.cpp index 6a3881d98..b40bf7123 100644 --- a/zone/bot_commands/aggressive.cpp +++ b/zone/bot_commands/aggressive.cpp @@ -7,32 +7,26 @@ void bot_command_aggressive(Client* c, const Seperator* sep) helper_command_alias_fail(c, "bot_command_aggressive", sep->arg[0], "aggressive")) { return; } + if (helper_is_help_or_usage(sep->arg[1])) { - c->Message( - Chat::White, - "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", - sep->arg[0] - ); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "note: Orders a bot to use a aggressive discipline"); helper_send_usage_required_bots(c, BCEnum::SpT_Stance); return; } - const int ab_mask = ActionableBots::ABM_Type1; - std::string class_race_arg = sep->arg[1]; - bool class_race_check = false; + const int ab_mask = ActionableBots::ABM_Type1; + int ab_arg = 1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { class_race_check = true; } std::list sbl; - if (ActionableBots::PopulateSBL( - c, - sep->arg[1], - sbl, - ab_mask, - !class_race_check ? sep->arg[2] : nullptr, - class_race_check ? atoi(sep->arg[2]) : 0 - ) == ActionableBots::ABT_None) { + + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } diff --git a/zone/bot_commands/appearance.cpp b/zone/bot_commands/appearance.cpp index fcaec025c..c6aed9e08 100644 --- a/zone/bot_commands/appearance.cpp +++ b/zone/bot_commands/appearance.cpp @@ -158,7 +158,7 @@ void bot_command_dye_armor(Client *c, const Seperator *sep) c->Message( Chat::White, fmt::format( - "Usage: {} [Material Slot] [Red: 0-255] [Green: 0-255] [Blue: 0-255] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", + "Usage: {} [Material Slot] [Red: 0-255] [Green: 0-255] [Blue: 0-255] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0] ).c_str() ); diff --git a/zone/bot_commands/apply_poison.cpp b/zone/bot_commands/apply_poison.cpp index cfffff2cb..444e370ed 100644 --- a/zone/bot_commands/apply_poison.cpp +++ b/zone/bot_commands/apply_poison.cpp @@ -11,6 +11,7 @@ void bot_command_apply_poison(Client* c, const Seperator* sep) if (helper_is_help_or_usage(sep->arg[1])) { c->Message(Chat::White, "usage: %s", sep->arg[0]); + c->Message(Chat::White, "note: Applies cursor-held poison to a rogue bot's weapon"); return; } diff --git a/zone/bot_commands/attack.cpp b/zone/bot_commands/attack.cpp index 60cad8cd8..8cce85db1 100644 --- a/zone/bot_commands/attack.cpp +++ b/zone/bot_commands/attack.cpp @@ -5,14 +5,17 @@ void bot_command_attack(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_command_attack", sep->arg[0], "attack")) { return; } + if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | default: spawned] ([actionable_name])", sep->arg[0]); + c->Message(Chat::White, "usage: %s [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | mmr | byclass | byrace | default: spawned] ([actionable_name])", sep->arg[0]); + c->Message(Chat::White, "note: Orders bots to attack a designated target"); return; } - const int ab_mask = ActionableBots::ABM_Type2; + const int ab_mask = ActionableBots::ABM_Type2; Mob* target_mob = ActionableTarget::AsSingle_ByAttackable(c); + if (!target_mob) { c->Message(Chat::White, "You must an enemy to use this command"); @@ -26,11 +29,13 @@ void bot_command_attack(Client *c, const Seperator *sep) std::string class_race_arg(sep->arg[1]); bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { class_race_check = true; } std::list sbl; + if (ActionableBots::PopulateSBL(c, ab_arg.c_str(), sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == ActionableBots::ABT_None) { return; } diff --git a/zone/bot_commands/behind_mob.cpp b/zone/bot_commands/behind_mob.cpp new file mode 100644 index 000000000..5761c4c31 --- /dev/null +++ b/zone/bot_commands/behind_mob.cpp @@ -0,0 +1,173 @@ +#include "../bot_command.h" + +void bot_command_behind_mob(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_behind_mob", sep->arg[0], "behindmob")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Toggles whether or not bots will stay behind the mob during combat." + }; + + std::vector notes = { }; + + std::vector example_format = + { + fmt::format( + "{} [value] [actionable]" + , sep->arg[0] + ) + }; + + std::vector examples_one = + { + "To set Monks to stay behind the mob:", + fmt::format( + "{} 1 byclass 7", + sep->arg[0] + ) + }; + std::vector examples_two = { }; + std::vector examples_three = + { + "To check the behind mob status for all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + int ab_arg = 1; + bool current_check = false; + uint32 typeValue = 0; + + if (sep->IsNumber(1)) { + typeValue = atoi(sep->arg[1]); + ++ab_arg; + if (typeValue < 0 || typeValue > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} stay behind mobs.'", + my_bot->GetCleanName(), + my_bot->GetBehindMob() ? "will" : "will not" + ).c_str() + ); + } + else { + my_bot->SetBehindMob(typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} stay behind mobs.'", + first_found->GetCleanName(), + first_found->GetBehindMob() ? "will now" : "will no longer" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots {} stay behind mobs.", + success_count, + typeValue ? "will now" : "will no longer" + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/bind_affinity.cpp b/zone/bot_commands/bind_affinity.cpp index e6d01fe4c..a59c5947a 100644 --- a/zone/bot_commands/bind_affinity.cpp +++ b/zone/bot_commands/bind_affinity.cpp @@ -7,6 +7,7 @@ void bot_command_bind_affinity(Client *c, const Seperator *sep) return; if (helper_is_help_or_usage(sep->arg[1])) { c->Message(Chat::White, "usage: () %s", sep->arg[0]); + c->Message(Chat::White, "note: Orders a bot to attempt an affinity binding", sep->arg[0]); helper_send_usage_required_bots(c, BCEnum::SpT_BindAffinity); return; } diff --git a/zone/bot_commands/bot.cpp b/zone/bot_commands/bot.cpp index 5d2bfe18e..7da91504d 100644 --- a/zone/bot_commands/bot.cpp +++ b/zone/bot_commands/bot.cpp @@ -18,7 +18,7 @@ void bot_command_bot(Client *c, const Seperator *sep) subcommand_list.push_back("botstance"); subcommand_list.push_back("botstopmeleelevel"); subcommand_list.push_back("botsummon"); - subcommand_list.push_back("bottogglearcher"); + subcommand_list.push_back("bottoggleranged"); subcommand_list.push_back("bottogglehelm"); subcommand_list.push_back("botupdate"); @@ -33,7 +33,7 @@ void bot_command_camp(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_command_camp", sep->arg[0], "botcamp")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } const int ab_mask = ActionableBots::ABM_Type1; @@ -452,8 +452,8 @@ void bot_command_follow_distance(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_command_follow_distance", sep->arg[0], "botfollowdistance")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [set] [distance] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); - c->Message(Chat::White, "usage: %s [clear] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s [set [1 to %i]] [distance] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0], BOT_FOLLOW_DISTANCE_DEFAULT_MAX); + c->Message(Chat::White, "usage: %s [clear] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } const int ab_mask = ActionableBots::ABM_NoFilter; @@ -464,7 +464,7 @@ void bot_command_follow_distance(Client *c, const Seperator *sep) if (!strcasecmp(sep->arg[1], "set")) { if (!sep->IsNumber(2)) { - c->Message(Chat::White, "A numeric [distance] is required to use this command"); + c->Message(Chat::White, "A numeric [distance] is required to use this command. [1 to %i]", BOT_FOLLOW_DISTANCE_DEFAULT_MAX); return; } @@ -492,22 +492,15 @@ void bot_command_follow_distance(Client *c, const Seperator *sep) continue; bot_iter->SetFollowDistance(bfd); - if (ab_type != ActionableBots::ABT_All && !database.botdb.SaveFollowDistance(bot_iter->GetBotID(), bfd)) { - return; - } ++bot_count; } if (ab_type == ActionableBots::ABT_All) { - if (!database.botdb.SaveAllFollowDistances(c->CharacterID(), bfd)) { - return; - } - - c->Message(Chat::White, "%s all of your bot follow distances", set_flag ? "Set" : "Cleared"); + c->Message(Chat::White, "%s all of your bot follow distances to %i", set_flag ? "Set" : "Cleared", bfd); } else { - c->Message(Chat::White, "%s %i of your spawned bot follow distances", (set_flag ? "Set" : "Cleared"), bot_count); + c->Message(Chat::White, "%s %i of your spawned bot follow distances to %i", (set_flag ? "Set" : "Cleared"), bot_count, bfd); } } @@ -516,7 +509,7 @@ void bot_command_inspect_message(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_command_inspect_message", sep->arg[0], "botinspectmessage")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [set | clear] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s [set | clear] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); c->Message(Chat::White, "Notes:"); if (c->ClientVersion() >= EQ::versions::ClientVersion::SoF) { c->Message(Chat::White, "- Self-inspect and type your bot's inspect message"); @@ -764,75 +757,31 @@ void bot_command_list_bots(Client *c, const Seperator *sep) } } -void bot_command_out_of_combat(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_command_out_of_combat", sep->arg[0], "botoutofcombat")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); - return; - } - const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); - - std::string arg1 = sep->arg[1]; - - bool behavior_state = false; - bool toggle_behavior = true; - int ab_arg = 1; - if (!arg1.compare("on")) { - behavior_state = true; - toggle_behavior = false; - ab_arg = 2; - } - else if (!arg1.compare("off")) { - toggle_behavior = false; - ab_arg = 2; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]) == ActionableBots::ABT_None) - return; - - for (auto bot_iter : sbl) { - if (!bot_iter) - continue; - - if (toggle_behavior) - bot_iter->SetAltOutOfCombatBehavior(!bot_iter->GetAltOutOfCombatBehavior()); - else - bot_iter->SetAltOutOfCombatBehavior(behavior_state); - - helper_bot_out_of_combat(c, bot_iter); - } -} - void bot_command_report(Client *c, const Seperator *sep) { if (helper_command_alias_fail(c, "bot_command_report", sep->arg[0], "botreport")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } - const int ab_mask = ActionableBots::ABM_NoFilter; + + const int ab_mask = ActionableBots::ABM_Type1; - std::string ab_type_arg = sep->arg[1]; - if (ab_type_arg.empty()) { - auto t = c->GetTarget(); - if (t && t->IsClient()) { - if (t->CastToClient() == c) { - ab_type_arg = "ownergroup"; - } else { - ab_type_arg = "targetgroup"; - } - } else { - ab_type_arg = "spawned"; - } + std::string arg1 = sep->arg[1]; + int ab_arg = 1; + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; } std::list sbl; - if (ActionableBots::PopulateSBL(c, ab_type_arg, sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; + } for (auto bot_iter : sbl) { if (!bot_iter) @@ -1062,65 +1011,74 @@ void bot_command_spawn(Client *c, const Seperator *sep) void bot_command_stance(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_command_stance", sep->arg[0], "botstance")) + if (helper_command_alias_fail(c, "bot_command_stance", sep->arg[0], "botstance")) { return; + } + if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [current | value: 1-9] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s [current | value: 1-9] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); c->Message( Chat::White, fmt::format( - "Value: {} ({}), {} ({}), {} ({}), {} ({}), {} ({}), {} ({}), {} ({}), {} ({}), {} ({})", + "Value: {} ({}), {} ({}), {} ({})", Stance::Passive, Stance::GetName(Stance::Passive), Stance::Balanced, Stance::GetName(Stance::Balanced), - Stance::Efficient, - Stance::GetName(Stance::Efficient), - Stance::Reactive, - Stance::GetName(Stance::Reactive), Stance::Aggressive, - Stance::GetName(Stance::Aggressive), - Stance::Assist, - Stance::GetName(Stance::Assist), - Stance::Burn, - Stance::GetName(Stance::Burn), - Stance::Efficient2, - Stance::GetName(Stance::Efficient2), - Stance::AEBurn, - Stance::GetName(Stance::AEBurn) + Stance::GetName(Stance::Aggressive) ).c_str() ); return; } - int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); - bool current_flag = false; - uint8 bst = Stance::Unknown; + const int ab_mask = ActionableBots::ABM_Type1; - if (!strcasecmp(sep->arg[1], "current")) - current_flag = true; - else if (sep->IsNumber(1)) { - bst = static_cast(Strings::ToUnsignedInt(sep->arg[1])); - if (!Stance::IsValid(bst)) { - bst = Stance::Unknown; + std::string arg1 = sep->arg[1]; + int ab_arg = 1; + bool current_check = false; + uint32 value = 0; + + if (sep->IsNumber(1)) { + ++ab_arg; + value = atoi(sep->arg[1]); + if (value < 0 || value > 300) { + c->Message(Chat::White, "You must enter a value within the range of 0 - 300."); + return; } } - - if (!current_flag && bst == Stance::Unknown) { - c->Message(Chat::White, "A [current] argument or valid numeric [value] is required to use this command"); + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message(Chat::White, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); return; } + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[2], sbl, ab_mask, sep->arg[3]) == ActionableBots::ABT_None) + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; + } + + if (!current_check && (value == Stance::Unknown || (value != Stance::Passive && value != Stance::Balanced && value != Stance::Aggressive))) { + c->Message(Chat::White, "A [current] argument or valid numeric [value] is required to use this command"); + return; + } for (auto bot_iter : sbl) { if (!bot_iter) continue; - if (!current_flag) { - bot_iter->SetBotStance(bst); + if (!current_check) { + bot_iter->SetBotStance(value); bot_iter->Save(); } @@ -1135,50 +1093,138 @@ void bot_command_stance(Client *c, const Seperator *sep) } } -void bot_command_stop_melee_level(Client *c, const Seperator *sep) +void bot_command_stop_melee_level(Client* c, const Seperator* sep) { - if (helper_command_alias_fail(c, "bot_command_stop_melee_level", sep->arg[0], "botstopmeleelevel")) + if (helper_command_alias_fail(c, "bot_command_stop_melee_level", sep->arg[0], "botstopmeleelevel")) { return; + } + if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [current | reset | sync | value: 0-255]", sep->arg[0]); + c->Message(Chat::White, "usage: %s [current | reset | sync | value: 0-255] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); c->Message(Chat::White, "note: Only caster or hybrid class bots may be modified"); c->Message(Chat::White, "note: Use [reset] to set stop melee level to server rule"); c->Message(Chat::White, "note: Use [sync] to set stop melee level to current bot level"); return; } - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - if (!IsCasterClass(my_bot->GetClass()) && !IsHybridClass(my_bot->GetClass())) { - c->Message(Chat::White, "You must a caster or hybrid class bot to use this command"); - return; - } + const int ab_mask = ActionableBots::ABM_Type1; + std::string arg1 = sep->arg[1]; + int ab_arg = 1; uint8 sml = RuleI(Bots, CasterStopMeleeLevel); + bool sync_sml = false; + bool reset_sml = false; + bool current_check = false; if (sep->IsNumber(1)) { + ab_arg = 2; sml = Strings::ToInt(sep->arg[1]); + if (sml <= 0 || sml > 255) { + c->Message(Chat::White, "You must provide a value between 0-255."); + return; + } } else if (!strcasecmp(sep->arg[1], "sync")) { - sml = my_bot->GetLevel(); + ab_arg = 2; + sync_sml = true; } - else if (!strcasecmp(sep->arg[1], "current")) { - c->Message(Chat::White, "My current melee stop level is %u", my_bot->GetStopMeleeLevel()); + else if (!arg1.compare("current")) { + ab_arg = 2; + current_check = true; + } + else if (!strcasecmp(sep->arg[1], "reset")) { + ab_arg = 2; + reset_sml = true; + } + else { + c->Message(Chat::White, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); return; } - else if (strcasecmp(sep->arg[1], "reset")) { - c->Message(Chat::White, "A [current] or [reset] argument, or numeric [value] is required to use this command"); + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } - // [reset] falls through with initialization value - my_bot->SetStopMeleeLevel(sml); - database.botdb.SaveStopMeleeLevel(my_bot->GetBotID(), sml); + sbl.remove(nullptr); - c->Message(Chat::White, "Successfully set stop melee level for %s to %u", my_bot->GetCleanName(), sml); + Bot* first_found = nullptr; + int success_count = 0; + + for (auto my_bot : sbl) { + if (!IsCasterClass(my_bot->GetClass()) && !IsHybridClass(my_bot->GetClass())) { + c->Message( + Chat::White, + fmt::format( + "{} says, 'This command only works on caster or hybrid classes.'", + my_bot->GetCleanName() + ).c_str() + ); + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (sync_sml) { + sml = my_bot->GetLevel(); + } + + if (reset_sml) { + sml = my_bot->GetDefaultBotBaseSetting(BotBaseSettings::StopMeleeLevel); + } + + if (current_check) { + c->Message( + Chat::White, + fmt::format( + "{} says, 'My current stop melee level is {}.'", + my_bot->GetCleanName(), + my_bot->GetStopMeleeLevel() + ).c_str() + ); + continue; + } + else { + my_bot->SetStopMeleeLevel(sml); + ++success_count; + } + } + + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::White, + fmt::format( + "{} says, 'My stop melee level was {} to {}.'", + first_found->GetCleanName(), + reset_sml ? "reset" : "set", + sml + ).c_str() + ); + } + else { + c->Message( + Chat::White, + fmt::format( + "{} of your bots {} their stop melee{}'", // level to {}. + success_count, + reset_sml ? "reset" : "set", + fmt::format("{}", reset_sml ? "." : fmt::format(" level to {}.", sml).c_str()).c_str(), + sml + ).c_str() + ); + } + } } void bot_command_summon(Client *c, const Seperator *sep) @@ -1188,19 +1234,24 @@ void bot_command_summon(Client *c, const Seperator *sep) } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } + const int ab_mask = ActionableBots::ABM_Type1; - std::string class_race_arg = sep->arg[1]; + std::string arg1 = sep->arg[1]; + int ab_arg = 1; + + std::string class_race_arg = sep->arg[ab_arg]; bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { class_race_check = true; } std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == ActionableBots::ABT_None) { + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } @@ -1245,34 +1296,44 @@ void bot_command_summon(Client *c, const Seperator *sep) } } -void bot_command_toggle_archer(Client *c, const Seperator *sep) +void bot_command_toggle_ranged(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_command_toggle_archer", sep->arg[0], "bottogglearcher")) { + if (helper_command_alias_fail(c, "bot_command_toggle_ranged", sep->arg[0], "bottoggleranged")) { return; } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "note: Toggles a ranged bot between melee and ranged weapon use"); return; } - const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); + + const int ab_mask = ActionableBots::ABM_Type1; std::string arg1 = sep->arg[1]; - bool archer_state = false; - bool toggle_archer = true; + bool ranged_state = false; + bool toggle_ranged = true; int ab_arg = 1; if (!arg1.compare("on")) { - archer_state = true; - toggle_archer = false; - ab_arg = 2; + ranged_state = true; + toggle_ranged = false; + ++ab_arg; } else if (!arg1.compare("off")) { - toggle_archer = false; - ab_arg = 2; + toggle_ranged = false; + ++ab_arg; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; } std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]) == ActionableBots::ABT_None) { + + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } @@ -1281,17 +1342,18 @@ void bot_command_toggle_archer(Client *c, const Seperator *sep) continue; } - if (toggle_archer) { - bot_iter->SetBotArcherySetting(!bot_iter->IsBotArcher(), true); + if (bot_iter->GetBotRangedValue() < RuleI(Combat, MinRangedAttackDist)) { + c->Message(Chat::Yellow, "%s does not have proper weapons or ammo to be at range.", bot_iter->GetCleanName()); + continue; + } + + if (toggle_ranged) { + bot_iter->SetBotRangedSetting(!bot_iter->IsBotRanged()); } else { - bot_iter->SetBotArcherySetting(archer_state, true); - } - bot_iter->ChangeBotArcherWeapons(bot_iter->IsBotArcher()); - - if (bot_iter->GetClass() == Class::Ranger && bot_iter->GetLevel() >= 61) { - bot_iter->SetRangerAutoWeaponSelect(bot_iter->IsBotArcher()); + bot_iter->SetBotRangedSetting(ranged_state); } + bot_iter->ChangeBotRangedWeapons(bot_iter->IsBotRanged()); } } @@ -1300,7 +1362,7 @@ void bot_command_toggle_helm(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_command_toggle_helm", sep->arg[0], "bottogglehelm")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } const int ab_mask = ActionableBots::ABM_NoFilter; @@ -1313,11 +1375,11 @@ void bot_command_toggle_helm(Client *c, const Seperator *sep) if (!arg1.compare("on")) { helm_state = true; toggle_helm = false; - ab_arg = 2; + ++ab_arg; } else if (!arg1.compare("off")) { toggle_helm = false; - ab_arg = 2; + ++ab_arg; } std::list sbl; @@ -1336,9 +1398,6 @@ void bot_command_toggle_helm(Client *c, const Seperator *sep) bot_iter->SetShowHelm(helm_state); if (ab_type != ActionableBots::ABT_All) { - if (!database.botdb.SaveHelmAppearance(bot_iter->GetBotID(), bot_iter->GetShowHelm())) { - return; - } EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); SpawnAppearance_Struct* saptr = (SpawnAppearance_Struct*)outapp->pBuffer; @@ -1355,13 +1414,6 @@ void bot_command_toggle_helm(Client *c, const Seperator *sep) } if (ab_type == ActionableBots::ABT_All) { - if (toggle_helm) { - database.botdb.ToggleAllHelmAppearances(c->CharacterID()); - } - else { - database.botdb.SaveAllHelmAppearances(c->CharacterID(), helm_state); - } - c->Message(Chat::White, "%s all of your bot show helm flags", toggle_helm ? "Toggled" : (helm_state ? "Set" : "Cleared")); } else { @@ -1439,7 +1491,6 @@ void bot_command_update(Client *c, const Seperator *sep) if (!bot_iter || bot_iter->IsEngaged() || bot_iter->GetLevel() == c->GetLevel()) continue; - bot_iter->SetPetChooser(false); bot_iter->CalcBotStats(c->GetBotOption(Client::booStatsUpdate)); bot_iter->SendAppearancePacket(AppearanceType::WhoLevel, bot_iter->GetLevel(), true, true); ++bot_count; diff --git a/zone/bot_commands/bot_settings.cpp b/zone/bot_commands/bot_settings.cpp new file mode 100644 index 000000000..e94bea5f1 --- /dev/null +++ b/zone/bot_commands/bot_settings.cpp @@ -0,0 +1,43 @@ +#include "../bot_command.h" + +void bot_command_bot_settings(Client* c, const Seperator* sep) +{ + std::list subcommand_list; + subcommand_list.push_back("behindmob"); + subcommand_list.push_back("casterrange"); + subcommand_list.push_back("copysettings"); + subcommand_list.push_back("defaultsettings"); + subcommand_list.push_back("enforcespelllist"); + subcommand_list.push_back("follow"); + subcommand_list.push_back("followdistance"); + subcommand_list.push_back("illusionblock"); + subcommand_list.push_back("maxmeleerange"); + subcommand_list.push_back("owneroption"); + subcommand_list.push_back("petsettype"); + subcommand_list.push_back("sithppercent"); + subcommand_list.push_back("sitincombat"); + subcommand_list.push_back("sitmanapercent"); + subcommand_list.push_back("sithppercent"); + subcommand_list.push_back("spellaggrocheck"); + subcommand_list.push_back("spelldelays"); + subcommand_list.push_back("spellengagedpriority"); + subcommand_list.push_back("spellholds"); + subcommand_list.push_back("spellidlepriority"); + subcommand_list.push_back("spellmaxhppct"); + subcommand_list.push_back("spellmaxmanapct"); + subcommand_list.push_back("spellmaxthresholds"); + subcommand_list.push_back("spellminhppct"); + subcommand_list.push_back("spellminmanapct"); + subcommand_list.push_back("spellminthresholds"); + subcommand_list.push_back("spellpursuepriority"); + subcommand_list.push_back("spelltargetcount"); + subcommand_list.push_back("spelllist"); + subcommand_list.push_back("stance"); + subcommand_list.push_back("togglehelm"); + subcommand_list.push_back("bottoggleranged"); + + if (helper_command_alias_fail(c, "bot_command_bot_settings", sep->arg[0], "botsettings")) + return; + + helper_send_available_subcommands(c, "botsettings", subcommand_list); +} diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp new file mode 100644 index 000000000..77d3ab23b --- /dev/null +++ b/zone/bot_commands/cast.cpp @@ -0,0 +1,301 @@ +#include "../bot_command.h" + +void bot_command_cast(Client* c, const Seperator* sep) +{ + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Commands bots to force cast a specific spell type, ignoring all settings (holds, delays, thresholds, etc)" + }; + + std::vector notes = + { + "- This will interrupt any spell currently being cast by bots told to use the command.", + "- Bots will still check to see if they have the spell in their spell list, whether the target is immune, spell is allowed and all other sanity checks for spells" + }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To tell everyone to Nuke the target:", + fmt::format( + "{} {} spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ), + fmt::format( + "{} {} spawned", + sep->arg[0], + BotSpellTypes::Nuke + ) + }; + std::vector examples_two = + { + "To tell all Enchanters to slow the target:", + fmt::format( + "{} {} byclass {}", + sep->arg[0], + Class::Enchanter, + c->GetSpellTypeShortNameByID(BotSpellTypes::Slow) + ), + fmt::format( + "{} {} byclass {}", + sep->arg[0], + Class::Enchanter, + BotSpellTypes::Slow + ) + }; + std::vector examples_three = + { + "To tell Clrbot to resurrect the targeted corpse:", + fmt::format( + "{} {} byname Clrbot", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Resurrect) + ), + fmt::format( + "{} {} byname Clrbot", + sep->arg[0], + BotSpellTypes::Resurrect + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + int ab_arg = 2; + uint16 spellType = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if ( + spellType == BotSpellTypes::PetBuffs || + spellType == BotSpellTypes::PetCompleteHeals || + spellType == BotSpellTypes::PetFastHeals || + spellType == BotSpellTypes::PetHoTHeals || + spellType == BotSpellTypes::PetRegularHeals || + spellType == BotSpellTypes::PetVeryFastHeals + ) { + c->Message(Chat::Yellow, "Pet type heals and buffs are not supported, use the regular spell type."); + return; + } + + Mob* tar = c->GetTarget(); + LogTestDebug("{}: Attempting {} on {}", __LINE__, c->GetSpellTypeNameByID(spellType), (tar ? tar->GetCleanName() : "NOBODY")); //deleteme + if (spellType != BotSpellTypes::Escape && spellType != BotSpellTypes::Pet) { + if (!tar) { + c->Message(Chat::Yellow, "You need a target for that."); + return; + } + + if (BOT_SPELL_TYPES_DETRIMENTAL(spellType) && !c->IsAttackAllowed(tar)) { + c->Message(Chat::Yellow, "You cannot attack [%s].", tar->GetCleanName()); + return; + } + + if (BOT_SPELL_TYPES_BENEFICIAL(spellType)) { + if (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) { + c->Message(Chat::Yellow, "[%s] is an invalid target.", tar->GetCleanName()); + return; + } + } + } + LogTestDebug("{}: Attempting {} on {}", __LINE__, c->GetSpellTypeNameByID(spellType), (tar ? tar->GetCleanName() : "NOBODY")); //deleteme + switch (spellType) { + case BotSpellTypes::Stun: + case BotSpellTypes::AEStun: + if (tar->GetSpecialAbility(SpecialAbility::StunImmunity)) { + c->Message(Chat::Yellow, "[%s] is immune to stuns.", tar->GetCleanName()); + return; + } + + break; + case BotSpellTypes::Resurrect: + if (!tar->IsCorpse() || !tar->CastToCorpse()->IsPlayerCorpse()) { + c->Message(Chat::Yellow, "[%s] is an invalid target. I can only resurrect player corpses.", tar->GetCleanName()); + return; + } + + break; + default: + break; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + BotSpell botSpell; + botSpell.SpellId = 0; + botSpell.SpellIndex = 0; + botSpell.ManaCost = 0; + bool isSuccess = false; + uint16 successCount = 0; + Bot* firstFound = nullptr; + + for (auto bot_iter : sbl) { + if (!bot_iter->IsInGroupOrRaid()) { + continue; + } + + /* + TODO bot rewrite - + FIX: Snares, Group Cures, OOC Song, Precombat, HateRedux, Fear/AE Fear + ICB (SK) casting hate on friendly but not hostile? + NEED TO CHECK: precombat, AE Dispel, AE Lifetap + DO I NEED A PBAE CHECK??? + */ + if (bot_iter->GetHoldFlag() || bot_iter->GetAppearance() == eaDead || bot_iter->IsFeared() || bot_iter->IsStunned() || bot_iter->IsMezzed() || bot_iter->DivineAura() || bot_iter->GetHP() < 0) { + continue; + } + + Mob* newTar = tar; + LogTestDebug("{}: Attempting {} on {}", __LINE__, c->GetSpellTypeNameByID(spellType), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme + if (!SpellTypeRequiresTarget(spellType, bot_iter->GetClass())) { + newTar = bot_iter; + } + + if (!newTar) { + continue; + } + + if (BOT_SPELL_TYPES_DETRIMENTAL(spellType, bot_iter->GetClass()) && !bot_iter->IsAttackAllowed(newTar)) { + bot_iter->BotGroupSay( + bot_iter, + fmt::format( + "I cannot attack [{}].", + newTar->GetCleanName() + ).c_str() + ); + + continue; + } + + LogTestDebug("{}: Attempting {} on {}", __LINE__, c->GetSpellTypeNameByID(spellType), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme + + bot_iter->SetCommandedSpell(true); + + if (bot_iter->AICastSpell(newTar, 100, spellType)) { + if (!firstFound) { + firstFound = bot_iter; + } + + isSuccess = true; + ++successCount; + } + + bot_iter->SetCommandedSpell(false); + continue; + } + + if (!isSuccess) { + c->Message( + Chat::Yellow, + fmt::format( + "No bots are capable of casting [{}] on {}.", + c->GetSpellTypeNameByID(spellType), + tar ? tar->GetCleanName() : "your target" + ).c_str() + ); + } + else { + c->Message( Chat::Yellow, + fmt::format( + "{} {} [{}]{}", + ((successCount == 1 && firstFound) ? firstFound->GetCleanName() : (fmt::format("{}", successCount).c_str())), + ((successCount == 1 && firstFound) ? "casted" : "of your bots casted"), + c->GetSpellTypeNameByID(spellType), + tar ? (fmt::format(" on {}.", tar->GetCleanName()).c_str()) : "." + ).c_str() + ); + } +} diff --git a/zone/bot_commands/caster_range.cpp b/zone/bot_commands/caster_range.cpp index 2c1faf639..447c739db 100644 --- a/zone/bot_commands/caster_range.cpp +++ b/zone/bot_commands/caster_range.cpp @@ -7,7 +7,7 @@ void bot_command_caster_range(Client* c, const Seperator* sep) } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [current | value: 0 - 300] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s [current | value: 0 - 300] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); c->Message(Chat::White, "note: Can only be used for Casters or Hybrids."); c->Message(Chat::White, "note: Use [current] to check the current setting."); c->Message(Chat::White, "note: Set the value to the minimum distance you want your bot to try to remain from its target."); @@ -15,6 +15,7 @@ void bot_command_caster_range(Client* c, const Seperator* sep) c->Message(Chat::White, "note: This is set to (90) units by default."); return; } + const int ab_mask = ActionableBots::ABM_Type1; std::string arg1 = sep->arg[1]; @@ -23,7 +24,7 @@ void bot_command_caster_range(Client* c, const Seperator* sep) uint32 crange = 0; if (sep->IsNumber(1)) { - ab_arg = 2; + ++ab_arg; crange = atoi(sep->arg[1]); if (crange < 0 || crange > 300) { c->Message(Chat::White, "You must enter a value within the range of 0 - 300."); @@ -31,7 +32,7 @@ void bot_command_caster_range(Client* c, const Seperator* sep) } } else if (!arg1.compare("current")) { - ab_arg = 2; + ++ab_arg; current_check = true; } else { @@ -78,8 +79,6 @@ void bot_command_caster_range(Client* c, const Seperator* sep) else { my_bot->SetBotCasterRange(crange); ++success_count; - - database.botdb.SaveBotCasterRange(my_bot->GetBotID(), crange); } } if (!current_check) { diff --git a/zone/bot_commands/class_race_list.cpp b/zone/bot_commands/class_race_list.cpp new file mode 100644 index 000000000..84c611ab3 --- /dev/null +++ b/zone/bot_commands/class_race_list.cpp @@ -0,0 +1,113 @@ +#include "../bot_command.h" + +void bot_command_class_race_list(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" + }; + + 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] = { + Race::Doug, + Race::Human, Race::Barbarian, Race::Erudite, Race::WoodElf, + Race::HighElf, Race::DarkElf, Race::HalfElf, Race::Dwarf, + Race::Troll, Race::Ogre, Race::Halfling, Race::Gnome, + Race::Iksar, Race::VahShir, Race::Froglok2, Race::Drakkin + }; + + std::string window_text; + std::string message_separator; + int object_count = 0; + const int object_max = 4; + + window_text.append( + fmt::format( + "Classes{}", + DialogueWindow::Break() + ) + ); + + window_text.append( + fmt::format( + "--------------------------------------------------------------------", + DialogueWindow::Break() + ) + ); + + window_text.append(DialogueWindow::Break()); + + message_separator = " "; + object_count = 0; + for (int i = 0; i <= 15; ++i) { + window_text.append(message_separator); + + if (object_count >= object_max) { + window_text.append(DialogueWindow::Break()); + object_count = 0; + } + + window_text.append( + fmt::format("{} ({})", + class_substrs[i + 1], + (i + 1) + ) + ); + + ++object_count; + message_separator = ", "; + } + + window_text.append(DialogueWindow::Break(2)); + + window_text.append( + fmt::format( + "Races{}", + DialogueWindow::Break() + ) + ); + + window_text.append( + fmt::format( + "--------------------------------------------------------------------", + DialogueWindow::Break() + ) + ); + + window_text.append(DialogueWindow::Break()); + + message_separator = " "; + object_count = 0; + for (int i = 0; i <= 15; ++i) { + window_text.append(message_separator); + + if (object_count >= object_max) { + window_text.append(DialogueWindow::Break()); + object_count = 0; + } + + window_text.append( + fmt::format("{} ({})", + race_substrs[i + 1], + race_values[i + 1] + ) + ); + + ++object_count; + message_separator = ", "; + } + + c->SendPopupToClient("Bot Creation Options", window_text.c_str()); + + return; +} diff --git a/zone/bot_commands/click_item.cpp b/zone/bot_commands/click_item.cpp index b236815d8..80375b52c 100644 --- a/zone/bot_commands/click_item.cpp +++ b/zone/bot_commands/click_item.cpp @@ -8,7 +8,7 @@ void bot_command_click_item(Client* c, const Seperator* sep) } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); c->Message(Chat::White, "This will cause the selected bots to click the item in the given slot ID."); c->Message(Chat::White, "Use ^invlist to see their items along with slot IDs."); return; @@ -25,7 +25,7 @@ void bot_command_click_item(Client* c, const Seperator* sep) uint32 slot_id = 0; if (sep->IsNumber(1)) { - ab_arg = 2; + ++ab_arg; slot_id = atoi(sep->arg[1]); if (slot_id < EQ::invslot::EQUIPMENT_BEGIN || slot_id > EQ::invslot::EQUIPMENT_END) { c->Message(Chat::Yellow, "You must specify a valid inventory slot from 0 to 22. Use %s help for more information", sep->arg[0]); diff --git a/zone/bot_commands/copy_settings.cpp b/zone/bot_commands/copy_settings.cpp new file mode 100644 index 000000000..fafead14c --- /dev/null +++ b/zone/bot_commands/copy_settings.cpp @@ -0,0 +1,366 @@ +#include "../bot_command.h" + +void bot_command_copy_settings(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_copy_settings", sep->arg[0], "copysettings")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Copies settings from one bot to another bot" + }; + + std::vector notes = + { + "- You can put a spell type ID or shortname after any option except [all], [misc] and [spellsettings] to restore that specifc spell type only" + }; + + std::vector example_format = + { + fmt::format( + "{} [from] [to] [option]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To copy all settings from BotA to BotB:", + fmt::format( + "{} BotA BotB all", + sep->arg[0] + ) + }; + std::vector examples_two = + { + "To copy only Nuke spelltypesettings from BotA to BotB:", + fmt::format( + "{} BotA BotB spelltypesettings {}", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ), + fmt::format( + "{} BotA BotB spelltypesettings {}", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ), + }; + std::vector examples_three = { }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid", + "targetgroup, namesgroup, healrotationtargets", + "mmr, byclass, byrace, spawned" + }; + + std::vector options = + { + "all, misc, spellsettings, spelltypesettings", + "holds, delays, minthresholds, maxthresholds", + "minmanapct, maxmanapct, minhppct, maxhppct", + "idlepriority, engagedpriority, pursuepriority", + "aggrochecks, targetcounts" + }; + std::vector options_one = + { + "[spellsettings] will copy ^spellsettings options", + "[spelltypesettings] copies all spell type settings", + "[all] copies all settings" + }; + std::vector options_two = + { + "[misc] copies all miscellaneous options such as:", + "- ^showhelm, ^followd, ^stopmeleelevel", + "- ^enforcespellsettings, ^bottoggleranged, ^petsettype", + "- ^behindmob, ^casterrange, ^illusionblock", + "- ^sitincombat, ^sithppercent and ^sitmanapercent", + + }; + std::vector options_three = + { + "The remaining options copy that specific type" + }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + int ab_arg = 2; + bool validOption = false; + uint16 spellType = UINT16_MAX; + std::vector options = + { + "all", + "misc" + "spellsettings", + "spelltypesettings", + "holds", + "delays", + "minthresholds", + "maxthresholds", + "aggrochecks", + "minmanapct", + "maxmanapct", + "minhppct", + "maxhppct", + "idlepriority", + "engagedpriority", + "pursuepriority", + "targetcounts" + }; + + for (int i = 0; i < options.size(); i++) { + if (sep->arg[3] == options[i]) { + if (options[i] != "all" && options[i] != "misc" && options[i] != "spellsettings") { + + if (sep->IsNumber(4) || c->GetSpellTypeIDByShortName(sep->arg[4]) != UINT16_MAX) { + if (sep->IsNumber(4)) { + spellType = atoi(sep->arg[4]); + } + + if (c->GetSpellTypeIDByShortName(sep->arg[4]) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(sep->arg[4]); + } + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + } + else if ( + (options[i] == "all" || options[i] == "misc" || options[i] == "spellsettings") && + ((sep->IsNumber(2) || c->GetSpellTypeIDByShortName(sep->arg[4]) != UINT16_MAX)) + ) { + break; + } + + validOption = true; + break; + } + } + + if (!validOption) { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + auto from = entity_list.GetBotByBotName(sep->arg[1]); + + if (!from) { + c->Message(Chat::Yellow, "Could not find %s.", sep->arg[1]); + return; + } + + if (!from->IsBot()) { + c->Message(Chat::Yellow, "%s is not a bot.", from->GetCleanName()); + return; + } + + if (!from->GetOwner()) { + c->Message(Chat::Yellow, "Could not find %s's owner.", from->GetCleanName()); + } + + if (RuleB(Bots, CopySettingsOwnBotsOnly) && from->GetOwner() != c) { + c->Message(Chat::Yellow, "You name a bot you own to use this command."); + return; + } + + if (!RuleB(Bots, AllowCopySettingsAnon) && from->GetOwner() != c && from->GetOwner()->CastToClient()->GetAnon()) { + c->Message(Chat::Yellow, "You name a bot you own to use this command."); + return; + } + + auto to = ActionableBots::AsNamed_ByBot(c, sep->arg[2]); + + if (!to) { + c->Message(Chat::Yellow, "Could not find %s.", sep->arg[1]); + return; + } + + if (!to->IsBot()) { + c->Message(Chat::Yellow, "%s is not a bot.", to->GetCleanName()); + return; + } + + if (!to->GetOwner()) { + c->Message(Chat::Yellow, "Could not find %s's owner.", to->GetCleanName()); + } + + if (to->GetOwner() != c) { + c->Message(Chat::Yellow, "You must name a spawned bot that you own to use this command."); + return; + } + + if (to == from) { + c->Message(Chat::Yellow, "You cannot copy to the same bot that you're copying from."); + return; + } + + std::string output = ""; + + if (!strcasecmp(sep->arg[3], "misc")) { + from->CopySettings(to, BotSettingCategories::BaseSetting); + output = "Miscellaneous"; + } + else if (!strcasecmp(sep->arg[3], "holds")) { + from->CopySettings(to, BotSettingCategories::SpellHold, spellType); + output = from->GetBotSpellCategoryName(BotSettingCategories::SpellHold); + } + else if (!strcasecmp(sep->arg[3], "delays")) { + from->CopySettings(to, BotSettingCategories::SpellDelay, spellType); + output = from->GetBotSpellCategoryName(BotSettingCategories::SpellDelay); + } + else if (!strcasecmp(sep->arg[3], "minthresholds")) { + from->CopySettings(to, BotSettingCategories::SpellMinThreshold, spellType); + output = from->GetBotSpellCategoryName(BotSettingCategories::SpellMinThreshold); + } + else if (!strcasecmp(sep->arg[3], "maxthresholds")) { + from->CopySettings(to, BotSettingCategories::SpellMaxThreshold, spellType); + output = from->GetBotSpellCategoryName(BotSettingCategories::SpellMaxThreshold); + } + else if (!strcasecmp(sep->arg[3], "aggrochecks")) { + from->CopySettings(to, BotSettingCategories::SpellTypeAggroCheck, spellType); + output = from->GetBotSpellCategoryName(BotSettingCategories::SpellTypeAggroCheck); + } + else if (!strcasecmp(sep->arg[3], "minmanapct")) { + from->CopySettings(to, BotSettingCategories::SpellTypeMinManaPct, spellType); + output = from->GetBotSpellCategoryName(BotSettingCategories::SpellTypeMinManaPct); + } + else if (!strcasecmp(sep->arg[3], "maxmanapct")) { + from->CopySettings(to, BotSettingCategories::SpellTypeMaxManaPct, spellType); + output = from->GetBotSpellCategoryName(BotSettingCategories::SpellTypeMaxManaPct); + } + else if (!strcasecmp(sep->arg[3], "minhppct")) { + from->CopySettings(to, BotSettingCategories::SpellTypeMinHPPct, spellType); + output = from->GetBotSpellCategoryName(BotSettingCategories::SpellTypeMinHPPct); + } + else if (!strcasecmp(sep->arg[3], "maxhppct")) { + from->CopySettings(to, BotSettingCategories::SpellTypeMaxHPPct, spellType); + output = from->GetBotSpellCategoryName(BotSettingCategories::SpellTypeMaxHPPct); + } + else if (!strcasecmp(sep->arg[3], "idlepriority")) { + from->CopySettings(to, BotSettingCategories::SpellTypeIdlePriority, spellType); + output = from->GetBotSpellCategoryName(BotSettingCategories::SpellTypeIdlePriority); + } + else if (!strcasecmp(sep->arg[3], "engagedpriority")) { + from->CopySettings(to, BotSettingCategories::SpellTypeEngagedPriority, spellType); + output = from->GetBotSpellCategoryName(BotSettingCategories::SpellTypeEngagedPriority); + } + else if (!strcasecmp(sep->arg[3], "pursuepriority")) { + from->CopySettings(to, BotSettingCategories::SpellTypePursuePriority, spellType); + output = from->GetBotSpellCategoryName(BotSettingCategories::SpellTypePursuePriority); + } + else if (!strcasecmp(sep->arg[3], "targetcounts")) { + from->CopySettings(to, BotSettingCategories::SpellTypeAEOrGroupTargetCount, spellType); + output = from->GetBotSpellCategoryName(BotSettingCategories::SpellTypeAEOrGroupTargetCount); + } + else if (!strcasecmp(sep->arg[3], "spellsettings")) { + from->CopyBotSpellSettings(to); + output = "^spellsettings"; + } + else if (!strcasecmp(sep->arg[3], "spelltypesettings")) { + from->CopySettings(to, BotSettingCategories::SpellHold, spellType); + from->CopySettings(to, BotSettingCategories::SpellDelay, spellType); + from->CopySettings(to, BotSettingCategories::SpellMinThreshold, spellType); + from->CopySettings(to, BotSettingCategories::SpellMaxThreshold, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeAggroCheck, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeMinManaPct, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeMaxManaPct, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeMinHPPct, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeMaxHPPct, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeIdlePriority, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeEngagedPriority, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypePursuePriority, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeAEOrGroupTargetCount, spellType); + output = "spell type"; + } + else if (!strcasecmp(sep->arg[3], "all")) { + from->CopySettings(to, BotSettingCategories::BaseSetting); + from->CopySettings(to, BotSettingCategories::SpellHold, spellType); + from->CopySettings(to, BotSettingCategories::SpellDelay, spellType); + from->CopySettings(to, BotSettingCategories::SpellMinThreshold, spellType); + from->CopySettings(to, BotSettingCategories::SpellMaxThreshold, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeAggroCheck, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeMinManaPct, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeMaxManaPct, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeMinHPPct, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeMaxHPPct, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeIdlePriority, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeEngagedPriority, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypePursuePriority, spellType); + from->CopySettings(to, BotSettingCategories::SpellTypeAEOrGroupTargetCount, spellType); + from->CopyBotSpellSettings(to); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + c->Message( + Chat::Green, + fmt::format( + "{}'s{}{} settings were copied to {}.", + from->GetCleanName(), + ( + spellType != UINT16_MAX ? + fmt::format(" [{}] ", + c->GetSpellTypeNameByID(spellType) + ) + : " " + ), + output, + to->GetCleanName() + ).c_str() + ); +} diff --git a/zone/bot_commands/default_settings.cpp b/zone/bot_commands/default_settings.cpp new file mode 100644 index 000000000..1f7c854ca --- /dev/null +++ b/zone/bot_commands/default_settings.cpp @@ -0,0 +1,473 @@ +#include "../bot_command.h" + +void bot_command_default_settings(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_default_settings", sep->arg[0], "defaultsettings")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Restores a bot's setting(s) to defaults" + }; + + std::vector notes = + { + "- You can put a spell type ID or shortname after any option except [all], [misc] and [spellsettings] to restore that specifc spell type only" + }; + + std::vector example_format = + { + fmt::format( + "{} [option] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To restore delays for Clerics:", + fmt::format( + "{} delays byclass 2", + sep->arg[0] + ) + }; + std::vector examples_two = + { + "To restore only Snare delays for BotA:", + fmt::format( + "{} delays {} byname BotA", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} delays {} byname BotA", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + std::vector examples_three = { }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets mmr, byclass, byrace, spawned" + }; + + std::vector options = + { + "all, misc, spellsettings, spelltypesettings, holds, delays, minthresholds, maxthresholds minmanapct, maxmanapct, minhppct, maxhppct, idlepriority, engagedpriority, pursuepriority, aggrocheck, targetcounts" + }; + std::vector options_one = + { + "[spellsettings] will restore ^spellsettings options", + "[spelltypesettings] restores all spell type settings", + "[all] restores all settings" + }; + std::vector options_two = + { + "[misc] restores all miscellaneous options such as:", + "- ^showhelm, ^followd, ^stopmeleelevel", + "- ^enforcespellsettings, ^bottoggleranged, ^petsettype", + "- ^behindmob, ^casterrange, ^illusionblock", + "- ^sitincombat, ^sithppercent and ^sitmanapercent", + + }; + std::vector options_three = + { + "The remaining options restore that specific type" + }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + int ab_arg = 2; + bool validOption = false; + uint16 spellType = UINT16_MAX; + std::vector options = + { + "all", + "misc" + "spellsettings", + "spelltypesettings", + "holds", + "delays", + "minthresholds", + "maxthresholds", + "aggrocheck", + "minmanapct", + "maxmanapct", + "minhppct", + "maxhppct", + "idlepriority", + "engagedpriority", + "pursuepriority", + "targetcounts" + }; + + for (int i = 0; i < options.size(); i++) { + if (sep->arg[1] == options[i]) { + if (options[i] != "all" && options[i] != "misc" && options[i] != "spellsettings") { + + if (sep->IsNumber(2) || c->GetSpellTypeIDByShortName(sep->arg[2]) != UINT16_MAX) { + if (sep->IsNumber(2)) { + spellType = atoi(sep->arg[2]); + } + + if (c->GetSpellTypeIDByShortName(sep->arg[2]) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(sep->arg[2]); + } + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + + ++ab_arg; + } + } + else if ( + (options[i] == "all" || options[i] == "misc" || options[i] == "spellsettings") && + ((sep->IsNumber(2) || c->GetSpellTypeIDByShortName(sep->arg[2]) != UINT16_MAX)) + ) { + break; + } + + validOption = true; + break; + } + } + + if (!validOption) { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + std::string output = ""; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + + if (!strcasecmp(sep->arg[1], "misc")) { + for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { + my_bot->SetBotBaseSetting(i, my_bot->GetDefaultBotBaseSetting(i)); + output = "miscellanous settings"; + } + } + else if (!strcasecmp(sep->arg[1], "holds")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellHold(spellType, my_bot->GetDefaultSpellHold(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellHold(i, my_bot->GetDefaultSpellHold(i)); + } + } + + output = "hold settings"; + } + else if (!strcasecmp(sep->arg[1], "delays")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellDelay(spellType, my_bot->GetDefaultSpellDelay(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellDelay(i, my_bot->GetDefaultSpellDelay(i)); + } + } + + output = "delay settings"; + } + else if (!strcasecmp(sep->arg[1], "minthresholds")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellMinThreshold(spellType, my_bot->GetDefaultSpellMinThreshold(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellMinThreshold(i, my_bot->GetDefaultSpellMinThreshold(i)); + } + } + + output = "minimum threshold settings"; + } + else if (!strcasecmp(sep->arg[1], "maxthresholds")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellMaxThreshold(spellType, my_bot->GetDefaultSpellMaxThreshold(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellMaxThreshold(i, my_bot->GetDefaultSpellMaxThreshold(i)); + } + } + + output = "maximum threshold settings"; + } + else if (!strcasecmp(sep->arg[1], "aggrochecks")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellTypeAggroCheck(spellType, my_bot->GetDefaultSpellTypeAggroCheck(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellTypeAggroCheck(i, my_bot->GetDefaultSpellTypeAggroCheck(i)); + } + } + + output = "aggro check settings"; + } + else if (!strcasecmp(sep->arg[1], "minmanapct")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellTypeMinManaLimit(spellType, my_bot->GetDefaultSpellTypeMinManaLimit(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellTypeMinManaLimit(i, my_bot->GetDefaultSpellTypeMinManaLimit(i)); + } + } + + output = "min mana settings"; + } + else if (!strcasecmp(sep->arg[1], "maxmanapct")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellTypeMaxManaLimit(spellType, my_bot->GetDefaultSpellTypeMaxManaLimit(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellTypeMaxManaLimit(i, my_bot->GetDefaultSpellTypeMaxManaLimit(i)); + } + } + + output = "max mana settings"; + } + else if (!strcasecmp(sep->arg[1], "minhppct")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellTypeMinHPLimit(spellType, my_bot->GetDefaultSpellTypeMinHPLimit(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellTypeMinHPLimit(i, my_bot->GetDefaultSpellTypeMinHPLimit(i)); + } + } + + output = "min hp settings"; + } + else if (!strcasecmp(sep->arg[1], "maxhppct")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellTypeMaxHPLimit(spellType, my_bot->GetDefaultSpellTypeMaxHPLimit(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellTypeMaxHPLimit(i, my_bot->GetDefaultSpellTypeMaxHPLimit(i)); + } + } + + output = "max hp settings"; + } + else if (!strcasecmp(sep->arg[1], "idlepriority")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Idle, my_bot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Idle, my_bot->GetClass())); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellTypePriority(i, BotPriorityCategories::Idle, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Idle, my_bot->GetClass())); + } + } + + output = "idle priority settings"; + } + else if (!strcasecmp(sep->arg[1], "engagedpriority")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Engaged, my_bot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Engaged, my_bot->GetClass())); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellTypePriority(i, BotPriorityCategories::Engaged, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Engaged, my_bot->GetClass())); + } + } + + output = "engaged priority settings"; + } + else if (!strcasecmp(sep->arg[1], "pursuepriority")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Pursue, my_bot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Pursue, my_bot->GetClass())); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellTypePriority(i, BotPriorityCategories::Pursue, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Pursue, my_bot->GetClass())); + } + } + + output = "pursue priority settings"; + } + else if (!strcasecmp(sep->arg[1], "targetcounts")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellDelay(spellType, my_bot->GetDefaultSpellDelay(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellTypeAEOrGroupTargetCount(i, my_bot->GetDefaultSpellTypeAEOrGroupTargetCount(i)); + } + } + + output = "ae/group count settings"; + } + else if (!strcasecmp(sep->arg[1], "spellsettings")) { + my_bot->ResetBotSpellSettings(); + output = "^spellsettings"; + } + else if (!strcasecmp(sep->arg[1], "spelltypesettings")) { + if (spellType != UINT16_MAX) { + my_bot->SetSpellHold(spellType, my_bot->GetDefaultSpellHold(spellType)); + my_bot->SetSpellDelay(spellType, my_bot->GetDefaultSpellDelay(spellType)); + my_bot->SetSpellMinThreshold(spellType, my_bot->GetDefaultSpellMinThreshold(spellType)); + my_bot->SetSpellMaxThreshold(spellType, my_bot->GetDefaultSpellMaxThreshold(spellType)); + my_bot->SetSpellTypeAggroCheck(spellType, my_bot->GetDefaultSpellTypeAggroCheck(spellType)); + my_bot->SetSpellTypeMinManaLimit(spellType, my_bot->GetDefaultSpellTypeMinManaLimit(spellType)); + my_bot->SetSpellTypeMaxManaLimit(spellType, my_bot->GetDefaultSpellTypeMaxManaLimit(spellType)); + my_bot->SetSpellTypeMinHPLimit(spellType, my_bot->GetDefaultSpellTypeMinHPLimit(spellType)); + my_bot->SetSpellTypeMaxHPLimit(spellType, my_bot->GetDefaultSpellTypeMaxHPLimit(spellType)); + my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Idle, my_bot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Idle, my_bot->GetClass())); + my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Engaged, my_bot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Engaged, my_bot->GetClass())); + my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Pursue, my_bot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Pursue, my_bot->GetClass())); + my_bot->SetSpellTypeAEOrGroupTargetCount(spellType, my_bot->GetDefaultSpellTypeAEOrGroupTargetCount(spellType)); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellHold(i, my_bot->GetDefaultSpellHold(i)); + my_bot->SetSpellDelay(i, my_bot->GetDefaultSpellDelay(i)); + my_bot->SetSpellMinThreshold(i, my_bot->GetDefaultSpellMinThreshold(i)); + my_bot->SetSpellMaxThreshold(i, my_bot->GetDefaultSpellMaxThreshold(i)); + my_bot->SetSpellTypeAggroCheck(i, my_bot->GetDefaultSpellTypeAggroCheck(i)); + my_bot->SetSpellTypeMinManaLimit(i, my_bot->GetDefaultSpellTypeMinManaLimit(i)); + my_bot->SetSpellTypeMaxManaLimit(i, my_bot->GetDefaultSpellTypeMaxManaLimit(i)); + my_bot->SetSpellTypeMinHPLimit(i, my_bot->GetDefaultSpellTypeMinHPLimit(i)); + my_bot->SetSpellTypeMaxHPLimit(i, my_bot->GetDefaultSpellTypeMaxHPLimit(i)); + my_bot->SetSpellTypePriority(i, BotPriorityCategories::Idle, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Idle, my_bot->GetClass())); + my_bot->SetSpellTypePriority(i, BotPriorityCategories::Engaged, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Engaged, my_bot->GetClass())); + my_bot->SetSpellTypePriority(i, BotPriorityCategories::Pursue, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Pursue, my_bot->GetClass())); + my_bot->SetSpellTypeAEOrGroupTargetCount(i, my_bot->GetDefaultSpellTypeAEOrGroupTargetCount(i)); + } + } + + output = "spell type settings"; + } + else if (!strcasecmp(sep->arg[1], "all")) { + for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { + my_bot->SetBotBaseSetting(i, my_bot->GetDefaultBotBaseSetting(i)); + } + + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetSpellHold(i, my_bot->GetDefaultSpellHold(i)); + my_bot->SetSpellDelay(i, my_bot->GetDefaultSpellDelay(i)); + my_bot->SetSpellMinThreshold(i, my_bot->GetDefaultSpellMinThreshold(i)); + my_bot->SetSpellMaxThreshold(i, my_bot->GetDefaultSpellMaxThreshold(i)); + my_bot->SetSpellTypeAggroCheck(i, my_bot->GetDefaultSpellTypeAggroCheck(i)); + my_bot->SetSpellTypeMinManaLimit(i, my_bot->GetDefaultSpellTypeMinManaLimit(i)); + my_bot->SetSpellTypeMaxManaLimit(i, my_bot->GetDefaultSpellTypeMaxManaLimit(i)); + my_bot->SetSpellTypeMinHPLimit(i, my_bot->GetDefaultSpellTypeMinHPLimit(i)); + my_bot->SetSpellTypeMaxHPLimit(i, my_bot->GetDefaultSpellTypeMaxHPLimit(i)); + my_bot->SetSpellTypePriority(i, BotPriorityCategories::Idle, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Idle, my_bot->GetClass())); + my_bot->SetSpellTypePriority(i, BotPriorityCategories::Engaged, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Engaged, my_bot->GetClass())); + my_bot->SetSpellTypePriority(i, BotPriorityCategories::Pursue, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Pursue, my_bot->GetClass())); + my_bot->SetSpellTypeAEOrGroupTargetCount(i, my_bot->GetDefaultSpellTypeAEOrGroupTargetCount(i)); + }; + + my_bot->ResetBotSpellSettings(); + + output = "settings"; + + } + + ++success_count; + } + + if (success_count == 1) { + c->Message( + Chat::Green, + fmt::format( + "{} says, '{}{} were restored.'", + first_found->GetCleanName(), + ( + spellType != UINT16_MAX ? + fmt::format("My [{}] ", + c->GetSpellTypeNameByID(spellType) + ) + : "My " + ), + output + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bot's{}{} were restored.", + success_count, + ( + spellType != UINT16_MAX ? + fmt::format(" [{}] ", + c->GetSpellTypeNameByID(spellType) + ) + : " " + ), + output + ).c_str() + ); + } +} diff --git a/zone/bot_commands/defensive.cpp b/zone/bot_commands/defensive.cpp index a2edc3e6e..081e07f34 100644 --- a/zone/bot_commands/defensive.cpp +++ b/zone/bot_commands/defensive.cpp @@ -6,22 +6,27 @@ void bot_command_defensive(Client *c, const Seperator *sep) if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Stance) || helper_command_alias_fail(c, "bot_command_defensive", sep->arg[0], "defensive")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); helper_send_usage_required_bots(c, BCEnum::SpT_Stance); return; } const int ab_mask = ActionableBots::ABM_Type1; - std::string class_race_arg = sep->arg[1]; + std::string arg1 = sep->arg[1]; + int ab_arg = 1; + + std::string class_race_arg = sep->arg[ab_arg]; bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { class_race_check = true; } std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == ActionableBots::ABT_None) { + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } + sbl.remove(nullptr); int success_count = 0; diff --git a/zone/bot_commands/follow.cpp b/zone/bot_commands/follow.cpp index 65ec73f8b..3ff0cb44b 100644 --- a/zone/bot_commands/follow.cpp +++ b/zone/bot_commands/follow.cpp @@ -1,11 +1,11 @@ #include "../bot_command.h" -void bot_command_follow(Client *c, const Seperator *sep) +void bot_command_follow(Client* c, const Seperator* sep) { if (helper_command_alias_fail(c, "bot_command_follow", sep->arg[0], "follow")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s ([option: reset]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); + c->Message(Chat::White, "usage: () %s ([option: reset]) [actionable: byname | ownergroup | ownerraid | namesgroup | mmr | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); c->Message(Chat::White, "usage: %s chain", sep->arg[0]); return; } @@ -26,13 +26,22 @@ void bot_command_follow(Client *c, const Seperator *sep) } else if (!optional_arg.compare("reset")) { reset = true; - ab_arg = 2; - name_arg = 3; + ++ab_arg; + ++name_arg ; } else { - target_mob = ActionableTarget::VerifyFriendly(c, BCEnum::TT_Single); + //target_mob = ActionableTarget::VerifyFriendly(c, BCEnum::TT_Single); + target_mob = c->GetTarget(); if (!target_mob) { - c->Message(Chat::White, "You must a friendly mob to use this command"); + c->Message(Chat::White, "You must a friendly player or bot within your group or raid to use this command"); + return; + } + else if (!target_mob->IsBot() && !target_mob->IsClient()) { + c->Message(Chat::White, "You must a friendly player or bot within your group or raid to use this command"); + return; + } + else if ((target_mob->GetGroup() && target_mob->GetGroup() != c->GetGroup()) || (target_mob->GetRaid() && target_mob->GetRaid() != c->GetRaid())) { + c->Message(Chat::White, "You must a friendly player or bot within your group or raid to use this command"); return; } } @@ -51,29 +60,54 @@ void bot_command_follow(Client *c, const Seperator *sep) sbl.remove(nullptr); for (auto bot_iter : sbl) { bot_iter->WipeHateList(); - auto my_group = bot_iter->GetGroup(); - if (my_group) { - if (reset) { - if (!my_group->GetLeader() || my_group->GetLeader() == bot_iter) - bot_iter->SetFollowID(c->GetID()); - else - bot_iter->SetFollowID(my_group->GetLeader()->GetID()); - bot_iter->SetManualFollow(false); - } - else { - if (bot_iter == target_mob) - bot_iter->SetFollowID(c->GetID()); - else - bot_iter->SetFollowID(target_mob->GetID()); - - bot_iter->SetManualFollow(true); - } - } - else { + if (!bot_iter->GetGroup() && !bot_iter->GetRaid()) { bot_iter->SetFollowID(0); bot_iter->SetManualFollow(false); } + else { + if (reset) { + bot_iter->SetFollowID(c->GetID()); + bot_iter->SetManualFollow(false); + } + else { + if (target_mob->IsGrouped() || target_mob->IsRaidGrouped()) { + bot_iter->SetFollowID(target_mob->GetID()); + bot_iter->SetManualFollow(true); + } + else if (bot_iter == target_mob) { + bot_iter->SetFollowID(c->GetID()); + bot_iter->SetManualFollow(true); + } + else { + bot_iter->SetFollowID(0); + bot_iter->SetManualFollow(false); + } + } + } + //auto my_group = bot_iter->GetGroup(); + //if (my_group) { + // if (reset) { + // if (!my_group->GetLeader() || my_group->GetLeader() == bot_iter) + // bot_iter->SetFollowID(c->GetID()); + // else + // bot_iter->SetFollowID(my_group->GetLeader()->GetID()); + // + // bot_iter->SetManualFollow(false); + // } + // else { + // if (bot_iter == target_mob) + // bot_iter->SetFollowID(c->GetID()); + // else + // bot_iter->SetFollowID(target_mob->GetID()); + // + // bot_iter->SetManualFollow(true); + // } + //} + //else { + // bot_iter->SetFollowID(0); + // bot_iter->SetManualFollow(false); + //} if (!bot_iter->GetPet()) continue; @@ -81,31 +115,37 @@ void bot_command_follow(Client *c, const Seperator *sep) bot_iter->GetPet()->SetFollowID(bot_iter->GetID()); } + Mob* follow_mob = nullptr; if (sbl.size() == 1) { - Mob* follow_mob = entity_list.GetMob(sbl.front()->GetFollowID()); - Bot::BotGroupSay( - sbl.front(), + follow_mob = entity_list.GetMob(sbl.front()->GetFollowID()); + c->Message( + Chat::White, fmt::format( "Following {}.", - follow_mob ? follow_mob->GetCleanName() : "no one" + follow_mob ? follow_mob->GetCleanName() : "you" ).c_str() ); - } else { + } + else { if (reset) { c->Message( Chat::White, fmt::format( - "{} of your bots are following their default assignments.", + "{} of your bots are following you.", sbl.size() ).c_str() ); - } else { + } + else { + if (!sbl.empty()) { + follow_mob = entity_list.GetMob(sbl.front()->GetFollowID()); + } c->Message( Chat::White, fmt::format( "{} of your bots are following {}.", sbl.size(), - target_mob->GetCleanName() + follow_mob ? follow_mob->GetCleanName() : "you" ).c_str() ); } diff --git a/zone/bot_commands/guard.cpp b/zone/bot_commands/guard.cpp index 27a9ed343..1cacd2742 100644 --- a/zone/bot_commands/guard.cpp +++ b/zone/bot_commands/guard.cpp @@ -7,7 +7,7 @@ void bot_command_guard(Client *c, const Seperator *sep) } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: clear]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([option: clear]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | mmr | byclass | byrace | default: spawned] ([actionable_name])", sep->arg[0]); return; } const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_Type2); @@ -20,8 +20,8 @@ void bot_command_guard(Client *c, const Seperator *sep) if (!clear_arg.compare("clear")) { clear = true; - ab_arg = 2; - name_arg = 3; + ++ab_arg; + ++name_arg; } std::string class_race_arg = sep->arg[ab_arg]; diff --git a/zone/bot_commands/hold.cpp b/zone/bot_commands/hold.cpp index 49f0f516f..0fad872dc 100644 --- a/zone/bot_commands/hold.cpp +++ b/zone/bot_commands/hold.cpp @@ -7,7 +7,7 @@ void bot_command_hold(Client *c, const Seperator *sep) } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: clear]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([option: clear]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | mmr | byclass | byrace | default: spawned] ([actionable_name])", sep->arg[0]); return; } const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_Type2); @@ -20,8 +20,8 @@ void bot_command_hold(Client *c, const Seperator *sep) if (!clear_arg.compare("clear")) { clear = true; - ab_arg = 2; - name_arg = 3; + ++ab_arg; + ++name_arg; } std::string class_race_arg = sep->arg[ab_arg]; diff --git a/zone/bot_commands/illusion_block.cpp b/zone/bot_commands/illusion_block.cpp new file mode 100644 index 000000000..0a3625046 --- /dev/null +++ b/zone/bot_commands/illusion_block.cpp @@ -0,0 +1,172 @@ +#include "../bot_command.h" + +void bot_command_illusion_block(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_illusion_block", sep->arg[0], "illusionblock")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Toggles whether or not bots will block the illusion effects of spells cast by players or bots." + }; + + std::vector notes = { }; + + std::vector example_format = + { + fmt::format( + "{} [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set BotA to block illusions:", + fmt::format( + "{} 1 byname BotA", + sep->arg[0] + ) + }; + std::vector examples_two = { }; + std::vector examples_three = + { + "To check the illusion block status for all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + int ab_arg = 1; + bool current_check = false; + uint32 typeValue = 0; + + if (sep->IsNumber(1)) { + typeValue = atoi(sep->arg[1]); + ++ab_arg; + if (typeValue < 0 || typeValue > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} block illusions.'", + my_bot->GetCleanName(), + my_bot->GetIllusionBlock() ? "will" : "will not" + ).c_str() + ); + } + else { + my_bot->SetIllusionBlock(typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} block illusions.'", + first_found->GetCleanName(), + first_found->GetIllusionBlock() ? "will now" : "will no longer" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots {} block illusions.", + success_count, + typeValue ? "will now" : "will no longer" + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/inventory.cpp b/zone/bot_commands/inventory.cpp index b934f96a2..7371475ed 100644 --- a/zone/bot_commands/inventory.cpp +++ b/zone/bot_commands/inventory.cpp @@ -217,9 +217,18 @@ void bot_command_inventory_remove(Client* c, const Seperator* sep) } const auto* itm = inst->GetItem(); + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemInst); + linker.SetItemInst(inst); if (inst && itm && c->CheckLoreConflict(itm)) { - c->MessageString(Chat::White, PICK_LORE); + c->Message( + Chat::White, + fmt::format( + "You cannot pick up {} because it is a lore item you already possess.", + linker.GenerateLink() + ).c_str() + ); return; } @@ -233,7 +242,13 @@ void bot_command_inventory_remove(Client* c, const Seperator* sep) continue; } - c->MessageString(Chat::White, PICK_LORE); + c->Message( + Chat::White, + fmt::format( + "You cannot pick up {} because it is a lore item you already possess.", + linker.GenerateLink() + ).c_str() + ); return; } @@ -247,7 +262,7 @@ void bot_command_inventory_remove(Client* c, const Seperator* sep) slot_id == EQ::invslot::slotRange || slot_id == EQ::invslot::slotAmmo ) { - my_bot->SetBotArcherySetting(false, true); + my_bot->SetBotRangedSetting(false); } my_bot->RemoveBotItemBySlot(slot_id); diff --git a/zone/bot_commands/max_melee_range.cpp b/zone/bot_commands/max_melee_range.cpp new file mode 100644 index 000000000..e876a9759 --- /dev/null +++ b/zone/bot_commands/max_melee_range.cpp @@ -0,0 +1,172 @@ +#include "../bot_command.h" + +void bot_command_max_melee_range(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_max_melee_range", sep->arg[0], "maxmeleerange")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Toggles whether or not bots will stay at max melee range during combat." + }; + + std::vector notes = { }; + + std::vector example_format = + { + fmt::format( + "{} [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set BotA to stay at max melee range:", + fmt::format( + "{} 1 byname BotA", + sep->arg[0] + ) + }; + std::vector examples_two = { }; + std::vector examples_three = + { + "To check the max melee range status for all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + int ab_arg = 1; + bool current_check = false; + uint32 typeValue = 0; + + if (sep->IsNumber(1)) { + typeValue = atoi(sep->arg[1]); + ++ab_arg; + if (typeValue < 0 || typeValue > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} stay at max melee range.'", + my_bot->GetCleanName(), + my_bot->GetMaxMeleeRange() ? "will" : "will not" + ).c_str() + ); + } + else { + my_bot->SetMaxMeleeRange(typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} stay at max melee range.'", + first_found->GetCleanName(), + first_found->GetMaxMeleeRange() ? "will now" : "will no longer" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots {} stay at max melee range.", + success_count, + typeValue ? "will now" : "will no longer" + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/mesmerize.cpp b/zone/bot_commands/mesmerize.cpp index 532a3c404..1f51a70d3 100644 --- a/zone/bot_commands/mesmerize.cpp +++ b/zone/bot_commands/mesmerize.cpp @@ -2,42 +2,40 @@ void bot_command_mesmerize(Client *c, const Seperator *sep) { - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Mesmerize]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Mesmerize) || helper_command_alias_fail(c, "bot_command_mesmerize", sep->arg[0], "mesmerize")) - return; if (helper_is_help_or_usage(sep->arg[1])) { c->Message(Chat::White, "usage: %s", sep->arg[0]); helper_send_usage_required_bots(c, BCEnum::SpT_Mesmerize); return; } - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; + bool isSuccess = false; std::list sbl; MyBots::PopulateSBL_BySpawnedBots(c, sbl); - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; + for (auto bot_iter : sbl) { + std::list botSpellList = bot_iter->GetPrioritizedBotSpellsBySpellType(bot_iter, BotSpellTypes::Mez, c->GetTarget(), IsAEBotSpellType(BotSpellTypes::Mez)); - auto target_mob = actionable_targets.Select(c, local_entry->target_type, ENEMY); - if (!target_mob) - continue; + for (const auto& s : botSpellList) { + if (!IsValidSpell(s.SpellId)) { + continue; + } - if (spells[local_entry->spell_id].max_value[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) - continue; + if (!bot_iter->IsInGroupOrRaid()) { + continue; + } - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; + if (!bot_iter->CastChecks(s.SpellId, c->GetTarget(), BotSpellTypes::Mez, false, false)) { + continue; + } - uint32 dont_root_before = 0; - if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before)) - target_mob->SetDontRootMeBefore(dont_root_before); - - break; + if (bot_iter->CommandedDoSpellCast(s.SpellIndex, c->GetTarget(), s.ManaCost)) { + bot_iter->BotGroupSay(bot_iter, "Casting %s [%s] on %s.", GetSpellName(s.SpellId), bot_iter->GetSpellTypeNameByID(BotSpellTypes::Mez), c->GetTarget()->GetCleanName()); + isSuccess = true; + } + } } - helper_no_available_bots(c, my_bot); + if (!isSuccess) { + helper_no_available_bots(c); + } } diff --git a/zone/bot_commands/pet.cpp b/zone/bot_commands/pet.cpp index f6c6ae21d..c8b48368c 100644 --- a/zone/bot_commands/pet.cpp +++ b/zone/bot_commands/pet.cpp @@ -19,7 +19,7 @@ void bot_command_pet_get_lost(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_command_pet_get_lost", sep->arg[0], "petgetlost")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } int ab_mask = ActionableBots::ABM_NoFilter; @@ -98,7 +98,8 @@ void bot_command_pet_set_type(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_command_pet_set_type", sep->arg[0], "petsettype")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [type: water | fire | air | earth | monster] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s [type: auto | water | fire | air | earth | monster | epic] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "if set to 'auto', bots will choose their own pet type"); c->Message(Chat::White, "requires one of the following bot classes:"); c->Message(Chat::White, "Magician(1)"); return; @@ -109,29 +110,41 @@ void bot_command_pet_set_type(Client *c, const Seperator *sep) uint8 pet_type = 255; uint8 level_req = 255; - if (!pet_arg.compare("water")) { + if (!pet_arg.compare("auto")) { pet_type = 0; level_req = 1; } - else if (!pet_arg.compare("fire")) { + else if (!pet_arg.compare("water")) { pet_type = 1; + level_req = 1; + } + else if (!pet_arg.compare("fire")) { + pet_type = 2; level_req = 3; } else if (!pet_arg.compare("air")) { - pet_type = 2; + pet_type = 3; level_req = 4; } else if (!pet_arg.compare("earth")) { - pet_type = 3; + pet_type = 4; level_req = 5; } else if (!pet_arg.compare("monster")) { - pet_type = 4; + pet_type = 5; level_req = 30; } + else if (!pet_arg.compare("epic")) { + pet_type = 6; + if (!RuleB(Bots, AllowMagicianEpicPet)) { + c->Message(Chat::Yellow, "Epic pets are currently disabled for bots."); + return; + } + level_req = RuleI(Bots,AllowMagicianEpicPetLevel); + } if (pet_type == 255) { - c->Message(Chat::White, "You must specify a pet [type: water | fire | air | earth | monster]"); + c->Message(Chat::White, "You must specify a pet [type: auto | water | fire | air | earth | monster | epic]"); return; } @@ -157,7 +170,23 @@ void bot_command_pet_set_type(Client *c, const Seperator *sep) if (!bot_iter) continue; - bot_iter->SetPetChooser(true); + if (RuleI(Bots, RequiredMagicianEpicPetItemID) > 0) { + bool has_item = bot_iter->HasBotItem(RuleI(Bots, RequiredMagicianEpicPetItemID)) != INVALID_INDEX; + + if (!has_item) { + c->Message( + Chat::Say, + fmt::format( + "{} says, 'I require {} to cast an epic pet which I do not currently possess.'", + bot_iter->GetCleanName(), + (database.GetItem(RuleI(Bots, RequiredMagicianEpicPetItemID)) ? database.CreateItemLink(RuleI(Bots, RequiredMagicianEpicPetItemID)) : "an item") + ).c_str() + ); + + continue; + } + } + bot_iter->SetPetChooserID(pet_type); if (bot_iter->GetPet()) { auto pet_id = bot_iter->GetPetID(); diff --git a/zone/bot_commands/pull.cpp b/zone/bot_commands/pull.cpp index 6265d141e..fa222fcfa 100644 --- a/zone/bot_commands/pull.cpp +++ b/zone/bot_commands/pull.cpp @@ -7,15 +7,27 @@ void bot_command_pull(Client *c, const Seperator *sep) } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } - int ab_mask = ActionableBots::ABM_OwnerGroup; // existing behavior - need to add c->IsGrouped() check and modify code if different behavior is desired + + const int ab_mask = ActionableBots::ABM_Type1; + + std::string arg1 = sep->arg[1]; + int ab_arg = 1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } std::list sbl; - if (ActionableBots::PopulateSBL(c, "ownergroup", sbl, ab_mask) == ActionableBots::ABT_None) { + + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } + sbl.remove(nullptr); auto target_mob = ActionableTarget::VerifyEnemy(c, BCEnum::TT_Single); diff --git a/zone/bot_commands/release.cpp b/zone/bot_commands/release.cpp index 1872d43d6..bf8741d33 100644 --- a/zone/bot_commands/release.cpp +++ b/zone/bot_commands/release.cpp @@ -5,7 +5,7 @@ void bot_command_release(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_command_release", sep->arg[0], "release")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: ] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } const int ab_mask = ActionableBots::ABM_NoFilter; diff --git a/zone/bot_commands/sit_hp_percent.cpp b/zone/bot_commands/sit_hp_percent.cpp new file mode 100644 index 000000000..ac94a13fd --- /dev/null +++ b/zone/bot_commands/sit_hp_percent.cpp @@ -0,0 +1,172 @@ +#include "../bot_command.h" + +void bot_command_sit_hp_percent(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_sit_hp_percent", sep->arg[0], "sithppercent")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "HP % threshold when bots will sit in combat if allowed." + }; + + std::vector notes = { }; + + std::vector example_format = + { + fmt::format( + "{} [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set Clerics to sit at 45% HP:", + fmt::format( + "{} 45 byclass 2", + sep->arg[0] + ) + }; + std::vector examples_two = { }; + std::vector examples_three = + { + "To check the HP threshold for all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + int ab_arg = 1; + bool current_check = false; + uint32 typeValue = 0; + + if (sep->IsNumber(1)) { + typeValue = atoi(sep->arg[1]); + ++ab_arg; + if (typeValue < 0 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of health)."); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I sit in combat whem at or below [{}%%] HP.'", + my_bot->GetCleanName(), + my_bot->GetHPWhenToMed() + ).c_str() + ); + } + else { + my_bot->SetHPWhenToMed(typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I will now sit in combat whem at or below [{}%%] HP.'", + first_found->GetCleanName(), + first_found->GetHPWhenToMed() + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots will now sit in combat whem at or below [{}%%] HP.'", + success_count, + typeValue + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/sit_in_combat.cpp b/zone/bot_commands/sit_in_combat.cpp new file mode 100644 index 000000000..ffcf4d887 --- /dev/null +++ b/zone/bot_commands/sit_in_combat.cpp @@ -0,0 +1,172 @@ +#include "../bot_command.h" + +void bot_command_sit_in_combat(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_sit_in_combat", sep->arg[0], "sitincombat")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Toggles whether or not bots will sit in combat to heal or med." + }; + + std::vector notes = { }; + + std::vector example_format = + { + fmt::format( + "{} [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set Clerics to sit in combat:", + fmt::format( + "{} 1 byclass 2", + sep->arg[0] + ) + }; + std::vector examples_two = { }; + std::vector examples_three = + { + "To check the sit in combat state for all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + int ab_arg = 1; + bool current_check = false; + uint32 typeValue = 0; + + if (sep->IsNumber(1)) { + typeValue = atoi(sep->arg[1]); + ++ab_arg; + if (typeValue < 0 || typeValue > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} sit in combat.'", + my_bot->GetCleanName(), + my_bot->GetMedInCombat() ? "will" : "will not" + ).c_str() + ); + } + else { + my_bot->SetMedInCombat(typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} sit in combat.'", + first_found->GetCleanName(), + first_found->GetMedInCombat() ? "will now" : "will no longer" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots {} sit in combat.", + success_count, + typeValue ? "will now" : "will no longer" + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/sit_mana_percent.cpp b/zone/bot_commands/sit_mana_percent.cpp new file mode 100644 index 000000000..85afc8047 --- /dev/null +++ b/zone/bot_commands/sit_mana_percent.cpp @@ -0,0 +1,172 @@ +#include "../bot_command.h" + +void bot_command_sit_mana_percent(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_sit_mana_percent", sep->arg[0], "sitmanapercent")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Mana % threshold when bots will sit in combat if allowed." + }; + + std::vector notes = { }; + + std::vector example_format = + { + fmt::format( + "{} [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set Clerics to sit at 45% mana:", + fmt::format( + "{} 45 byclass 2", + sep->arg[0] + ) + }; + std::vector examples_two = { }; + std::vector examples_three = + { + "To check the mana threshold for all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + int ab_arg = 1; + bool current_check = false; + uint32 typeValue = 0; + + if (sep->IsNumber(1)) { + typeValue = atoi(sep->arg[1]); + ++ab_arg; + if (typeValue < 0 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of health)."); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I sit in combat whem at or below [{}%%] mana.'", + my_bot->GetCleanName(), + my_bot->GetManaWhenToMed() + ).c_str() + ); + } + else { + my_bot->SetManaWhenToMed(typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I will now sit in combat whem at or below [{}%%] mana.'", + first_found->GetCleanName(), + first_found->GetManaWhenToMed() + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots will now sit in combat whem at or below [{}%%] mana.'", + success_count, + typeValue + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell.cpp b/zone/bot_commands/spell.cpp index 45d9ec6eb..833c8ef7a 100644 --- a/zone/bot_commands/spell.cpp +++ b/zone/bot_commands/spell.cpp @@ -503,7 +503,7 @@ void bot_spell_info_dialogue_window(Client* c, const Seperator *sep) auto results = database.QueryDatabase( fmt::format( "SELECT value FROM db_str WHERE id = {} and type = 6 LIMIT 1", - spells[spell_id].effect_description_id + spells[spell_id].description_id ) ); @@ -557,7 +557,7 @@ void bot_command_enforce_spell_list(Client* c, const Seperator *sep) } bool enforce_state = (sep->argnum > 0) ? Strings::ToBool(sep->arg[1]) : !my_bot->GetBotEnforceSpellSetting(); - my_bot->SetBotEnforceSpellSetting(enforce_state, true); + my_bot->SetBotEnforceSpellSetting(enforce_state); c->Message( Chat::White, diff --git a/zone/bot_commands/spell_aggro_checks.cpp b/zone/bot_commands/spell_aggro_checks.cpp new file mode 100644 index 000000000..e9ae709ee --- /dev/null +++ b/zone/bot_commands/spell_aggro_checks.cpp @@ -0,0 +1,236 @@ +#include "../bot_command.h" + +void bot_command_spell_aggro_checks(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_aggro_checks", sep->arg[0], "spellaggrochecks")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Toggles whether or not bots will cast a spell type if they think it will get them aggro" + }; + + std::vector notes = { }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [value] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all bots to check aggro on nukes:", + fmt::format( + "{} {} 1 spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ), + fmt::format( + "{} {} 1 spawned", + sep->arg[0], + BotSpellTypes::Nuke + ) + }; + std::vector examples_two = + { + "To set Shadowknights to ignore aggro checks on snares:", + fmt::format( + "{} {} 0 byclass 5", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} {} 0 byclass 5", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + std::vector examples_three = + { + "To check the current DoT aggro check on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::DOT) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::DOT + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] aggro check is currently [{}].'", + my_bot->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + my_bot->GetSpellTypeAggroCheck(spellType) ? "enabled" : "disabled" + ).c_str() + ); + } + else { + my_bot->SetSpellTypeAggroCheck(spellType, typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] aggro check was [{}].'", + first_found->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + first_found->GetSpellTypeAggroCheck(spellType) ? "enabled" : "disabled" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots [{}] their [{}] aggro check.", + success_count, + c->GetSpellTypeNameByID(spellType), + typeValue ? "enabled" : "disabled" + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_delays.cpp b/zone/bot_commands/spell_delays.cpp new file mode 100644 index 000000000..3c3b153ca --- /dev/null +++ b/zone/bot_commands/spell_delays.cpp @@ -0,0 +1,242 @@ +#include "../bot_command.h" + +void bot_command_spell_delays(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_delays", sep->arg[0], "spelldelays")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Controls how long a bot will wait between casts of different spell types" + }; + + std::vector notes = + { + "- All pet types are based off the pet's owner's setting", + "- Any remaining types use the owner's setting when a pet is the target", + "- All Heals, Cures, Buffs (DS and resists included) are based off the target's setting, not the caster", + "- e.g., BotA is healing BotB using BotB's settings", + }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [value] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all Necromancers to an 8s DoT delay:", + fmt::format( + "{} {} 8000 byclass 11", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::DOT) + ), + fmt::format( + "{} {} 8000 byclass 11", + sep->arg[0], + BotSpellTypes::DOT + ) + }; + std::vector examples_two = + { + "To set all Warriors to receive Fast Heals every 2.5s:", + fmt::format( + "{} {} 2500 byclass 1", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::FastHeals) + ), + fmt::format( + "{} {} 2500 byclass 1", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + std::vector examples_three = + { + "To check the current Nuke delay on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Nuke + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid targetgroup, namesgroup, healrotationtargets mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 1 || typeValue > 60000) { + c->Message(Chat::Yellow, "You must enter a value between 1-60000 (1ms to 60s)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] spell delay is currently [{}] seconds.'", + my_bot->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + my_bot->GetSpellDelay(spellType) / 1000.00 + ).c_str() + ); + } + else { + my_bot->SetSpellDelay(spellType, typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] spell delay was set to [{}] seconds.'", + first_found->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + first_found->GetSpellDelay(spellType) / 1000.00 + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] spell delay to [{}] seconds.", + success_count, + c->GetSpellTypeNameByID(spellType), + typeValue / 1000.00 + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_engaged_priority.cpp b/zone/bot_commands/spell_engaged_priority.cpp new file mode 100644 index 000000000..03c151c3a --- /dev/null +++ b/zone/bot_commands/spell_engaged_priority.cpp @@ -0,0 +1,240 @@ +#include "../bot_command.h" + +void bot_command_spell_engaged_priority(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_engaged_priority", sep->arg[0], "spellengagedpriority")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Sets the order of spell casts when engaged in combat by spell type" + }; + + std::vector notes = + { + "-Setting a spell type to 0 will prevent that type from being cast.", + "-If 2 or more are set to the same priority they will sort by spell type ID." + }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [value] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all Shaman to cast slows first:", + fmt::format( + "{} {} 1 byclass 10", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Slow) + ), + fmt::format( + "{} {} 1 byclass 10", + sep->arg[0], + BotSpellTypes::Slow + ) + }; + std::vector examples_two = + { + "To set all bots to not cast snares:", + fmt::format( + "{} {} 0 spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} {} 0 spawned", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + std::vector examples_three = + { + "To check the current engaged priority of dispels on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Dispel) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Dispel + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] engaged cast priority is currently [{}].'", + my_bot->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + my_bot->GetSpellTypePriority(spellType, BotPriorityCategories::Engaged) + ).c_str() + ); + } + else { + my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Engaged, typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] engaged cast priority was set to [{}].'", + first_found->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + first_found->GetSpellTypePriority(spellType, BotPriorityCategories::Engaged) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] engaged cast priority to [{}].", + success_count, + c->GetSpellTypeNameByID(spellType), + typeValue + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_holds.cpp b/zone/bot_commands/spell_holds.cpp new file mode 100644 index 000000000..effd97a7a --- /dev/null +++ b/zone/bot_commands/spell_holds.cpp @@ -0,0 +1,229 @@ +#include "../bot_command.h" + +void bot_command_spell_holds(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_holds", sep->arg[0], "spellholds")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Toggles whether or not bots can cast or receive certain spell types" + }; + + std::vector notes = + { + "- All pet types are based off the pet's owner's setting", + "- Any remaining types use the owner's setting when a pet is the target", + "- All Heals, Cures, Buffs (DS and resists included) are based off the target's setting, not the caster", + "- e.g., BotA is healing BotB using BotB's settings", + }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [value] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all bots to hold DoTs:", + fmt::format( + "{} {} 1 spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::DOT) + ), + fmt::format( + "{} {} 1 spawned", + sep->arg[0], + BotSpellTypes::DOT + ) + }; + std::vector examples_two = + { + "To check the current DoT settings on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::DOT) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::DOT + ) + }; + std::vector examples_three = { }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] spell hold is currently [{}].'", + my_bot->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + my_bot->GetSpellHold(spellType) ? "enabled" : "disabled" + ).c_str() + ); + } + else { + my_bot->SetSpellHold(spellType, typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] spell hold was [{}].'", + first_found->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + first_found->GetSpellHold(spellType) ? "enabled" : "disabled" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots [{}] their [{}] spell hold.", + success_count, + typeValue ? "enabled" : "disabled", + c->GetSpellTypeNameByID(spellType) + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_idle_priority.cpp b/zone/bot_commands/spell_idle_priority.cpp new file mode 100644 index 000000000..9bb95cf46 --- /dev/null +++ b/zone/bot_commands/spell_idle_priority.cpp @@ -0,0 +1,240 @@ +#include "../bot_command.h" + +void bot_command_spell_idle_priority(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_idle_priority", sep->arg[0], "spellidlepriority")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Sets the order of spell casts when not in combat by spell type" + }; + + std::vector notes = + { + "-Setting a spell type to 0 will prevent that type from being cast.", + "-If 2 or more are set to the same priority they will sort by spell type ID." + }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [value] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all Clerics to cast fast heals third:", + fmt::format( + "{} {} 3 byclass 2", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::FastHeals) + ), + fmt::format( + "{} {} 3 byclass 2", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + std::vector examples_two = + { + "To set all bots to not cast cures:", + fmt::format( + "{} {} 0 spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Cure) + ), + fmt::format( + "{} {} 0 spawned", + sep->arg[0], + BotSpellTypes::Cure + ) + }; + std::vector examples_three = + { + "To check the current idle priority of buffs on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Buff) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Buff + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] idle cast priority is currently [{}].'", + my_bot->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + my_bot->GetSpellTypePriority(spellType, BotPriorityCategories::Idle) + ).c_str() + ); + } + else { + my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Idle, typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] idle cast priority was set to [{}].'", + first_found->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + first_found->GetSpellTypePriority(spellType, BotPriorityCategories::Idle) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] idle cast priority to [{}].", + success_count, + c->GetSpellTypeNameByID(spellType), + typeValue + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_max_hp_pct.cpp b/zone/bot_commands/spell_max_hp_pct.cpp new file mode 100644 index 000000000..9d94fd722 --- /dev/null +++ b/zone/bot_commands/spell_max_hp_pct.cpp @@ -0,0 +1,236 @@ +#include "../bot_command.h" + +void bot_command_spell_max_hp_pct(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_max_hp_pct", sep->arg[0], "spellmaxhppct")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Controls at what health percentage a bot will start casting different spell types" + }; + + std::vector notes = { }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [value] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all bots to start snaring when their health is at or below 100% HP:", + fmt::format( + "{} {} 100 spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + std::vector examples_two = + { + "To set BotA to start casting fast heals at 30% HP:", + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::FastHeals) + ), + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + std::vector examples_three = + { + "To check the current Stun max HP percent on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Stun) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Stun + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of health)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] maximum HP is currently [{}%%].'", + my_bot->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + my_bot->GetSpellTypeMaxHPLimit(spellType) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeMaxHPLimit(spellType, typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] maximum HP was set to [{}%%].'", + first_found->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + first_found->GetSpellTypeMaxHPLimit(spellType) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] maximum HP to [{}%%].", + success_count, + c->GetSpellTypeNameByID(spellType), + typeValue + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_max_mana_pct.cpp b/zone/bot_commands/spell_max_mana_pct.cpp new file mode 100644 index 000000000..9e22617b0 --- /dev/null +++ b/zone/bot_commands/spell_max_mana_pct.cpp @@ -0,0 +1,236 @@ +#include "../bot_command.h" + +void bot_command_spell_max_mana_pct(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_max_mana_pct", sep->arg[0], "spellmaxmanapct")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Controls at what mana percentage a bot will stop casting different spell types" + }; + + std::vector notes = { }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [value] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all bots to start snaring when their mana is at or below 100% HP:", + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + std::vector examples_two = + { + "To set BotA to start casting fast heals at 30% mana:", + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::FastHeals) + ), + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + std::vector examples_three = + { + "To check the current Stun max mana percent on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Stun) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Stun + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of mana)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] maximum mana is currently [{}%%].'", + my_bot->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + my_bot->GetSpellTypeMaxManaLimit(spellType) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeMaxManaLimit(spellType, typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] maximum mana was set to [{}%%].'", + first_found->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + first_found->GetSpellTypeMaxManaLimit(spellType) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] maximum mana to [{}%%].", + success_count, + c->GetSpellTypeNameByID(spellType), + typeValue + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_max_thresholds.cpp b/zone/bot_commands/spell_max_thresholds.cpp new file mode 100644 index 000000000..8ded61c1f --- /dev/null +++ b/zone/bot_commands/spell_max_thresholds.cpp @@ -0,0 +1,243 @@ +#include "../bot_command.h" + +void bot_command_spell_max_thresholds(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_max_thresholds", sep->arg[0], "spellmaxthresholds")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Controls at what target HP % the bot will start casting different spell types" + }; + + std::vector notes = + { + "- All pet types are based off the pet's owner's setting", + "- Any remaining types use the owner's setting when a pet is the target", + "- All Heals, Cures, Buffs (DS and resists included)", + "are based off the target's setting, not the caster", + "- e.g., BotA is healing BotB using BotB's settings", + }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [value] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all bots to start snaring at 99%:", + fmt::format( + "{} {} 99 spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} {} 99 spawned", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + std::vector examples_two = + { + "To set bot Enchbot to start casting Debuffs at 99%:", + fmt::format( + "{} {} 99 byname Enchbot", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Debuff) + ), + fmt::format( + "{} {} 99 byname Enchbot", + sep->arg[0], + BotSpellTypes::Debuff + ) + }; + std::vector examples_three = + { + "To check the current Nuke max threshold on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Nuke + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of health)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] maximum threshold is currently [{}%%].'", + my_bot->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + my_bot->GetSpellMaxThreshold(spellType) + ).c_str() + ); + } + else { + my_bot->SetSpellMaxThreshold(spellType, typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] maximum threshold was set to [{}%%].'", + first_found->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + first_found->GetSpellMaxThreshold(spellType) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] maximum threshold to [{}%%].", + success_count, + c->GetSpellTypeNameByID(spellType), + typeValue + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_min_hp_pct.cpp b/zone/bot_commands/spell_min_hp_pct.cpp new file mode 100644 index 000000000..739ab0d1c --- /dev/null +++ b/zone/bot_commands/spell_min_hp_pct.cpp @@ -0,0 +1,236 @@ +#include "../bot_command.h" + +void bot_command_spell_min_hp_pct(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_min_hp_pct", sep->arg[0], "spellminhppct")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Controls at what health percentage a bot will stop casting different spell types" + }; + + std::vector notes = { }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [value] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all bots to stop snaring when their health is below 10% HP:", + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + std::vector examples_two = + { + "To set BotA to stop casting fast heals at 30% HP:", + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::FastHeals) + ), + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + std::vector examples_three = + { + "To check the current Stun min HP percent on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Stun) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Stun + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of mana)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] minimum HP is currently [{}%%].'", + my_bot->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + my_bot->GetSpellTypeMinHPLimit(spellType) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeMinHPLimit(spellType, typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] minimum HP was set to [{}%%].'", + first_found->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + first_found->GetSpellTypeMinHPLimit(spellType) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] minimum HP to [{}%%].", + success_count, + c->GetSpellTypeNameByID(spellType), + typeValue + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_min_mana_pct.cpp b/zone/bot_commands/spell_min_mana_pct.cpp new file mode 100644 index 000000000..e83362f76 --- /dev/null +++ b/zone/bot_commands/spell_min_mana_pct.cpp @@ -0,0 +1,236 @@ +#include "../bot_command.h" + +void bot_command_spell_min_mana_pct(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_min_mana_pct", sep->arg[0], "spellminmanapct")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Controls at what mana percentage a bot will stop casting different spell types" + }; + + std::vector notes = { }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [value] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all bots to stop snaring when their mana is below 10% HP:", + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + std::vector examples_two = + { + "To set BotA to stop casting fast heals at 30% mana:", + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::FastHeals) + ), + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + std::vector examples_three = + { + "To check the current Stun min mana percent on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Stun) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Stun + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of mana)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] minimum mana is currently [{}%%].'", + my_bot->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + my_bot->GetSpellTypeMinManaLimit(spellType) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeMinManaLimit(spellType, typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] minimum mana was set to [{}%%].'", + first_found->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + first_found->GetSpellTypeMinManaLimit(spellType) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] minimum mana to [{}%%].", + success_count, + c->GetSpellTypeNameByID(spellType), + typeValue + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_min_thresholds.cpp b/zone/bot_commands/spell_min_thresholds.cpp new file mode 100644 index 000000000..6df8fc999 --- /dev/null +++ b/zone/bot_commands/spell_min_thresholds.cpp @@ -0,0 +1,244 @@ +#include "../bot_command.h" + +void bot_command_spell_min_thresholds(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_min_thresholds", sep->arg[0], "spellminthresholds")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Controls at what target HP % the bot will stop casting different spell types" + }; + + std::vector notes = + { + "- All pet types are based off the pet's owner's setting", + "- Any remaining types use the owner's setting when a pet is the target", + "- All Heals, Cures, Buffs (DS and resists included) are based off the target's setting, not the caster", + "- e.g., BotA is healing BotB using BotB's settings", + }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [value] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all bots to stop debuffing at 10%:", + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Debuff) + ), + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + BotSpellTypes::Debuff + ) + }; + std::vector examples_two = + { + "To set all Druids to stop casting DoTs at 15%:", + fmt::format( + "{} {} 15 byclass 6", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::DOT) + ), + fmt::format( + "{} {} 15 byclass 6", + sep->arg[0], + BotSpellTypes::DOT + ) + }; + std::vector examples_three = + { + "To check the current Fast Heal min threshold on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::FastHeals) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid", + "targetgroup, namesgroup, healrotationtargets", + "mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of health)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] minimum threshold is currently [{}%%].'", + my_bot->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + my_bot->GetSpellMinThreshold(spellType) + ).c_str() + ); + } + else { + my_bot->SetSpellMinThreshold(spellType, typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] minimum threshold was set to [{}%%].'", + first_found->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + first_found->GetSpellMinThreshold(spellType) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] minimum threshold to [{}%%].", + success_count, + c->GetSpellTypeNameByID(spellType), + typeValue + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_pursue_priority.cpp b/zone/bot_commands/spell_pursue_priority.cpp new file mode 100644 index 000000000..593606437 --- /dev/null +++ b/zone/bot_commands/spell_pursue_priority.cpp @@ -0,0 +1,240 @@ +#include "../bot_command.h" + +void bot_command_spell_pursue_priority(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_pursue_priority", sep->arg[0], "spellpursuepriority")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Sets the order of spell casts when the mob is fleeing in combat by spell type" + }; + + std::vector notes = + { + "- Setting a spell type to 0 will prevent that type from being cast.", + "- If 2 or more are set to the same priority they will sort by spell type ID." + }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [value] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all bots to cast nukes first:", + fmt::format( + "{} {} 1 spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ), + fmt::format( + "{} {} 1 spawned", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + std::vector examples_two = + { + "To set all Shaman to not cast cures:", + fmt::format( + "{} {} 0 byclass 10", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Cure) + ), + fmt::format( + "{} {} 0 byclass 10", + sep->arg[0], + BotSpellTypes::Cure + ) + }; + std::vector examples_three = + { + "To check the current pursue priority of buffs on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Buff) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Buff + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] pursue cast priority is currently [{}].'", + my_bot->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + my_bot->GetSpellTypePriority(spellType, BotPriorityCategories::Pursue) + ).c_str() + ); + } + else { + my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Pursue, typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] pursue cast priority was set to [{}].'", + first_found->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + first_found->GetSpellTypePriority(spellType, BotPriorityCategories::Pursue) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] pursue cast priority to [{}].", + success_count, + c->GetSpellTypeNameByID(spellType), + typeValue + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_target_count.cpp b/zone/bot_commands/spell_target_count.cpp new file mode 100644 index 000000000..f87e232ee --- /dev/null +++ b/zone/bot_commands/spell_target_count.cpp @@ -0,0 +1,236 @@ +#include "../bot_command.h" + +void bot_command_spell_target_count(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_target_count", sep->arg[0], "spelltargetcount")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Decides how many eligible targets are required for an AE or group spell to cast by spell type" + }; + + std::vector notes = { }; + + std::vector example_format = + { + fmt::format( + "{} [Type Shortname] [value] [actionable]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all bots to AEMez with 5 or more targets:", + fmt::format( + "{} {} 5 spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::AEMez) + ), + fmt::format( + "{} {} 5 spawned", + sep->arg[0], + BotSpellTypes::AEMez + ) + }; + std::vector examples_two = + { + "To set Wizards to require 5 targets for AENukes:", + fmt::format( + "{} {} 3 byname BotA", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::AENukes) + ), + fmt::format( + "{} {} 3 byname BotA", + sep->arg[0], + BotSpellTypes::AENukes + ) + }; + std::vector examples_three = + { + "To check the current AESlow count on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::AESlow) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::AESlow + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + + return; + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 1 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 1-100."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] target count is currently [{}].'", + my_bot->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + my_bot->GetSpellTypeAEOrGroupTargetCount(spellType) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeAEOrGroupTargetCount(spellType, typeValue); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] target count was set to [{}].'", + first_found->GetCleanName(), + c->GetSpellTypeNameByID(spellType), + first_found->GetSpellTypeAEOrGroupTargetCount(spellType) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] target count to [{}].", + success_count, + c->GetSpellTypeNameByID(spellType), + typeValue + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/suspend.cpp b/zone/bot_commands/suspend.cpp index e3198e79e..287db739c 100644 --- a/zone/bot_commands/suspend.cpp +++ b/zone/bot_commands/suspend.cpp @@ -6,7 +6,7 @@ void bot_command_suspend(Client *c, const Seperator *sep) return; } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: ] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } const int ab_mask = ActionableBots::ABM_NoFilter; diff --git a/zone/bot_commands/taunt.cpp b/zone/bot_commands/taunt.cpp index a15d29a5b..866e9899c 100644 --- a/zone/bot_commands/taunt.cpp +++ b/zone/bot_commands/taunt.cpp @@ -2,12 +2,15 @@ void bot_command_taunt(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_command_taunt", sep->arg[0], "taunt")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + if (helper_command_alias_fail(c, "bot_command_taunt", sep->arg[0], "taunt")) { return; } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_Type1; std::string arg1 = sep->arg[1]; @@ -15,26 +18,30 @@ void bot_command_taunt(Client *c, const Seperator *sep) bool taunt_state = false; bool toggle_taunt = true; int ab_arg = 1; + if (!arg1.compare("on")) { taunt_state = true; toggle_taunt = false; - ab_arg = 2; + ++ab_arg; } else if (!arg1.compare("off")) { toggle_taunt = false; - ab_arg = 2; + ++ab_arg; } std::string class_race_arg = sep->arg[ab_arg]; bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { class_race_check = true; } std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[(ab_arg + 1)] : nullptr, class_race_check ? atoi(sep->arg[(ab_arg + 1)]) : 0) == ActionableBots::ABT_None) { return; } + sbl.remove(nullptr); int taunting_count = 0; diff --git a/zone/bot_commands/timer.cpp b/zone/bot_commands/timer.cpp index 93eab687d..30b495d3a 100644 --- a/zone/bot_commands/timer.cpp +++ b/zone/bot_commands/timer.cpp @@ -2,10 +2,12 @@ void bot_command_timer(Client* c, const Seperator* sep) { - if (helper_command_alias_fail(c, "bot_command_timer", sep->arg[0], "timer")) + if (helper_command_alias_fail(c, "bot_command_timer", sep->arg[0], "timer")) { return; + } + if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [clear | has | set] [disc | item | spell] [timer ID | item ID | spell ID | all] [optional ms for set] [actionable].", sep->arg[0]); + c->Message(Chat::White, "usage: %s [clear | has | set] [disc | item | spell] [timer ID | item ID | spell ID | all] [optional ms for set] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name])).", sep->arg[0]); c->Message(Chat::White, "When setting, you can leave the value blank to use the default for the item or specify a value in ms to set the timer to."); c->Message(Chat::White, "Returns or sets the provided timer(s) for the selected bot(s) or clears the selected timer(s) for the selected bot(s)."); return; @@ -88,14 +90,17 @@ void bot_command_timer(Client* c, const Seperator* sep) std::string class_race_arg = sep->arg[ab_arg]; bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { class_race_check = true; } std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } + sbl.remove(nullptr); for (auto my_bot : sbl) { diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index a24cd0c30..20715d0d7 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -35,6 +35,7 @@ #include "../common/repositories/bot_pet_buffs_repository.h" #include "../common/repositories/bot_pet_inventories_repository.h" #include "../common/repositories/bot_spell_casting_chances_repository.h" +#include "../common/repositories/bot_settings_repository.h" #include "../common/repositories/bot_stances_repository.h" #include "../common/repositories/bot_timers_repository.h" #include "../common/repositories/character_data_repository.h" @@ -400,28 +401,13 @@ bool BotDatabase::LoadBot(const uint32 bot_id, Bot*& loaded_bot) e.spells_id, e.time_spawned, e.zone_id, - t, - e.expansion_bitmask + t ); if (loaded_bot) { loaded_bot->SetSurname(e.last_name); loaded_bot->SetTitle(e.title); loaded_bot->SetSuffix(e.suffix); - - loaded_bot->SetShowHelm(e.show_helm); - - auto bfd = EQ::Clamp(e.follow_distance, static_cast(1), BOT_FOLLOW_DISTANCE_DEFAULT_MAX); - - loaded_bot->SetFollowDistance(bfd); - - loaded_bot->SetStopMeleeLevel(e.stop_melee_level); - - loaded_bot->SetBotEnforceSpellSetting(e.enforce_spell_settings); - - loaded_bot->SetBotArcherySetting(e.archery_setting); - - loaded_bot->SetBotCasterRange(e.caster_range); } return true; @@ -476,13 +462,6 @@ bool BotDatabase::SaveNewBot(Bot* b, uint32& bot_id) e.poison = b->GetBasePR(); e.disease = b->GetBaseDR(); e.corruption = b->GetBaseCorrup(); - e.show_helm = b->GetShowHelm() ? 1 : 0; - e.follow_distance = b->GetFollowDistance(); - e.stop_melee_level = b->GetStopMeleeLevel(); - e.expansion_bitmask = b->GetExpansionBitmask(); - e.enforce_spell_settings = b->GetBotEnforceSpellSetting(); - e.archery_setting = b->IsBotArcher() ? 1 : 0; - e.caster_range = b->GetBotCasterRange(); e = BotDataRepository::InsertOne(database, e); @@ -547,12 +526,6 @@ bool BotDatabase::SaveBot(Bot* b) e.poison = b->GetBasePR(); e.disease = b->GetBaseDR(); e.corruption = b->GetBaseCorrup(); - e.show_helm = b->GetShowHelm() ? 1 : 0; - e.follow_distance = b->GetFollowDistance(); - e.stop_melee_level = b->GetStopMeleeLevel(); - e.expansion_bitmask = b->GetExpansionBitmask(); - e.enforce_spell_settings = b->GetBotEnforceSpellSetting(); - e.archery_setting = b->IsBotArcher() ? 1 : 0; return BotDataRepository::UpdateOne(database, e); } @@ -868,7 +841,7 @@ bool BotDatabase::SaveTimers(Bot* b) std::vector l; if (!v.empty()) { - for (auto & bot_timer : v) { + for (auto& bot_timer : v) { if (bot_timer.timer_value <= Timer::GetCurrentTime()) { continue; } @@ -1670,59 +1643,6 @@ bool BotDatabase::SaveAllArmorColors(const uint32 owner_id, const uint32 rgb_val return BotInventoriesRepository::SaveAllArmorColors(database, owner_id, rgb_value); } -bool BotDatabase::SaveHelmAppearance(const uint32 bot_id, const bool show_flag) -{ - if (!bot_id) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - e.show_helm = show_flag ? 1 : 0; - - return BotDataRepository::UpdateOne(database, e); -} - -bool BotDatabase::SaveAllHelmAppearances(const uint32 owner_id, const bool show_flag) -{ - if (!owner_id) { - return false; - } - - return BotDataRepository::SaveAllHelmAppearances(database, owner_id, show_flag); -} - -bool BotDatabase::ToggleAllHelmAppearances(const uint32 owner_id) -{ - if (!owner_id) { - return false; - } - - return BotDataRepository::ToggleAllHelmAppearances(database, owner_id); -} - -bool BotDatabase::SaveFollowDistance(const uint32 bot_id, const uint32 follow_distance) -{ - if (!bot_id || !follow_distance) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - e.follow_distance = follow_distance; - - return BotDataRepository::UpdateOne(database, e); -} - -bool BotDatabase::SaveAllFollowDistances(const uint32 owner_id, const uint32 follow_distance) -{ - if (!owner_id || !follow_distance) { - return false; - } - - return BotDataRepository::SaveAllFollowDistances(database, owner_id, follow_distance); -} - bool BotDatabase::CreateCloneBot(const uint32 bot_id, const std::string& clone_name, uint32& clone_id) { if (!bot_id || clone_name.empty()) { @@ -1771,19 +1691,6 @@ bool BotDatabase::CreateCloneBotInventory(const uint32 bot_id, const uint32 clon return BotInventoriesRepository::InsertMany(database, l); } -bool BotDatabase::SaveStopMeleeLevel(const uint32 bot_id, const uint8 sml_value) -{ - if (!bot_id) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - e.stop_melee_level = sml_value; - - return BotDataRepository::UpdateOne(database, e); -} - bool BotDatabase::LoadOwnerOptions(Client* c) { if (!c || !c->CharacterID()) { @@ -2224,74 +2131,6 @@ uint32 BotDatabase::GetRaceClassBitmask(uint32 bot_race) return e.race ? e.classes : 0; } -bool BotDatabase::SaveExpansionBitmask(const uint32 bot_id, const int expansion_bitmask) -{ - if (!bot_id) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - if (!e.bot_id) { - return false; - } - - e.expansion_bitmask = expansion_bitmask; - - return BotDataRepository::UpdateOne(database, e); -} - -bool BotDatabase::SaveEnforceSpellSetting(const uint32 bot_id, const bool enforce_spell_setting) -{ - if (!bot_id) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - if (!e.bot_id) { - return false; - } - - e.enforce_spell_settings = enforce_spell_setting ? 1 : 0; - - return BotDataRepository::UpdateOne(database, e); -} - -bool BotDatabase::SaveBotArcherSetting(const uint32 bot_id, const bool bot_archer_setting) -{ - if (!bot_id) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - if (!e.bot_id) { - return false; - } - - e.archery_setting = bot_archer_setting ? 1 : 0; - - return BotDataRepository::UpdateOne(database, e); -} - -bool BotDatabase::SaveBotCasterRange(const uint32 bot_id, const uint32 bot_caster_range_value) -{ - if (!bot_id) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - if (!e.bot_id) { - return false; - } - - e.caster_range = bot_caster_range_value; - - return BotDataRepository::UpdateOne(database, e); -} - const uint8 BotDatabase::GetBotClassByID(const uint32 bot_id) { const auto& e = BotDataRepository::FindOne(database, bot_id); @@ -2322,7 +2161,7 @@ std::vector BotDatabase::GetBotIDsByCharacterID(const uint32 character_i class_id ) : "" - ) + ) ) ); @@ -2360,3 +2199,177 @@ const int BotDatabase::GetBotExtraHasteByID(const uint32 bot_id) return e.bot_id ? e.extra_haste : 0; } + +bool BotDatabase::LoadBotSettings(Mob* m) +{ + if (!m) { + return false; + } + + if (!m->IsOfClientBot()) { + return false; + } + + uint32 mobID = (m->IsClient() ? m->CastToClient()->CharacterID() : m->CastToBot()->GetBotID()); + + std::string query = ""; + + if (m->IsClient()) { + query = fmt::format("`char_id` = {}", mobID); + } + else { + query = fmt::format("`bot_id` = {}", mobID); + } + + const auto& l = BotSettingsRepository::GetWhere(database, query); + + if (l.empty()) { + return true; + } + + for (const auto& e : l) { + LogBotSettings("[{}] says, 'Loading {} [{}] - setting to [{}]." + , m->GetCleanName() + , (e.setting_type == BotSettingCategories::BaseSetting ? m->CastToBot()->GetBotSettingCategoryName(e.setting_id) : m->CastToBot()->GetBotSpellCategoryName(e.setting_id)) + , e.setting_id + , e.value + ); //deleteme + + m->SetBotSetting(e.setting_type, e.setting_id, e.value); + } + + return true; +} + +bool BotDatabase::SaveBotSettings(Mob* m) +{ + if (!m) { + return false; + } + + if (!m->IsOfClientBot()) { + return false; + } + + uint32 botID = (m->IsClient() ? 0 : m->CastToBot()->GetBotID()); + uint32 charID = (m->IsClient() ? m->CastToClient()->CharacterID() : 0); + + std::string query = ""; + + if (m->IsClient()) { + query = fmt::format("`char_id` = {}", charID); + } + else { + query = fmt::format("`bot_id` = {}", botID); + } + + BotSettingsRepository::DeleteWhere(database, query); + + std::vector v; + + if (m->IsBot()) { + for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { + if (m->CastToBot()->GetBotBaseSetting(i) != m->CastToBot()->GetDefaultBotBaseSetting(i)) { + auto e = BotSettingsRepository::BotSettings{ + .char_id = charID, + .bot_id = botID, + .setting_id = static_cast(i), + .setting_type = static_cast(BotSettingCategories::BaseSetting), + .value = static_cast(m->CastToBot()->GetBotBaseSetting(i)), + .category_name = m->CastToBot()->GetBotSpellCategoryName(BotSettingCategories::BaseSetting), + .setting_name = m->CastToBot()->GetBotSettingCategoryName(i) + }; + + v.emplace_back(e); + + LogBotSettings("{} says, 'Saving {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), m->CastToBot()->GetBotSettingCategoryName(i), i, e.value, m->CastToBot()->GetDefaultBotBaseSetting(i)); //deleteme + } + } + + for (uint16 i = BotSettingCategories::START_NO_BASE; i <= BotSettingCategories::END; ++i) { + for (uint16 x = BotSpellTypes::START; x <= BotSpellTypes::END; ++x) { + if (m->CastToBot()->GetSetting(i, x) != m->CastToBot()->GetDefaultSetting(i, x)) { + auto e = BotSettingsRepository::BotSettings{ + .char_id = charID, + .bot_id = botID, + .setting_id = static_cast(x), + .setting_type = static_cast(i), + .value = m->CastToBot()->GetSetting(i, x), + .category_name = m->CastToBot()->GetBotSpellCategoryName(i), + .setting_name = m->CastToBot()->GetSpellTypeNameByID(x) + }; + + v.emplace_back(e); + + LogBotSettings("{} says, 'Saving {} {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), m->CastToBot()->GetBotSpellCategoryName(i), m->GetSpellTypeNameByID(x), x, e.value, m->CastToBot()->GetDefaultSetting(i, x)); //deleteme + } + } + } + } + + if (m->IsClient()) { + if (m->CastToClient()->GetDefaultBotSettings(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock) != m->GetIllusionBlock()) { // Only illusion block supported + auto e = BotSettingsRepository::BotSettings{ + .char_id = charID, + .bot_id = botID, + .setting_id = static_cast(BotBaseSettings::IllusionBlock), + .setting_type = static_cast(BotSettingCategories::BaseSetting), + .value = m->GetIllusionBlock(), + .category_name = m->CastToBot()->GetBotSpellCategoryName(BotSettingCategories::BaseSetting), + .setting_name = m->CastToBot()->GetBotSettingCategoryName(BotBaseSettings::IllusionBlock) + }; + + v.emplace_back(e); + + LogBotSettings("{} says, 'Saving {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), m->CastToBot()->GetBotSettingCategoryName(BotBaseSettings::IllusionBlock), BotBaseSettings::IllusionBlock, e.value, m->GetIllusionBlock()); //deleteme + } + + for (uint16 i = BotSettingCategories::START_CLIENT; i <= BotSettingCategories::END_CLIENT; ++i) { + for (uint16 x = BotSpellTypes::START; x <= BotSpellTypes::END; ++x) { + LogBotSettings("{} says, 'Checking {} {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), m->CastToBot()->GetBotSpellCategoryName(i), m->CastToBot()->GetSpellTypeNameByID(x), x, m->CastToClient()->GetBotSetting(i, x), m->CastToClient()->GetDefaultBotSettings(i, x)); //deleteme + if (IsClientBotSpellType(x) && m->CastToClient()->GetBotSetting(i, x) != m->CastToClient()->GetDefaultBotSettings(i, x)) { + auto e = BotSettingsRepository::BotSettings{ + .char_id = charID, + .bot_id = botID, + .setting_id = static_cast(x), + .setting_type = static_cast(i), + .value = m->CastToClient()->GetBotSetting(i, x), + .category_name = m->CastToBot()->GetBotSpellCategoryName(i), + .setting_name = m->CastToBot()->GetSpellTypeNameByID(x) + }; + + v.emplace_back(e); + + LogBotSettings("{} says, 'Saving {} {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), m->CastToBot()->GetBotSpellCategoryName(i), m->CastToBot()->GetSpellTypeNameByID(x), x, e.value, m->CastToClient()->GetDefaultBotSettings(i, x)); //deleteme + } + } + } + } + + if (!v.empty()) { + const int inserted = BotSettingsRepository::ReplaceMany(database, v); + + if (!inserted) { + return false; + } + } + + return true; +} + +bool BotDatabase::DeleteBotSettings(const uint32 bot_id) +{ + if (!bot_id) { + return false; + } + + BotSettingsRepository::DeleteWhere( + database, + fmt::format( + "`bot_id` = {}", + bot_id + ) + ); + + return true; +} diff --git a/zone/bot_database.h b/zone/bot_database.h index 139ebc643..3539db5c6 100644 --- a/zone/bot_database.h +++ b/zone/bot_database.h @@ -88,10 +88,6 @@ public: bool SaveEquipmentColor(const uint32 bot_id, const int16 slot_id, const uint32 color); - bool SaveExpansionBitmask(const uint32 bot_id, const int expansion_bitmask); - bool SaveEnforceSpellSetting(const uint32 bot_id, const bool enforce_spell_setting); - - /* Bot pet functions */ bool LoadPetIndex(const uint32 bot_id, uint32& pet_index); bool LoadPetSpellID(const uint32 bot_id, uint32& pet_spell_id); @@ -120,26 +116,16 @@ public: bool SaveAllArmorColorBySlot(const uint32 owner_id, const int16 slot_id, const uint32 rgb_value); bool SaveAllArmorColors(const uint32 owner_id, const uint32 rgb_value); - bool SaveHelmAppearance(const uint32 bot_id, const bool show_flag = true); - bool SaveAllHelmAppearances(const uint32 owner_id, const bool show_flag = true); - - bool ToggleAllHelmAppearances(const uint32 owner_id); - - bool SaveFollowDistance(const uint32 bot_id, const uint32 follow_distance); - bool SaveAllFollowDistances(const uint32 owner_id, const uint32 follow_distance); - bool CreateCloneBot(const uint32 bot_id, const std::string& clone_name, uint32& clone_id); bool CreateCloneBotInventory(const uint32 bot_id, const uint32 clone_id); - bool SaveStopMeleeLevel(const uint32 bot_id, const uint8 sml_value); - - bool SaveBotArcherSetting(const uint32 bot_id, const bool bot_archer_setting); - bool LoadOwnerOptions(Client *owner); bool SaveOwnerOption(const uint32 owner_id, size_t type, const bool flag); bool SaveOwnerOption(const uint32 owner_id, const std::pair type, const std::pair flag); - bool SaveBotCasterRange(const uint32 bot_id, const uint32 bot_caster_range_value); + bool LoadBotSettings(Mob* m); + bool SaveBotSettings(Mob* m); + bool DeleteBotSettings(const uint32 bot_id); /* Bot group functions */ bool LoadGroupedBotsByGroupID(const uint32 owner_id, const uint32 group_id, std::list& group_list); @@ -210,12 +196,6 @@ public: static const char* SaveAllInspectMessages(); static const char* SaveAllArmorColorBySlot(); static const char* SaveAllArmorColors(); - static const char* SaveAllHelmAppearances(); - static const char* ToggleAllHelmAppearances(); - static const char* SaveFollowDistance(); - static const char* SaveAllFollowDistances(); - static const char* SaveStopMeleeLevel(); - static const char* SaveBotCasterRange(); /* fail::Bot heal rotation functions */ static const char* LoadHealRotation(); diff --git a/zone/bot_structs.h b/zone/bot_structs.h index 50e319e9e..0cfc5913e 100644 --- a/zone/bot_structs.h +++ b/zone/bot_structs.h @@ -92,4 +92,9 @@ struct BotTimer_Struct { uint32 item_id; }; +struct BotSpellTypeOrder { + uint16 spellType; + uint16 priority; +}; + #endif // BOT_STRUCTS diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 0050a58b2..ee3aaf3a3 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -21,1226 +21,528 @@ #include "../common/repositories/bot_spells_entries_repository.h" #include "../common/repositories/npc_spells_repository.h" -bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { - - // Bot AI - Raid* raid = entity_list.GetRaidByBotName(GetName()); - +bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType) { if (!tar) { return false; } - if (!AI_HasSpells()) { + LogBotPreChecksDetail("{} says, 'Attempting {} AICastSpell on {}.'", GetCleanName(), GetSpellTypeNameByID(spellType), tar->GetCleanName()); //deleteme + + if ( + !AI_HasSpells() || + (spellType == BotSpellTypes::Pet && tar != this) || + (IsPetBotSpellType(spellType) && !tar->IsPet()) || + (spellType == BotSpellTypes::Buff && tar->IsPet()) || + (spellType == BotSpellTypes::InCombatBuffSong && tar->IsPet()) || + (spellType == BotSpellTypes::OutOfCombatBuffSong && tar->IsPet()) || + (spellType == BotSpellTypes::PreCombatBuffSong && tar->IsPet()) || + (!RuleB(Bots, AllowBuffingHealingFamiliars) && tar->IsFamiliar()) || + (tar->IsPet() && tar->IsCharmed() && spellType == BotSpellTypes::PetBuffs && !RuleB(Bots, AllowCharmedPetBuffs)) || + (tar->IsPet() && tar->IsCharmed() && (spellType == BotSpellTypes::Cure || spellType == BotSpellTypes::GroupCures) && !RuleB(Bots, AllowCharmedPetCures)) || + (tar->IsPet() && tar->IsCharmed() && IsHealBotSpellType(spellType) && !RuleB(Bots, AllowCharmedPetHeals)) + ) { return false; } - + if (iChance < 100 && zone->random.Int(0, 100) > iChance) { return false; } - if (tar->GetAppearance() == eaDead && ((tar->IsClient() && !tar->CastToClient()->GetFeigned()) || tar->IsBot())) { - return false; + if (spellType != BotSpellTypes::Resurrect && tar->GetAppearance() == eaDead) { + if (!((tar->IsClient() && tar->CastToClient()->GetFeigned()) || tar->IsBot())) { + return false; + } } uint8 botClass = GetClass(); - uint8 botLevel = GetLevel(); - bool checked_los = false; //we do not check LOS until we are absolutely sure we need to, and we only do it once. + SetCastedSpellType(UINT16_MAX); BotSpell botSpell; botSpell.SpellId = 0; botSpell.SpellIndex = 0; botSpell.ManaCost = 0; - switch (iSpellTypes) { - case SpellType_Mez: - return BotCastMez(tar, botLevel, checked_los, botSpell, raid); - case SpellType_Heal: - return BotCastHeal(tar, botLevel, botClass, botSpell, raid); - case SpellType_Root: - return BotCastRoot(tar, botLevel, iSpellTypes, botSpell, checked_los); - case SpellType_Buff: - return BotCastBuff(tar, botLevel, botClass); - case SpellType_Escape: - return BotCastEscape(tar, botClass, botSpell, iSpellTypes); - case SpellType_Nuke: - return BotCastNuke(tar, botLevel, botClass, botSpell, checked_los); - case SpellType_Dispel: - return BotCastDispel(tar, botSpell, iSpellTypes, checked_los); - case SpellType_Pet: - return BotCastPet(tar, botClass, botSpell); - case SpellType_InCombatBuff: - return BotCastCombatBuff(tar, botLevel, botClass); - case SpellType_Lifetap: - return BotCastLifetap(tar, botLevel, botSpell, checked_los, iSpellTypes); - case SpellType_Snare: - return BotCastSnare(tar, botLevel, botSpell, checked_los, iSpellTypes); - case SpellType_DOT: - return BotCastDOT(tar, botLevel, botSpell, checked_los); - case SpellType_Slow: - return BotCastSlow(tar, botLevel, botClass, botSpell, checked_los, raid); - case SpellType_Debuff: - return BotCastDebuff(tar, botLevel, botSpell, checked_los); - case SpellType_Cure: - return BotCastCure(tar, botClass, botSpell, raid); - case SpellType_HateRedux: - return BotCastHateReduction(tar, botLevel, botSpell); - case SpellType_InCombatBuffSong: - return BotCastCombatSong(tar, botLevel); - case SpellType_OutOfCombatBuffSong: - return BotCastSong(tar, botLevel); - case SpellType_Resurrect: - case SpellType_PreCombatBuff: - case SpellType_PreCombatBuffSong: + if (SpellTypeRequiresLoS(spellType, botClass) && tar != this) { + SetHasLoS(DoLosChecks(this, tar)); + } + else { + SetHasLoS(true); + } + + switch (spellType) { + case BotSpellTypes::Slow: + if (tar->GetSpecialAbility(SpecialAbility::SlowImmunity)) { + return false; + } + + break; + case BotSpellTypes::Snare: + if (tar->GetSpecialAbility(SpecialAbility::SnareImmunity)) { + return false; + } + + break; + //SpecialAbility::PacifyImmunity -- TODO bot rewrite + case BotSpellTypes::Fear: + if (tar->GetSpecialAbility(SpecialAbility::FearImmunity)) { + return false; + } + + if (!IsCommandedSpell() && (tar->IsRooted() || tar->GetSnaredAmount() == -1)) { + return false; + } + + break; + case BotSpellTypes::Dispel: + if (tar->GetSpecialAbility(SpecialAbility::DispellImmunity)) { + return false; + } + + if (!IsCommandedSpell() && tar->CountDispellableBuffs() <= 0) { + return false; + } + + break; + case BotSpellTypes::HateRedux: + if (!IsCommandedSpell() && !GetNeedsHateRedux(tar)) { + return false; + } + + break; + case BotSpellTypes::InCombatBuff: + if (GetClass() == Class::ShadowKnight && (tar->IsOfClientBot() || (tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot()))) { + return false; + } + + if (!IsCommandedSpell() && GetClass() != Class::Shaman && spellType == BotSpellTypes::InCombatBuff && IsCasterClass(GetClass()) && GetLevel() >= GetStopMeleeLevel()) { + return false; + } + + break; + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::Buff: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::DamageShields: + case BotSpellTypes::ResistBuffs: + if (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) { + return false; + } + + break; + case BotSpellTypes::PreCombatBuffSong: + case BotSpellTypes::OutOfCombatBuffSong: + if (!IsCommandedSpell() && (IsEngaged() || tar->IsEngaged())) { // Out-of-Combat songs can not be cast in combat + return false; + } + + break; + case BotSpellTypes::AEMez: + case BotSpellTypes::Mez: + return BotCastMez(tar, botClass, botSpell, spellType); + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Nuke: + case BotSpellTypes::AEStun: + case BotSpellTypes::Stun: + return BotCastNuke(tar, botClass, botSpell, spellType); + case BotSpellTypes::RegularHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + if (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) { + return false; + } + + return BotCastHeal(tar, botClass, botSpell, spellType); + case BotSpellTypes::GroupCures: + case BotSpellTypes::Cure: + if (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) { + return false; + } + + return BotCastCure(tar, botClass, botSpell, spellType); + case BotSpellTypes::Pet: + if (HasPet() || IsBotCharmer()) { + return false; + } + + return BotCastPet(tar, botClass, botSpell, spellType); + case BotSpellTypes::Resurrect: + if (!tar->IsCorpse() || !tar->CastToCorpse()->IsPlayerCorpse()) { + return false; + } + + break; + case BotSpellTypes::Charm: + if (tar->IsCharmed() || !tar->IsNPC() || tar->GetSpecialAbility(SpecialAbility::CharmImmunity)) { + return false; + } + + break; default: - return false; + break; + } + + std::list botSpellList = GetPrioritizedBotSpellsBySpellType(this, spellType, tar, IsAEBotSpellType(spellType)); + + for (const auto& s : botSpellList) { + + if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) { + continue; + } + + if (IsInvulnerabilitySpell(s.SpellId)) { + tar = this; //target self for invul type spells + } + + if (IsCommandedSpell() && IsCasting()) { + BotGroupSay( + this, + fmt::format( + "Interrupting {}. I have been commanded to try to cast a [{}] spell, {} on {}.", + CastingSpellID() ? spells[CastingSpellID()].name : "my spell", + GetSpellTypeNameByID(spellType), + spells[s.SpellId].name, + tar->GetCleanName() + ).c_str() + ); + + InterruptSpell(); + } + + if (AIDoSpellCast(s.SpellIndex, tar, s.ManaCost)) { + if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType)) { + SetCastedSpellType(UINT16_MAX); + + if (!IsCommandedSpell()) { + SetBotSpellRecastTimer(spellType, tar, true); + } + } + else { + SetCastedSpellType(spellType); + } + + if (botClass != Class::Bard || RuleB(Bots, BardsAnnounceCasts)) { + BotGroupSay( + this, + fmt::format( + "Casting {} [{}] on {}.", + GetSpellName(s.SpellId), + GetSpellTypeNameByID(spellType), + (tar == this ? "myself" : tar->GetCleanName()) + ).c_str() + ); + } + + return true; + } } return false; } -bool Bot::BotCastSong(Mob* tar, uint8 botLevel) { - bool casted_spell = false; - if (GetClass() != Class::Bard || tar != this || IsEngaged()) // Out-of-Combat songs can not be cast in combat - return casted_spell; +bool Bot::BotCastMez(Mob* tar, uint8 botClass, BotSpell& botSpell, uint16 spellType) { + std::list botSpellList = GetPrioritizedBotSpellsBySpellType(this, spellType, tar, IsAEBotSpellType(spellType)); - for (auto botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_OutOfCombatBuffSong); - auto iter : botSongList) { - if (!iter.SpellId) - continue; - if (!CheckSpellRecastTimer(iter.SpellId)) - continue; - if (!IsSpellUsableInThisZoneType(iter.SpellId, zone->GetZoneType())) - continue; - switch (spells[iter.SpellId].target_type) { - case ST_AEBard: - case ST_AECaster: - case ST_GroupTeleport: - case ST_Group: - case ST_Self: - break; - default: + for (const auto& s : botSpellList) { + if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) { continue; } - if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) - continue; - casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); - if (casted_spell) - break; - } + if (!IsCommandedSpell()) { + Mob* addMob = GetFirstIncomingMobToMez(this, s.SpellId, spellType, IsAEBotSpellType(spellType)); - return casted_spell; -} - -bool Bot::BotCastCombatSong(Mob* tar, uint8 botLevel) { - bool casted_spell = false; - if (tar != this) { // In-Combat songs can be cast Out-of-Combat in preparation for battle - return casted_spell; - } - for (auto botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_InCombatBuffSong); - auto iter : botSongList) { - if (!iter.SpellId) - continue; - if (!CheckSpellRecastTimer(iter.SpellId)) - continue; - if (!IsSpellUsableInThisZoneType(iter.SpellId, zone->GetZoneType())) - continue; - switch (spells[iter.SpellId].target_type) { - case ST_AEBard: - case ST_AECaster: - case ST_GroupTeleport: - case ST_Group: - case ST_Self: - break; - default: - continue; - } - if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) - continue; - - casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); - if (casted_spell) - break; - } - - return casted_spell; -} - -bool Bot::BotCastHateReduction(Mob* tar, uint8 botLevel, const BotSpell& botSpell) { - bool casted_spell = false; - if (GetClass() == Class::Bard) { - std::list botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_HateRedux); - for (auto iter : botSongList) { - if (!iter.SpellId) - continue; - if (!CheckSpellRecastTimer(iter.SpellId)) - continue; - if (!IsSpellUsableInThisZoneType(iter.SpellId, zone->GetZoneType())) - continue; - if (spells[iter.SpellId].target_type != ST_Target) - continue; - if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) - continue; - - if (IsValidSpellRange(iter.SpellId, tar)) { - casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + if (!addMob) { + return false; } - if (casted_spell) { - BotGroupSay( - this, - fmt::format( - "Attempting to reduce hate on {} with {}.", - tar->GetCleanName(), - spells[iter.SpellId].name - ).c_str() - ); + + tar = addMob; + } + + if (AIDoSpellCast(s.SpellIndex, tar, s.ManaCost)) { + if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType)) { + SetCastedSpellType(UINT16_MAX); + + if (!IsCommandedSpell()) { + SetBotSpellRecastTimer(spellType, tar, true); + } } + else { + SetCastedSpellType(spellType); + } + + BotGroupSay( + this, + fmt::format( + "Casting {} [{}] on {}.", + GetSpellName(s.SpellId), + GetSpellTypeNameByID(spellType), + (tar == this ? "myself" : tar->GetCleanName()) + ).c_str() + ); + + return true; } } - return casted_spell; + return false; } -bool Bot::BotCastCure(Mob* tar, uint8 botClass, BotSpell& botSpell, Raid* raid) { - bool casted_spell = false; - if ( - GetNeedsCured(tar) && - (tar->DontCureMeBefore() < Timer::GetCurrentTime()) && - GetNumberNeedingHealedInGroup(25, false, raid) <= 0 && - GetNumberNeedingHealedInGroup(40, false, raid) <= 2 - ) { - botSpell = GetBestBotSpellForCure(this, tar); +bool Bot::BotCastCure(Mob* tar, uint8 botClass, BotSpell& botSpell, uint16 spellType) { + uint32 currentTime = Timer::GetCurrentTime(); + uint32 nextCureTime = tar->DontCureMeBefore(); - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; + if (!IsCommandedSpell()) { + if ((nextCureTime > currentTime) || !GetNeedsCured(tar)) { + return false; } + } - uint32 TempDontCureMeBeforeTime = tar->DontCureMeBefore(); + botSpell = GetBestBotSpellForCure(this, tar, spellType); - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontCureMeBeforeTime); + if (!IsValidSpellAndLoS(botSpell.SpellId, HasLoS())) { + return false; + } - if (casted_spell && botClass != Class::Bard) { + if (AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost)) { + if (botClass != Class::Bard || RuleB(Bots, BardsAnnounceCasts)) { if (IsGroupSpell(botSpell.SpellId)) { - if (HasGroup()) { - Group const* group = GetGroup(); - if (group) { - for (auto member : group->members) { - if ( - member && - !member->qglobal && - TempDontCureMeBeforeTime != tar->DontCureMeBefore() - ) { - member->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); - } - } - } - } else if (IsRaidGrouped()) { - uint32 r_group = raid->GetGroup(GetName()); - if (r_group) { - std::vector raid_group_members = raid->GetRaidGroupMembers(r_group); - for (auto& iter: raid_group_members) { - if ( - iter.member && - !iter.member->qglobal && - TempDontCureMeBeforeTime != tar->DontCureMeBefore() - ) { - iter.member->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); - } - } - } - } - } else if (TempDontCureMeBeforeTime != tar->DontCureMeBefore()) { - tar->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); - } - } - } - return casted_spell; -} - -bool Bot::BotCastDebuff(Mob* tar, uint8 botLevel, BotSpell& botSpell, bool checked_los) { - bool casted_spell = false; - if ((tar->GetHPRatio() <= 99.0f) && (tar->GetHPRatio() > 20.0f)) - { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - botSpell = GetBestBotSpellForResistDebuff(this, tar); - - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetDebuffBotSpell(this, tar); - } - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - - if (! - ( - !tar->IsImmuneToSpell(botSpell.SpellId, this) && - (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0) - ) - ) { - return casted_spell; - } - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - return casted_spell; -} - -bool Bot::BotCastSlow(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los, Raid* raid) { - bool casted_spell = false; - if (tar->GetHPRatio() <= 99.0f) { - - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - switch (botClass) { - case Class::Bard: { - // probably needs attackable check - for ( - auto botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_Slow); - auto iter : botSongList - ) { - - if (!iter.SpellId) { - continue; - } - - if (!CheckSpellRecastTimer(iter.SpellId)) { - continue; - } - - if (!IsSpellUsableInThisZoneType(iter.SpellId, zone->GetZoneType())) { - continue; - } - - if (spells[iter.SpellId].target_type != ST_Target) { - continue; - } - - if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) { - continue; - } - - if (IsValidSpellRange(iter.SpellId, tar)) { - casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); - } - - if (casted_spell) { - return casted_spell; - } - } - - break; - } - case Class::Enchanter: { - botSpell = GetBestBotSpellForMagicBasedSlow(this); - break; - } - case Class::Shaman: - case Class::Beastlord: { - botSpell = GetBestBotSpellForDiseaseBasedSlow(this); - - if (botSpell.SpellId == 0 || ((tar->GetMR() - 50) < (tar->GetDR() + spells[botSpell.SpellId].resist_difficulty))) - botSpell = GetBestBotSpellForMagicBasedSlow(this); - break; - } - } - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - - if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) { - return casted_spell; - } - if (IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - - if (casted_spell && GetClass() != Class::Bard) { - if (raid) { - const auto msg = fmt::format("Attempting to slow {}.", tar->GetCleanName()); - raid->RaidSay(msg.c_str(), GetCleanName(), Language::CommonTongue, Language::MaxValue); - } else { BotGroupSay( this, fmt::format( - "Attempting to slow {} with {}.", - tar->GetCleanName(), - spells[botSpell.SpellId].name + "Curing the group with {}.", + GetSpellName(botSpell.SpellId) ).c_str() ); - } - } - } - return casted_spell; -} -bool Bot::BotCastDOT(Mob* tar, uint8 botLevel, const BotSpell& botSpell, const bool& checked_los) { - bool casted_spell = false; + const std::vector v = GatherGroupSpellTargets(tar); - if ((tar->GetHPRatio() <= 98.0f) && (tar->DontDotMeBefore() < Timer::GetCurrentTime()) && (tar->GetHPRatio() > 15.0f)) { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - if (GetClass() == Class::Bard) { - std::list dotList = GetPrioritizedBotSpellsBySpellType(this, SpellType_DOT); - - const int maxDotSelect = 5; - int dotSelectCounter = 0; - - for (const auto& s : dotList) { - - if (!IsValidSpell(s.SpellId)) { - continue; - } - - if (CheckSpellRecastTimer(s.SpellId)) - { - - if (!(!tar->IsImmuneToSpell(s.SpellId, this) && tar->CanBuffStack(s.SpellId, botLevel, true) >= 0)) { - continue; - } - - uint32 TempDontDotMeBefore = tar->DontDotMeBefore(); - - casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontDotMeBefore); - - if (TempDontDotMeBefore != tar->DontDotMeBefore()) { - tar->SetDontDotMeBefore(TempDontDotMeBefore); - } - } - - dotSelectCounter++; - - if ((dotSelectCounter == maxDotSelect) || casted_spell) { - break; - } - } - } - else { - std::list dotList = GetBotSpellsBySpellType(this, SpellType_DOT); - - const int maxDotSelect = 5; - int dotSelectCounter = 0; - - for (const auto& s : dotList) { - - if (!IsValidSpell(s.SpellId)) { - continue; - } - - if (CheckSpellRecastTimer(s.SpellId)) { - - if (!(!tar->IsImmuneToSpell(s.SpellId, this) && - tar->CanBuffStack(s.SpellId, botLevel, true) >= 0)) { - continue; - } - - uint32 TempDontDotMeBefore = tar->DontDotMeBefore(); - - if (IsValidSpellRange(s.SpellId, tar)) { - casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontDotMeBefore); - } - - if (TempDontDotMeBefore != tar->DontDotMeBefore()) { - tar->SetDontDotMeBefore(TempDontDotMeBefore); - } - } - - dotSelectCounter++; - - if ((dotSelectCounter == maxDotSelect) || casted_spell) { - return casted_spell; - } - } - } - } - return casted_spell; -} - -bool Bot::BotCastSnare(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes) { - bool casted_spell = false; - if (tar->DontSnareMeBefore() < Timer::GetCurrentTime()) { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - - if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) { - return casted_spell; - } - - uint32 TempDontSnareMeBefore = tar->DontSnareMeBefore(); - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontSnareMeBefore); - } - - if (TempDontSnareMeBefore != tar->DontSnareMeBefore()) { - tar->SetDontSnareMeBefore(TempDontSnareMeBefore); - } - } - return casted_spell; -} - -bool Bot::BotCastLifetap(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes) { - bool casted_spell = false; - if (GetHPRatio() < 90.0f) { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - - if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) { - return casted_spell; - } - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - return casted_spell; -} - -bool Bot::BotCastCombatBuff(Mob* tar, uint8 botLevel, uint8 botClass) { - - bool casted_spell = false; - if (tar->DontBuffMeBefore() < Timer::GetCurrentTime()) { - std::list buffSpellList = GetBotSpellsBySpellType(this, SpellType_InCombatBuff); - - for (const auto& s : buffSpellList) { - - if (!IsValidSpell(s.SpellId)) { - continue; - } - // no buffs with illusions.. use #bot command to cast illusions - if (IsEffectInSpell(s.SpellId, SE_Illusion) && tar != this) { - continue; - } - //no teleport spells use #bot command to cast teleports - if (IsEffectInSpell(s.SpellId, SE_Teleport) || IsEffectInSpell(s.SpellId, SE_Succor)) { - continue; - } - // can not cast buffs for your own pet only on another pet that isn't yours - if ((spells[s.SpellId].target_type == ST_Pet) && (tar != GetPet())) { - continue; - } - - //Conversion Spells - if ( - IsSelfConversionSpell(s.SpellId) && - ( - GetManaRatio() > 90.0f || - GetHPRatio() < 50.0f || - GetHPRatio() < (GetManaRatio() + 10.0f) - ) - ) { - break; //don't cast if low hp, lots of mana, or if mana is higher than hps - } - - // Validate target - // TODO: Add ST_TargetsTarget support for Buffing. - if ( - !( - ( - spells[s.SpellId].target_type == ST_Target || - spells[s.SpellId].target_type == ST_Pet || - (tar == this && spells[s.SpellId].target_type != ST_TargetsTarget) || - spells[s.SpellId].target_type == ST_Group || - spells[s.SpellId].target_type == ST_GroupTeleport || - (botClass == Class::Bard && spells[s.SpellId].target_type == ST_AEBard) - ) && - !tar->IsImmuneToSpell(s.SpellId, this) && - tar->CanBuffStack(s.SpellId, botLevel, true) >= 0 - ) - ) { - continue; - } - - // Put the zone levitate and movement check here since bots are able to bypass the client casting check - if ( - ((IsEffectInSpell(s.SpellId, SE_Levitate) && !zone->CanLevitate()) || - (IsEffectInSpell(s.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) && - (botClass != Class::Bard || !IsSpellUsableInThisZoneType(s.SpellId, zone->GetZoneType())) - ) { - continue; - } - - if (!IsGroupSpell(s.SpellId)) { - //Only check archetype if spell is not a group spell - //Hybrids get all buffs - switch (tar->GetArchetype()) { - case Archetype::Caster: - //TODO: probably more caster specific spell effects in here - if ( - ( - IsEffectInSpell(s.SpellId, SE_AttackSpeed) || - IsEffectInSpell(s.SpellId, SE_ATK) || - IsEffectInSpell(s.SpellId, SE_STR) || - IsEffectInSpell(s.SpellId, SE_ReverseDS) || - IsEffectInSpell(s.SpellId, SE_DamageShield) - ) && - spells[s.SpellId].target_type != ST_Self - ) { - continue; - } - break; - case Archetype::Melee: - if ( - ( - IsEffectInSpell(s.SpellId, SE_IncreaseSpellHaste) || - IsEffectInSpell(s.SpellId, SE_ManaPool) || - IsEffectInSpell(s.SpellId, SE_CastingLevel) || - IsEffectInSpell(s.SpellId, SE_ManaRegen_v2) || - IsEffectInSpell(s.SpellId, SE_CurrentMana) - ) && - spells[s.SpellId].target_type != ST_Self - ) { - continue; - } - break; - default: - break; - } - } - // TODO: Add TriggerSpell Support for Exchanter Runes - if (botClass == Class::Enchanter && IsEffectInSpell(s.SpellId, SE_Rune)) { - float manaRatioToCast = 75.0f; - - switch(GetBotStance()) { - case Stance::Efficient: - manaRatioToCast = 90.0f; - break; - case Stance::Balanced: - case Stance::Aggressive: - manaRatioToCast = 75.0f; - break; - case Stance::Reactive: - case Stance::Burn: - case Stance::AEBurn: - manaRatioToCast = 50.0f; - break; - default: - manaRatioToCast = 75.0f; - break; - } - - //If we're at specified mana % or below, don't rune as enchanter - if (GetManaRatio() <= manaRatioToCast) { - break; - } - } - - if (CheckSpellRecastTimer(s.SpellId)) { - uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); - casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontBuffMeBefore); - if (TempDontBuffMeBefore != tar->DontBuffMeBefore()) { - tar->SetDontBuffMeBefore(TempDontBuffMeBefore); - } - } - - if (casted_spell) { - return casted_spell; - } - } - } - return casted_spell; -} - -bool Bot::BotCastPet(Mob* tar, uint8 botClass, BotSpell& botSpell) { - bool casted_spell = false; - if (!IsPet() && !GetPetID() && !IsBotCharmer()) { - if (botClass == Class::Wizard) { - auto buffs_max = GetMaxBuffSlots(); - auto my_buffs = GetBuffs(); - int familiar_buff_slot = -1; - if (buffs_max && my_buffs) { - for (int index = 0; index < buffs_max; ++index) { - if (IsEffectInSpell(my_buffs[index].spellid, SE_Familiar)) { - MakePet(my_buffs[index].spellid, spells[my_buffs[index].spellid].teleport_zone); - familiar_buff_slot = index; - break; - } - } - } - if (GetPetID()) { - return casted_spell; - } - if (familiar_buff_slot >= 0) { - BuffFadeBySlot(familiar_buff_slot); - return casted_spell; - } - - botSpell = GetFirstBotSpellBySpellType(this, SpellType_Pet); - } - else if (botClass == Class::Magician) { - botSpell = GetBestBotMagicianPetSpell(this); - } - else { - botSpell = GetFirstBotSpellBySpellType(this, SpellType_Pet); - } - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - return casted_spell; -} - -bool Bot::BotCastDispel(Mob* tar, BotSpell& botSpell, uint32 iSpellTypes, const bool& checked_los) { - - bool casted_spell = false; - if (tar->GetHPRatio() > 95.0f) { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - // TODO: Check target to see if there is anything to dispel - - if (tar->CountDispellableBuffs() > 0 && IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - return casted_spell; -} - -bool Bot::BotCastNuke(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los) { - - bool casted_spell = false; - if ((tar->GetHPRatio() <= 95.0f) || ((botClass == Class::Bard) || (botClass == Class::Shaman) || (botClass == Class::Enchanter) || (botClass == Class::Paladin) || (botClass == Class::ShadowKnight) || (botClass == Class::Warrior))) - { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - if (botClass == Class::Cleric || botClass == Class::Enchanter) - { - float manaRatioToCast = 75.0f; - - switch(GetBotStance()) { - case Stance::Efficient: - manaRatioToCast = 90.0f; - break; - case Stance::Balanced: - manaRatioToCast = 75.0f; - break; - case Stance::Reactive: - case Stance::Aggressive: - manaRatioToCast = 50.0f; - break; - case Stance::Burn: - case Stance::AEBurn: - manaRatioToCast = 25.0f; - break; - default: - manaRatioToCast = 50.0f; - break; - } - - //If we're at specified mana % or below, don't nuke as cleric or enchanter - if (GetManaRatio() <= manaRatioToCast) - return casted_spell; - } - - if (botClass == Class::Magician || botClass == Class::ShadowKnight || botClass == Class::Necromancer || botClass == Class::Paladin || botClass == Class::Ranger || botClass == Class::Druid || botClass == Class::Cleric) { - if (tar->GetBodyType() == BodyType::Undead || tar->GetBodyType() == BodyType::SummonedUndead || tar->GetBodyType() == BodyType::Vampire) - botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Undead); - else if (tar->GetBodyType() == BodyType::Summoned || tar->GetBodyType() == BodyType::Summoned2 || tar->GetBodyType() == BodyType::Summoned3) - botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Summoned); - } - - if ((botClass == Class::Paladin || botClass == Class::Druid || botClass == Class::Cleric || botClass == Class::Enchanter || botClass == Class::Wizard) && !IsValidSpell(botSpell.SpellId)) { - uint8 stunChance = (tar->IsCasting() ? 30: 15); - - if (botClass == Class::Paladin) { - stunChance = 50; - } - - if (!tar->GetSpecialAbility(SpecialAbility::StunImmunity) && !tar->IsStunned() && (zone->random.Int(1, 100) <= stunChance)) { - botSpell = GetBestBotSpellForStunByTargetType(this, ST_Target); - } - } - - if (botClass == Class::Wizard && botSpell.SpellId == 0) { - botSpell = GetBestBotWizardNukeSpellByTargetResists(this, tar); - } - - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Target); - } - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) { - return casted_spell; - } - if (IsFearSpell(botSpell.SpellId) && tar->GetSnaredAmount() == -1 && !tar->IsRooted()) { - return casted_spell; - } - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - return casted_spell; -} - -bool Bot::BotCastEscape(Mob*& tar, uint8 botClass, BotSpell& botSpell, uint32 iSpellTypes) { - - bool casted_spell = false; - auto hpr = (uint8) GetHPRatio(); - bool mayGetAggro = false; - - if (hpr > 15 && ((botClass == Class::Wizard) || (botClass == Class::Enchanter) || (botClass == Class::Ranger))) { - mayGetAggro = HasOrMayGetAggro(); - } - - if (hpr <= 15 || mayGetAggro) - { - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - - if (IsInvulnerabilitySpell(botSpell.SpellId)) { - tar = this; //target self for invul type spells - } - - if (IsValidSpellRange(botSpell.SpellId, tar) || botClass == Class::Bard) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - return casted_spell; -} - -bool Bot::BotCastBuff(Mob* tar, uint8 botLevel, uint8 botClass) { - bool casted_spell = false; - if (tar->DontBuffMeBefore() < Timer::GetCurrentTime()) { - std::list buffSpellList = GetBotSpellsBySpellType(this, SpellType_Buff); - - for(const auto& s : buffSpellList) { - - if (!IsValidSpell(s.SpellId)) { - continue; - } - - // no buffs with illusions.. use #bot command to cast illusions - if (IsEffectInSpell(s.SpellId, SE_Illusion) && tar != this) { - continue; - } - - //no teleport spells use #bot command to cast teleports - if (IsEffectInSpell(s.SpellId, SE_Teleport) || IsEffectInSpell(s.SpellId, SE_Succor)) { - continue; - } - // can not cast buffs for your own pet only on another pet that isn't yours - if ((spells[s.SpellId].target_type == ST_Pet) && (tar != GetPet())) { - continue; - } - - // Validate target - // TODO: Add ST_TargetsTarget support for Buffing. - if ( - !( - ( - spells[s.SpellId].target_type == ST_Target || - spells[s.SpellId].target_type == ST_Pet || - (tar == this && spells[s.SpellId].target_type != ST_TargetsTarget) || - spells[s.SpellId].target_type == ST_Group || - spells[s.SpellId].target_type == ST_GroupTeleport || - (botClass == Class::Bard && spells[s.SpellId].target_type == ST_AEBard) - ) && - !tar->IsImmuneToSpell(s.SpellId, this) && - tar->CanBuffStack(s.SpellId, botLevel, true) >= 0 - ) - ) { - continue; - } - - // Put the zone levitate and movement check here since bots are able to bypass the client casting check - if ( - ( - (IsEffectInSpell(s.SpellId, SE_Levitate) && !zone->CanLevitate()) || - (IsEffectInSpell(s.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor()) - ) && - (botClass != Class::Bard || !IsSpellUsableInThisZoneType(s.SpellId, zone->GetZoneType())) - ) { - continue; - } - - switch (tar->GetArchetype()) - { - case Archetype::Caster: - //TODO: probably more caster specific spell effects in here - if (IsEffectInSpell(s.SpellId, SE_AttackSpeed) || IsEffectInSpell(s.SpellId, SE_ATK) || - IsEffectInSpell(s.SpellId, SE_STR) || IsEffectInSpell(s.SpellId, SE_ReverseDS)) - { - continue; - } - break; - case Archetype::Melee: - if (IsEffectInSpell(s.SpellId, SE_IncreaseSpellHaste) || IsEffectInSpell(s.SpellId, SE_ManaPool) || - IsEffectInSpell(s.SpellId, SE_CastingLevel) || IsEffectInSpell(s.SpellId, SE_ManaRegen_v2) || - IsEffectInSpell(s.SpellId, SE_CurrentMana)) - { - continue; - } - break; - default: //Hybrids get all buffs - break; - } - - if (botClass == Class::Enchanter && IsEffectInSpell(s.SpellId, SE_Rune)) - { - float manaRatioToCast = 75.0f; - - switch (GetBotStance()) { - case Stance::Efficient: - manaRatioToCast = 90.0f; - break; - case Stance::Balanced: - case Stance::Aggressive: - manaRatioToCast = 75.0f; - break; - case Stance::Reactive: - case Stance::Burn: - case Stance::AEBurn: - manaRatioToCast = 50.0f; - break; - default: - manaRatioToCast = 75.0f; - break; - } - - //If we're at specified mana % or below, don't rune as enchanter - if (GetManaRatio() <= manaRatioToCast) { - return casted_spell; - } - } - - if (CheckSpellRecastTimer(s.SpellId)) - { - uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); - - casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontBuffMeBefore); - - if (TempDontBuffMeBefore != tar->DontBuffMeBefore()) { - tar->SetDontBuffMeBefore(TempDontBuffMeBefore); - } - } - } - } - return casted_spell; -} - -bool Bot::BotCastRoot(Mob* tar, uint8 botLevel, uint32 iSpellTypes, BotSpell& botSpell, const bool& checked_los) { - bool casted_spell = false; - if (!tar->IsRooted() && tar->DontRootMeBefore() < Timer::GetCurrentTime()) { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - // TODO: If there is a ranger in the group then don't allow root spells - - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - if (tar->CanBuffStack(botSpell.SpellId, botLevel, true) == 0) { - return casted_spell; - } - uint32 TempDontRootMeBefore = tar->DontRootMeBefore(); - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontRootMeBefore); - } - - if (TempDontRootMeBefore != tar->DontRootMeBefore()) { - tar->SetDontRootMeBefore(TempDontRootMeBefore); - } - } - return casted_spell; -} - -bool Bot::BotCastHeal(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, Raid* raid) { - bool casted_spell = false; - if (tar->DontHealMeBefore() < Timer::GetCurrentTime()) { - auto hpr = (uint8)tar->GetHPRatio(); - bool hasAggro = false; - bool isPrimaryHealer = false; - - if (HasGroup() || IsRaidGrouped()) { - isPrimaryHealer = IsGroupHealer(); - } - - if (hpr < 95 || tar->IsClient() || botClass == Class::Bard) { - if (tar->GetClass() == Class::Necromancer && hpr >= 40) { - return false; - } - - if (tar->GetClass() == Class::Shaman && hpr >= 80) { - return false; - } - - // Evaluate the situation - if ((IsEngaged()) && ((botClass == Class::Cleric) || (botClass == Class::Druid) || (botClass == Class::Shaman) || (botClass == Class::Paladin))) { - if (tar->GetTarget() && tar->GetTarget()->GetHateTop() && tar->GetTarget()->GetHateTop() == tar) { - hasAggro = true; - } - - if (hpr < 35) { - botSpell = GetBestBotSpellForFastHeal(this); - } - else if (hpr < 70) { - if (GetNumberNeedingHealedInGroup(60, false, raid) >= 3) { - botSpell = GetBestBotSpellForGroupHeal(this); - } - - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - } - else if (hpr < 95) { - if (GetNumberNeedingHealedInGroup(80, false, raid) >= 3) { - botSpell = GetBestBotSpellForGroupHealOverTime(this); - } - - if (hasAggro) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - } - else { - if (!tar->FindType(SE_HealOverTime)) { - botSpell = GetBestBotSpellForHealOverTime(this); - } - } - } - else if ((botClass == Class::Cleric) || (botClass == Class::Druid) || (botClass == Class::Shaman) || (botClass == Class::Paladin)) { - if (GetNumberNeedingHealedInGroup(40, true, raid) >= 2) { - botSpell = GetBestBotSpellForGroupCompleteHeal(this); - - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForGroupHeal(this); - } - - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForGroupHealOverTime(this); - } - - if (hpr < 40 && !IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - } - else if (GetNumberNeedingHealedInGroup(60, true, raid) >= 2) { - botSpell = GetBestBotSpellForGroupHeal(this); - - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForGroupHealOverTime(this); - } - - if (hpr < 40 && !IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - } - else if (hpr < 40) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - else if (hpr < 75) { - botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); - } - else { - if (hpr < 90 && !tar->FindType(SE_HealOverTime)) { - botSpell = GetBestBotSpellForHealOverTime(this); + if (!IsCommandedSpell()) { + for (Mob* m : v) { + SetBotSpellRecastTimer(spellType, m, true); } } } else { - float hpRatioToCast = 0.0f; + BotGroupSay( + this, + fmt::format( + "Curing {} with {}.", + (tar == this ? "myself" : tar->GetCleanName()), + GetSpellName(botSpell.SpellId) + ).c_str() + ); - switch (GetBotStance()) { - case Stance::Efficient: - case Stance::Aggressive: - hpRatioToCast = isPrimaryHealer ? 90.0f : 50.0f; - break; - case Stance::Balanced: - hpRatioToCast = isPrimaryHealer ? 95.0f : 75.0f; - break; - case Stance::Reactive: - hpRatioToCast = isPrimaryHealer ? 100.0f : 90.0f; - break; - case Stance::Burn: - case Stance::AEBurn: - hpRatioToCast = isPrimaryHealer ? 75.0f : 25.0f; - break; - default: - hpRatioToCast = isPrimaryHealer ? 100.0f : 0.0f; - break; + if (!IsCommandedSpell()) { + SetBotSpellRecastTimer(spellType, tar, true); } - //If we're at specified mana % or below, don't heal as hybrid - if (tar->GetHPRatio() <= hpRatioToCast) { - botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); - } - } - - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); - } - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetFirstBotSpellForSingleTargetHeal(this); - } - if (botSpell.SpellId == 0 && botClass == Class::Bard) { - botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal); - } - - if (!IsValidSpell(botSpell.SpellId)) { - return false; - } - // Can we cast this spell on this target? - if (!(spells[botSpell.SpellId].target_type==ST_GroupTeleport || spells[botSpell.SpellId].target_type == ST_Target || tar == this) - && tar->CanBuffStack(botSpell.SpellId, botLevel, true) < 0) { - return false; - } - - uint32 TempDontHealMeBeforeTime = tar->DontHealMeBefore(); - - if (IsValidSpellRange(botSpell.SpellId, tar) || botClass == Class::Bard) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontHealMeBeforeTime); - } - - if (casted_spell && botClass != Class::Bard) { - if (IsGroupSpell(botSpell.SpellId)) { - if (HasGroup()) { - Group *group = GetGroup(); - if (group) { - BotGroupSay( - this, - fmt::format( - "Casting {}.", - spells[botSpell.SpellId].name - ).c_str() - ); - - for (const auto& member : group->members) { - if (member && !member->qglobal) { - member->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000); - } - } - } else if (IsRaidGrouped()) { - uint32 r_group = raid->GetGroup(GetName()); - const auto msg = fmt::format("Casting {}.", spells[botSpell.SpellId].name); - raid->RaidGroupSay(msg.c_str(), GetCleanName(), Language::CommonTongue, Language::MaxValue); - std::vector raid_group_members = raid->GetRaidGroupMembers(r_group); - for (const auto& rgm : raid_group_members) { - if (rgm.member && !rgm.member->qglobal) { - rgm.member->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000); - } - } - } - } else { - if (tar != this) { //we don't need spam of bots healing themselves - BotGroupSay( - this, - fmt::format( - "Casting {} on {}.", - spells[botSpell.SpellId].name, - tar->GetCleanName() - ).c_str() - ); - } - } - } - tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 2000); + return true; } } } - return casted_spell; + + return false; } -bool Bot::BotCastMez(Mob* tar, uint8 botLevel, bool checked_los, BotSpell& botSpell, Raid* raid) { - bool casted_spell = false; - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { +bool Bot::BotCastPet(Mob* tar, uint8 botClass, BotSpell& botSpell, uint16 spellType) { + if (botClass == Class::Wizard) { + auto buffs_max = GetMaxBuffSlots(); + auto my_buffs = GetBuffs(); + int familiar_buff_slot = -1; + if (buffs_max && my_buffs) { + for (int index = 0; index < buffs_max; ++index) { + if (IsEffectInSpell(my_buffs[index].spellid, SE_Familiar)) { + MakePet(my_buffs[index].spellid, spells[my_buffs[index].spellid].teleport_zone); + familiar_buff_slot = index; + break; + } + } + } + if (GetPetID()) { + return false; + } + if (familiar_buff_slot >= 0) { + BuffFadeBySlot(familiar_buff_slot); + return false; + } + + botSpell = GetFirstBotSpellBySpellType(this, spellType); + } + else if (botClass == Class::Magician) { + botSpell = GetBestBotMagicianPetSpell(this, spellType); + } + else { + botSpell = GetFirstBotSpellBySpellType(this, spellType); + } + + if (!IsValidSpellAndLoS(botSpell.SpellId, HasLoS())) { return false; } - //TODO - //Check if single target or AoE mez is best - //if (TARGETS ON MT IS => 3 THEN botSpell = AoEMez) - //if (TARGETS ON MT IS <= 2 THEN botSpell = BestMez) + if (AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost)) { + SetCastedSpellType(spellType); + BotGroupSay( + this, + fmt::format( + "Summoning a pet [{}].", + GetSpellName(botSpell.SpellId) + ).c_str() + ); - botSpell = GetBestBotSpellForMez(this); + return true; + } + + return false; +} + +bool Bot::BotCastNuke(Mob* tar, uint8 botClass, BotSpell& botSpell, uint16 spellType) { + if (spellType == BotSpellTypes::Stun || spellType == BotSpellTypes::AEStun) { + uint8 stunChance = (tar->IsCasting() ? RuleI(Bots, StunCastChanceIfCasting) : RuleI(Bots, StunCastChanceNormal)); + + if (botClass == Class::Paladin) { + stunChance = RuleI(Bots, StunCastChancePaladins); + } + + if ( + !tar->GetSpecialAbility(SpecialAbility::StunImmunity) && + ( + IsCommandedSpell() || + (!tar->IsStunned() && (zone->random.Int(1, 100) <= stunChance)) + ) + ) { + botSpell = GetBestBotSpellForStunByTargetType(this, ST_TargetOptional, spellType, IsAEBotSpellType(spellType), tar); + } + + if (!IsValidSpellAndLoS(botSpell.SpellId, HasLoS())) { + return false; + } + } + + if (!IsValidSpellAndLoS(botSpell.SpellId, HasLoS())) { + botSpell = GetBestBotSpellForNukeByBodyType(this, tar->GetBodyType(), spellType, IsAEBotSpellType(spellType), tar); + } + + if (!IsValidSpellAndLoS(botSpell.SpellId, HasLoS()) && spellType == BotSpellTypes::Nuke && botClass == Class::Wizard) { + botSpell = GetBestBotWizardNukeSpellByTargetResists(this, tar, spellType); + } + + if (!IsValidSpellAndLoS(botSpell.SpellId, HasLoS())) { + std::list botSpellList = GetPrioritizedBotSpellsBySpellType(this, spellType, tar, IsAEBotSpellType(spellType)); + + for (const auto& s : botSpellList) { + if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) { + continue; + } + + if (AIDoSpellCast(s.SpellIndex, tar, s.ManaCost)) { + SetCastedSpellType(spellType); + + if (botClass != Class::Bard || RuleB(Bots, BardsAnnounceCasts)) { + BotGroupSay( + this, + fmt::format( + "Casting {} [{}] on {}.", + GetSpellName(s.SpellId), + GetSpellTypeNameByID(spellType), + tar->GetCleanName() + ).c_str() + ); + } + + return true; + } + } + } + else { + if (AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost)) { + SetCastedSpellType(spellType); + + if (botClass != Class::Bard || RuleB(Bots, BardsAnnounceCasts)) { + BotGroupSay( + this, + fmt::format( + "Casting {} [{}] on {}.", + GetSpellName(botSpell.SpellId), + GetSpellTypeNameByID(spellType), + tar->GetCleanName() + ).c_str() + ); + } + + return true; + } + } + + return false; +} + +bool Bot::BotCastHeal(Mob* tar, uint8 botClass, BotSpell& botSpell, uint16 spellType) { + botSpell = GetSpellByHealType(spellType, tar); if (!IsValidSpell(botSpell.SpellId)) { return false; } - Mob* addMob = GetFirstIncomingMobToMez(this, botSpell); + if (AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost)) { + if (botClass != Class::Bard || RuleB(Bots, BardsAnnounceCasts)) { + if (IsGroupSpell(botSpell.SpellId)) { + BotGroupSay( + this, + fmt::format( + "Healing the group with {} [{}].", + GetSpellName(botSpell.SpellId), + GetSpellTypeNameByID(spellType) + ).c_str() + ); - if (!addMob) { - return false; - } + if (botClass != Class::Bard) { + const std::vector v = GatherGroupSpellTargets(tar); - if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) { - return false; - } + if (!IsCommandedSpell()) { + for (Mob* m : v) { + SetBotSpellRecastTimer(spellType, m, true); + } + } + } - if (IsValidSpellRange(botSpell.SpellId, addMob)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost); - } - if (casted_spell) { - if (raid) { - raid->RaidSay( - GetCleanName(), - fmt::format( - "Attempting to mesmerize {} with {}.", - addMob->GetCleanName(), - spells[botSpell.SpellId].name - ).c_str(), - 0, - 100 - ); - } else { - BotGroupSay( - this, - fmt::format( - "Attempting to mesmerize {} with {}.", - addMob->GetCleanName(), - spells[botSpell.SpellId].name - ).c_str() - ); + } + else { + BotGroupSay( + this, + fmt::format( + "Healing {} with {} [{}].", + (tar == this ? "myself" : tar->GetCleanName()), + GetSpellName(botSpell.SpellId), + GetSpellTypeNameByID(spellType) + ).c_str() + ); + + if (botClass != Class::Bard) { + if (!IsCommandedSpell()) { + SetBotSpellRecastTimer(spellType, tar, true); + } + } + } } + + return true; } - return casted_spell; + + return false; } bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) { @@ -1260,14 +562,14 @@ bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain // Allow bots to cast buff spells even if they are out of mana if ( RuleB(Bots, FinishBuffing) && - manaCost > hasMana && AIBot_spells[i].type & SpellType_Buff + manaCost > hasMana && AIBot_spells[i].type == BotSpellTypes::Buff ) { SetMana(manaCost); } float dist2 = 0; - if (AIBot_spells[i].type & SpellType_Escape) { + if (AIBot_spells[i].type == BotSpellTypes::Escape) { dist2 = 0; } else dist2 = DistanceSquared(m_Position, tar->GetPosition()); @@ -1276,7 +578,7 @@ bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain ( ( ( - (spells[AIBot_spells[i].spellid].target_type==ST_GroupTeleport && AIBot_spells[i].type == SpellType_Heal) || + (spells[AIBot_spells[i].spellid].target_type==ST_GroupTeleport && AIBot_spells[i].type == BotSpellTypes::RegularHeal) || spells[AIBot_spells[i].spellid].target_type ==ST_AECaster || spells[AIBot_spells[i].spellid].target_type ==ST_Group || spells[AIBot_spells[i].spellid].target_type ==ST_AEBard || @@ -1305,32 +607,49 @@ bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain if (!result) { SetMana(hasMana); } - else { - if (CalcSpellRecastTimer(AIBot_spells[i].spellid) > 0) { - SetSpellRecastTimer(AIBot_spells[i].spellid); - } - } return result; } bool Bot::AI_PursueCastCheck() { + if (GetAppearance() == eaDead || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0) { + return false; + } + bool result = false; - if (AIautocastspell_timer->Check(false)) { + if (GetTarget() && AIautocastspell_timer->Check(false)) { + + LogAIDetail("Bot Pursue autocast check triggered: [{}]", GetCleanName()); + LogBotPreChecksDetail("{} says, 'AI_PursueCastCheck started.'", GetCleanName()); //deleteme AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. - LogAIDetail("Bot Engaged (pursuing) autocast check triggered. Trying to cast offensive spells"); + if (!IsAttackAllowed(GetTarget())) { + return false; + } - if (!AICastSpell(GetTarget(), 100, SpellType_Snare)) { - if (!AICastSpell(GetTarget(), 100, SpellType_Lifetap) && !AICastSpell(GetTarget(), 100, SpellType_Nuke)) { + auto castOrder = GetSpellTypesPrioritized(BotPriorityCategories::Pursue); + + for (auto& currentCast : castOrder) { + if (currentCast.priority == 0) { + LogBotPreChecksDetail("{} says, '[{}] is priority 0, skipping.'", GetCleanName(), GetSpellTypeNameByID(currentCast.spellType)); //deleteme + continue; + } + + if (currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. + continue; + } + + result = AttemptAICastSpell(currentCast.spellType); + + if (result) { + break; } - result = true; } if (!AIautocastspell_timer->Enabled()) { - AIautocastspell_timer->Start(RandomTimer(100, 250), false); + AIautocastspell_timer->Start(RandomTimer(RuleI(Bots, MinDelayBetweenInCombatCastAttempts), RuleI(Bots, MaxDelayBetweenInCombatCastAttempts)), false); } } @@ -1338,453 +657,104 @@ bool Bot::AI_PursueCastCheck() { } bool Bot::AI_IdleCastCheck() { + if (GetAppearance() == eaDead || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0) { + return false; + } + bool result = false; if (AIautocastspell_timer->Check(false)) { + LogAIDetail("Bot Non-Engaged autocast check triggered: [{}]", GetCleanName()); + LogBotPreChecksDetail("{} says, 'AI_IdleCastCheck started.'", GetCleanName()); //deleteme + AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. - bool pre_combat = false; + bool preCombat = false; Client* test_against = nullptr; if (HasGroup() && GetGroup()->GetLeader() && GetGroup()->GetLeader()->IsClient()) { test_against = GetGroup()->GetLeader()->CastToClient(); - } else if (GetOwner() && GetOwner()->IsClient()) { + } + else if (GetOwner() && GetOwner()->IsClient()) { test_against = GetOwner()->CastToClient(); } if (test_against) { - pre_combat = test_against->GetBotPrecombat(); + preCombat = test_against->GetBotPrecombat(); } - //Ok, IdleCastCheck depends of class. - switch (GetClass()) { - // Healers WITHOUT pets will check if a heal is needed before buffing. - case Class::Cleric: - case Class::Paladin: - case Class::Ranger: { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Heal)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) { - } - } - } - } + auto castOrder = GetSpellTypesPrioritized(BotPriorityCategories::Idle); + + for (auto& currentCast : castOrder) { + if (currentCast.priority == 0) { + LogBotPreChecksDetail("{} says, '[{}] is priority 0, skipping.'", GetCleanName(), GetSpellTypeNameByID(currentCast.spellType)); //deleteme + continue; } - result = true; - break; - } - case Class::Monk: - case Class::Rogue: - case Class::Warrior: - case Class::Berserker: { - if (!AICastSpell(this, 100, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Heal)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) { - } - } - } + if (!preCombat && (currentCast.spellType == BotSpellTypes::PreCombatBuff || currentCast.spellType == BotSpellTypes::PreCombatBuffSong)) { + continue; } - result = true; - break; - } - // Pets class will first cast their pet, then buffs - - case Class::Magician: - case Class::ShadowKnight: - case Class::Necromancer: - case Class::Enchanter: { - if (!AICastSpell(this, 100, SpellType_Pet)) { - if (!AICastSpell(this, 100, SpellType_Cure)) { - if (!AICastSpell(GetPet(), 100, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!AICastSpell(GetPet(), 100, SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) { - } - } - } - } - } + if (currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. + continue; } - result = true; - break; - } - case Class::Druid: - case Class::Shaman: - case Class::Beastlord: { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Pet)) { - if (!AICastSpell(this, 100, SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Heal)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!AICastSpell(GetPet(), 100, SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) { - } - } - } - } - } - } - } + result = AttemptAICastSpell(currentCast.spellType); - result = true; - break; - } - case Class::Wizard: { // This can eventually be move into the Class::Beastlord case handler once pre-combat is fully implemented - if (pre_combat) { - if (!AICastSpell(this, 100, SpellType_Pet)) { - if (!AICastSpell(this, 100, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Heal)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_PreCombatBuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) { - } - } - } - } - } - } + if (result) { + break; } - else { - if (!AICastSpell(this, 100, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Pet)) { - if (!AICastSpell(this, 100, SpellType_Heal)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) { - } - } - } - } - } - } - - result = true; - break; - } - case Class::Bard: { - if (pre_combat) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!AICastSpell(this, 100, SpellType_PreCombatBuffSong)) { - if (!AICastSpell(this, 100, SpellType_InCombatBuffSong)) { - } - } - } - } - } - else { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!AICastSpell(this, 100, SpellType_OutOfCombatBuffSong)) { - if (!AICastSpell(this, 100, SpellType_InCombatBuffSong)) { - } - } - } - } - } - - result = true; - break; - } - default: - break; } - if (!AIautocastspell_timer->Enabled()) - AIautocastspell_timer->Start(RandomTimer(500, 2000), false); // avg human response is much less than 5 seconds..even for non-combat situations... + if (!AIautocastspell_timer->Enabled()) { + AIautocastspell_timer->Start(RandomTimer(RuleI(Bots, MinDelayBetweenOutCombatCastAttempts), RuleI(Bots, MaxDelayBetweenOutCombatCastAttempts)), false); + } } return result; } bool Bot::AI_EngagedCastCheck() { + if (GetAppearance() == eaDead || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0) { + return false; + } + bool result = false; bool failedToCast = false; if (GetTarget() && AIautocastspell_timer->Check(false)) { + LogAIDetail("Bot Engaged autocast check triggered: [{}]", GetCleanName()); + LogBotPreChecksDetail("{} says, 'AI_EngagedCastCheck started.'", GetCleanName()); //deleteme + AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. - uint8 botClass = GetClass(); - bool mayGetAggro = HasOrMayGetAggro(); + if (!IsAttackAllowed(GetTarget())) { + return false; + } - LogAIDetail("Engaged autocast check triggered (BOTS). Trying to cast healing spells then maybe offensive spells"); + auto castOrder = GetSpellTypesPrioritized(BotPriorityCategories::Engaged); - if (botClass == Class::Cleric) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Heal), BotAISpellRange, SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - //AIautocastspell_timer->Start(RandomTimer(100, 250), false); // Do not give healer classes a lot of time off or your tank's die - failedToCast = true; - } - } - } - } - } + for (auto& currentCast : castOrder) { + if (currentCast.priority == 0) { + LogBotPreChecksDetail("{} says, '[{}] is priority 0, skipping.'", GetCleanName(), GetSpellTypeNameByID(currentCast.spellType)); //deleteme + continue; } - } - else if (botClass == Class::Druid) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Heal), BotAISpellRange, SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - //AIautocastspell_timer->Start(RandomTimer(100, 250), false); // Do not give healer classes a lot of time off or your tank's die - failedToCast = true; - } - } - } - } - } - } + + if (currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. + continue; } - } - else if (botClass == Class::Shaman) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Slow), SpellType_Slow)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Heal), BotAISpellRange, SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - if (!AICastSpell(GetPet(), GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - failedToCast = true; - } - } - } - } - } - } - } - } - } - } - else if (botClass == Class::Ranger) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Heal), BotAISpellRange, SpellType_Heal)) { - failedToCast = true; - } - } - } - } - } - } - } - else if (botClass == Class::Beastlord) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Slow), SpellType_Slow)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Pet), SpellType_Pet)) { - if (!AICastSpell(GetPet(), GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Heal), BotAISpellRange, SpellType_Heal)) { - failedToCast = true; - } - } - } - } - } - } - } - } - } - } - else if (botClass == Class::Wizard) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - else if (botClass == Class::Paladin) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Heal), BotAISpellRange, SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - else if (botClass == Class::ShadowKnight) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Lifetap), SpellType_Lifetap)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - else if (botClass == Class::Magician) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Pet), SpellType_Pet)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetPet(), GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - else if (botClass == Class::Necromancer) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Pet), SpellType_Pet)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Lifetap), SpellType_Lifetap)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetPet(), GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - } - } - } - else if (botClass == Class::Enchanter) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Mez), SpellType_Mez)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Slow), SpellType_Slow)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - } - } - else if (botClass == Class::Bard) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) {// Bards will use their escape songs - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_HateRedux), BotAISpellRange, SpellType_HateRedux)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Slow), SpellType_Slow)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_InCombatBuffSong), SpellType_InCombatBuffSong)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), mayGetAggro ? 0 : GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) {// Bards will use their dot songs - if (!AICastSpell(GetTarget(), mayGetAggro ? 0 : GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) {// Bards will use their nuke songs - failedToCast = true; - } - } - } - } - } - } - } - } - } - else if (botClass == Class::Berserker) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_InCombatBuffSong), SpellType_InCombatBuffSong)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - } - } - else if (botClass == Class::Monk) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_InCombatBuffSong), SpellType_InCombatBuffSong)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - } - } - else if (botClass == Class::Rogue) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_InCombatBuffSong), SpellType_InCombatBuffSong)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - } - } - else if (botClass == Class::Warrior) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_InCombatBuffSong), SpellType_InCombatBuffSong)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } + + result = AttemptAICastSpell(currentCast.spellType); + + if (result) { + break; } } if (!AIautocastspell_timer->Enabled()) { - AIautocastspell_timer->Start(RandomTimer(150, 300), false); - } - - if (!failedToCast) { - result = true; + AIautocastspell_timer->Start(RandomTimer(RuleI(Bots, MinDelayBetweenInCombatCastAttempts), RuleI(Bots, MaxDelayBetweenInCombatCastAttempts)), false); } } @@ -1819,22 +789,22 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) { botSpell.ManaCost = 0; if (useFastHeals) { - botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); + botSpell = GetBestBotSpellForRegularSingleTargetHeal(this, tar); if (!IsValidSpell(botSpell.SpellId)) - botSpell = GetBestBotSpellForFastHeal(this); + botSpell = GetBestBotSpellForFastHeal(this, tar); } else { - botSpell = GetBestBotSpellForPercentageHeal(this); + botSpell = GetBestBotSpellForPercentageHeal(this, tar); if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); + botSpell = GetBestBotSpellForRegularSingleTargetHeal(this, tar); } if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetFirstBotSpellForSingleTargetHeal(this); + botSpell = GetFirstBotSpellForSingleTargetHeal(this, tar); } if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal); + botSpell = GetFirstBotSpellBySpellType(this, BotSpellTypes::RegularHeal); } } @@ -1879,7 +849,7 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) { return castedSpell; } -std::list Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEffect) { +std::list Bot::GetBotSpellsForSpellEffect(Bot* botCaster, uint16 spellType, int spellEffect) { std::list result; if (!botCaster) { @@ -1894,13 +864,16 @@ std::list Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEff std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + if (!IsValidSpellAndLoS(botSpellList[i].spellid, botCaster->HasLoS())) { continue; } - if (IsEffectInSpell(botSpellList[i].spellid, spellEffect) || GetSpellTriggerSpellID(botSpellList[i].spellid, spellEffect)) { + if ( + botCaster->CheckSpellRecastTimer(botSpellList[i].spellid) && + (botSpellList[i].type == spellType || botSpellList[i].type == botCaster->GetSpellListSpellType(spellType)) && + botCaster->IsValidSpellTypeBySpellID(spellType, botSpellList[i].spellid) && + (IsEffectInSpell(botSpellList[i].spellid, spellEffect) || GetSpellTriggerSpellID(botSpellList[i].spellid, spellEffect)) + ) { BotSpell botSpell; botSpell.SpellId = botSpellList[i].spellid; botSpell.SpellIndex = i; @@ -1914,7 +887,7 @@ std::list Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEff return result; } -std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, int spellEffect, SpellTargetType targetType) { +std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, uint16 spellType, int spellEffect, SpellTargetType targetType) { std::list result; if (!botCaster) { @@ -1929,18 +902,19 @@ std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + if (!IsValidSpellAndLoS(botSpellList[i].spellid, botCaster->HasLoS())) { continue; } if ( + botCaster->CheckSpellRecastTimer(botSpellList[i].spellid) && + (botSpellList[i].type == spellType || botSpellList[i].type == botCaster->GetSpellListSpellType(spellType)) && + botCaster->IsValidSpellTypeBySpellID(spellType, botSpellList[i].spellid) && ( IsEffectInSpell(botSpellList[i].spellid, spellEffect) || GetSpellTriggerSpellID(botSpellList[i].spellid, spellEffect) ) && - spells[botSpellList[i].spellid].target_type == targetType + (targetType == ST_TargetOptional || spells[botSpellList[i].spellid].target_type == targetType) ) { BotSpell botSpell; botSpell.SpellId = botSpellList[i].spellid; @@ -1954,7 +928,7 @@ std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, return result; } -std::list Bot::GetBotSpellsBySpellType(Bot* botCaster, uint32 spellType) { +std::list Bot::GetBotSpellsBySpellType(Bot* botCaster, uint16 spellType) { std::list result; if (!botCaster) { @@ -1969,13 +943,15 @@ std::list Bot::GetBotSpellsBySpellType(Bot* botCaster, uint32 spellTyp std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + if (!IsValidSpellAndLoS(botSpellList[i].spellid, botCaster->HasLoS())) { continue; } - if (botSpellList[i].type & spellType) { + if ( + botCaster->CheckSpellRecastTimer(botSpellList[i].spellid) && + (botSpellList[i].type == spellType || botSpellList[i].type == botCaster->GetSpellListSpellType(spellType)) && + botCaster->IsValidSpellTypeBySpellID(spellType, botSpellList[i].spellid) + ) { BotSpell botSpell; botSpell.SpellId = botSpellList[i].spellid; botSpell.SpellIndex = i; @@ -1989,27 +965,59 @@ std::list Bot::GetBotSpellsBySpellType(Bot* botCaster, uint32 spellTyp return result; } -std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCaster, uint32 spellType) { +std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCaster, uint16 spellType, Mob* tar, bool AE) { std::list result; if (botCaster && botCaster->AI_HasSpells()) { std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + if (!IsValidSpellAndLoS(botSpellList[i].spellid, botCaster->HasLoS())) { continue; } - if (botSpellList[i].type & spellType) { - BotSpell_wPriority botSpell; - botSpell.SpellId = botSpellList[i].spellid; - botSpell.SpellIndex = i; - botSpell.ManaCost = botSpellList[i].manacost; - botSpell.Priority = botSpellList[i].priority; + if (spellType == BotSpellTypes::HateRedux && botCaster->GetClass() == Class::Bard) { + if (spells[botSpellList[i].spellid].target_type != ST_Target) { + continue; + } + } - result.push_back(botSpell); + if ( + botCaster->CheckSpellRecastTimer(botSpellList[i].spellid) && + (botSpellList[i].type == spellType || botSpellList[i].type == botCaster->GetSpellListSpellType(spellType)) && + botCaster->IsValidSpellTypeBySpellID(spellType, botSpellList[i].spellid) + ) { + if (!AE && IsAnyAESpell(botSpellList[i].spellid) && !IsGroupSpell(botSpellList[i].spellid)) { + continue; + } + else if (AE && !IsAnyAESpell(botSpellList[i].spellid)) { + continue; + } + + if ( + ( + !botCaster->IsCommandedSpell() || (botCaster->IsCommandedSpell() && (spellType != BotSpellTypes::Mez && spellType != BotSpellTypes::AEMez)) + ) + && (!IsPBAESpell(botSpellList[i].spellid) && !botCaster->CastChecks(botSpellList[i].spellid, tar, spellType, false, IsGroupBotSpellType(spellType))) // TODO bot rewrite - needed for ae spells? + ) { + continue; + } + + if ( + botCaster->IsCommandedSpell() || + !AE || + (spellType == BotSpellTypes::GroupCures) || + (spellType == BotSpellTypes::AEMez) || + (AE && botCaster->HasValidAETarget(botCaster, botSpellList[i].spellid, spellType, tar)) + ) { + BotSpell_wPriority botSpell; + botSpell.SpellId = botSpellList[i].spellid; + botSpell.SpellIndex = i; + botSpell.ManaCost = botSpellList[i].manacost; + botSpell.Priority = botSpellList[i].priority; + + result.push_back(botSpell); + } } } @@ -2025,7 +1033,7 @@ std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa return result; } -BotSpell Bot::GetFirstBotSpellBySpellType(Bot* botCaster, uint32 spellType) { +BotSpell Bot::GetFirstBotSpellBySpellType(Bot* botCaster, uint16 spellType) { BotSpell result; result.SpellId = 0; @@ -2036,13 +1044,15 @@ BotSpell Bot::GetFirstBotSpellBySpellType(Bot* botCaster, uint32 spellType) { std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + if (!IsValidSpellAndLoS(botSpellList[i].spellid, botCaster->HasLoS())) { continue; } - if ((botSpellList[i].type & spellType) && botCaster->CheckSpellRecastTimer(botSpellList[i].spellid)) { + if ( + botCaster->CheckSpellRecastTimer(botSpellList[i].spellid) && + (botSpellList[i].type == spellType || botSpellList[i].type == botCaster->GetSpellListSpellType(spellType)) && + botCaster->IsValidSpellTypeBySpellID(spellType, botSpellList[i].spellid) + ) { result.SpellId = botSpellList[i].spellid; result.SpellIndex = i; result.ManaCost = botSpellList[i].manacost; @@ -2055,7 +1065,7 @@ BotSpell Bot::GetFirstBotSpellBySpellType(Bot* botCaster, uint32 spellType) { return result; } -BotSpell Bot::GetBestBotSpellForFastHeal(Bot *botCaster) { +BotSpell Bot::GetBestBotSpellForVeryFastHeal(Bot* botCaster, Mob* tar, uint16 spellType) { BotSpell result; result.SpellId = 0; @@ -2063,11 +1073,12 @@ BotSpell Bot::GetBestBotSpellForFastHeal(Bot *botCaster) { result.ManaCost = 0; if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP); + std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_CurrentHP); for (auto botSpellListItr : botSpellList) { // Assuming all the spells have been loaded into this list by level and in descending order - if (IsFastHealSpell(botSpellListItr.SpellId) && botCaster->CheckSpellRecastTimer(botSpellListItr.SpellId)) { + if ( + IsVeryFastHealSpell(botSpellListItr.SpellId) && botCaster->CastChecks(botSpellListItr.SpellId, tar, spellType)) { result.SpellId = botSpellListItr.SpellId; result.SpellIndex = botSpellListItr.SpellIndex; result.ManaCost = botSpellListItr.ManaCost; @@ -2080,7 +1091,7 @@ BotSpell Bot::GetBestBotSpellForFastHeal(Bot *botCaster) { return result; } -BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* botCaster) { +BotSpell Bot::GetBestBotSpellForFastHeal(Bot *botCaster, Mob* tar, uint16 spellType) { BotSpell result; result.SpellId = 0; @@ -2088,29 +1099,14 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* botCaster) { result.ManaCost = 0; if (botCaster) { - std::list botHoTSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime); - std::vector botSpellList = botCaster->AIBot_spells; + std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_CurrentHP); - for (auto botSpellListItr : botHoTSpellList) { + for (auto botSpellListItr : botSpellList) { // Assuming all the spells have been loaded into this list by level and in descending order - if (IsHealOverTimeSpell(botSpellListItr.SpellId)) { - for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here - continue; - } - - if ( - botSpellList[i].spellid == botSpellListItr.SpellId && - (botSpellList[i].type & SpellType_Heal) && - botCaster->CheckSpellRecastTimer(botSpellListItr.SpellId) - ) { - result.SpellId = botSpellListItr.SpellId; - result.SpellIndex = botSpellListItr.SpellIndex; - result.ManaCost = botSpellListItr.ManaCost; - } - } + if (IsFastHealSpell(botSpellListItr.SpellId) && botCaster->CastChecks(botSpellListItr.SpellId, tar, spellType)) { + result.SpellId = botSpellListItr.SpellId; + result.SpellIndex = botSpellListItr.SpellIndex; + result.ManaCost = botSpellListItr.ManaCost; break; } @@ -2120,7 +1116,32 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* botCaster) { return result; } -BotSpell Bot::GetBestBotSpellForPercentageHeal(Bot *botCaster) { +BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* botCaster, Mob* tar, uint16 spellType) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (botCaster) { + std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_HealOverTime); + + for (auto botSpellListItr : botSpellList) { + // Assuming all the spells have been loaded into this list by level and in descending order + if (IsHealOverTimeSpell(botSpellListItr.SpellId) && botCaster->CastChecks(botSpellListItr.SpellId, tar, spellType)) { + result.SpellId = botSpellListItr.SpellId; + result.SpellIndex = botSpellListItr.SpellIndex; + result.ManaCost = botSpellListItr.ManaCost; + + break; + } + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForPercentageHeal(Bot *botCaster, Mob* tar, uint16 spellType) { BotSpell result; result.SpellId = 0; @@ -2129,18 +1150,21 @@ BotSpell Bot::GetBestBotSpellForPercentageHeal(Bot *botCaster) { if (botCaster && botCaster->AI_HasSpells()) { std::vector botSpellList = botCaster->AIBot_spells; - for (int i = botSpellList.size() - 1; i >= 0; i--) { if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; } - if (IsCompleteHealSpell(botSpellList[i].spellid) && botCaster->CheckSpellRecastTimer(botSpellList[i].spellid)) { + if ( + (botSpellList[i].type == spellType || botSpellList[i].type == botCaster->GetSpellListSpellType(spellType)) && + botCaster->IsValidSpellTypeBySpellID(spellType, botSpellList[i].spellid) && + IsCompleteHealSpell(botSpellList[i].spellid) && + botCaster->CastChecks(botSpellList[i].spellid, tar, spellType) + ) { result.SpellId = botSpellList[i].spellid; result.SpellIndex = i; result.ManaCost = botSpellList[i].manacost; + break; } } @@ -2149,7 +1173,7 @@ BotSpell Bot::GetBestBotSpellForPercentageHeal(Bot *botCaster) { return result; } -BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* botCaster) { +BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* botCaster, Mob* tar, uint16 spellType) { BotSpell result; result.SpellId = 0; @@ -2157,14 +1181,15 @@ BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* botCaster) { result.ManaCost = 0; if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP); + std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_CurrentHP); for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if (IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) && botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { + if (IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) && botCaster->CastChecks(botSpellListItr->SpellId, tar, spellType)) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; + break; } } @@ -2173,7 +1198,7 @@ BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* botCaster) { return result; } -BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* botCaster) { +BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* botCaster, Mob* tar, uint16 spellType) { BotSpell result; result.SpellId = 0; @@ -2181,20 +1206,15 @@ BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* botCaster) { result.ManaCost = 0; if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP); + std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_CurrentHP); for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if ( - ( - IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) || - IsFastHealSpell(botSpellListItr->SpellId) - ) && - botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) - ) { + if (IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) && botCaster->CastChecks(botSpellListItr->SpellId, tar, spellType)) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; + break; } } @@ -2203,7 +1223,7 @@ BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* botCaster) { return result; } -BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* botCaster) { +BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* botCaster, Mob* tar, uint16 spellType) { BotSpell result; result.SpellId = 0; @@ -2211,85 +1231,31 @@ BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* botCaster) { result.ManaCost = 0; if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP); + std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_CurrentHP); + const std::vector v = botCaster->GatherSpellTargets(); + int targetCount = 0; for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if ( - IsRegularGroupHealSpell(botSpellListItr->SpellId) && - botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) - ) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; - break; - } - } - } + if (IsRegularGroupHealSpell(botSpellListItr->SpellId)) { + if (!botCaster->IsCommandedSpell()) { + targetCount = 0; - return result; -} - -BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* botCaster) { - BotSpell result; - - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; - - if (botCaster) { - std::list botHoTSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime); - std::vector botSpellList = botCaster->AIBot_spells; - - for (std::list::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if (IsGroupHealOverTimeSpell(botSpellListItr->SpellId)) { - - for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here - continue; + for (Mob* m : v) { + if (botCaster->IsValidSpellRange(botSpellListItr->SpellId, m) && botCaster->CastChecks(botSpellListItr->SpellId, m, spellType, true, IsGroupBotSpellType(spellType))) { + ++targetCount; + } } - if ( - botSpellList[i].spellid == botSpellListItr->SpellId && - (botSpellList[i].type & SpellType_Heal) && - botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) - ) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; + if (targetCount < botCaster->GetSpellTypeAEOrGroupTargetCount(spellType)) { + continue; } } - break; - } - } - } - - return result; -} - -BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* botCaster) { - BotSpell result; - - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; - - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CompleteHeal); - - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if ( - IsGroupCompleteHealSpell(botSpellListItr->SpellId) && - botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) - ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; + break; } } @@ -2298,7 +1264,7 @@ BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* botCaster) { return result; } -BotSpell Bot::GetBestBotSpellForMez(Bot* botCaster) { +BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* botCaster, Mob* tar, uint16 spellType) { BotSpell result; result.SpellId = 0; @@ -2306,7 +1272,89 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* botCaster) { result.ManaCost = 0; if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_Mez); + std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_HealOverTime); + const std::vector v = botCaster->GatherSpellTargets(); + int targetCount = 0; + + for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if (IsGroupHealOverTimeSpell(botSpellListItr->SpellId)) { + if (!botCaster->IsCommandedSpell()) { + targetCount = 0; + + for (Mob* m : v) { + if (botCaster->IsValidSpellRange(botSpellListItr->SpellId, m) && botCaster->CastChecks(botSpellListItr->SpellId, m, spellType, true, IsGroupBotSpellType(spellType))) { + ++targetCount; + } + } + + if (targetCount < botCaster->GetSpellTypeAEOrGroupTargetCount(spellType)) { + continue; + } + } + + result.SpellId = botSpellListItr->SpellId; + result.SpellIndex = botSpellListItr->SpellIndex; + result.ManaCost = botSpellListItr->ManaCost; + + break; + } + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* botCaster, Mob* tar, uint16 spellType) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (botCaster) { + std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_CompleteHeal); + const std::vector v = botCaster->GatherSpellTargets(); + int targetCount = 0; + + for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if (IsGroupCompleteHealSpell(botSpellListItr->SpellId)) { + if (!botCaster->IsCommandedSpell()) { + targetCount = 0; + + for (Mob* m : v) { + if (botCaster->IsValidSpellRange(botSpellListItr->SpellId, m) && botCaster->CastChecks(botSpellListItr->SpellId, m, spellType, true, IsGroupBotSpellType(spellType))) { + ++targetCount; + } + } + + if (targetCount < botCaster->GetSpellTypeAEOrGroupTargetCount(spellType)) { + continue; + } + } + + result.SpellId = botSpellListItr->SpellId; + result.SpellIndex = botSpellListItr->SpellIndex; + result.ManaCost = botSpellListItr->ManaCost; + + break; + } + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForMez(Bot* botCaster, uint16 spellType) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (botCaster) { + std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_Mez); for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order @@ -2326,102 +1374,101 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* botCaster) { return result; } -BotSpell Bot::GetBestBotSpellForMagicBasedSlow(Bot* botCaster) { - BotSpell result; +Mob* Bot::GetFirstIncomingMobToMez(Bot* botCaster, int16 spellid, uint16 spellType, bool AE) { + Mob* result = nullptr; - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; + if (botCaster && botCaster->GetOwner()) { + int spellRange = (!AE ? botCaster->GetActSpellRange(spellid, spells[spellid].range) : botCaster->GetActSpellRange(spellid, spells[spellid].aoe_range)); + int buff_count = 0; + NPC* npc = nullptr; - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_AttackSpeed); + for (auto& close_mob : botCaster->m_close_mobs) { + buff_count = 0; + npc = close_mob.second->CastToNPC(); - for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if ( - IsSlowSpell(botSpellListItr->SpellId) && - spells[botSpellListItr->SpellId].resist_type == RESIST_MAGIC && - botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) - ) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; - break; + if (!npc) { + continue; } - } - } - return result; -} - -BotSpell Bot::GetBestBotSpellForDiseaseBasedSlow(Bot* botCaster) { - BotSpell result; - - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; - - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_AttackSpeed); - - for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if ( - IsSlowSpell(botSpellListItr->SpellId) && - spells[botSpellListItr->SpellId].resist_type == RESIST_DISEASE && - botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) - ) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; - - break; + if (!botCaster->IsValidMezTarget(botCaster->GetOwner(), npc, spellid)) { + continue; } - } - } - return result; -} + if (AE) { + int targetCount = 0; -Mob* Bot::GetFirstIncomingMobToMez(Bot* botCaster, BotSpell botSpell) { - Mob* result = 0; + for (auto& close_mob : botCaster->m_close_mobs) { + Mob* m = close_mob.second; - if (botCaster && IsMesmerizeSpell(botSpell.SpellId)) { + if (npc == m) { + continue; + } - std::list npc_list; - entity_list.GetNPCList(npc_list); + if (!botCaster->IsValidMezTarget(botCaster->GetOwner(), m, spellid)) { + continue; + } - for(std::list::iterator itr = npc_list.begin(); itr != npc_list.end(); ++itr) { - NPC* npc = *itr; - - if (DistanceSquaredNoZ(npc->GetPosition(), botCaster->GetPosition()) <= botCaster->GetActSpellRange(botSpell.SpellId, spells[botSpell.SpellId].range)) { - if (!npc->IsMezzed()) { - if (botCaster->HasGroup()) { - Group* g = botCaster->GetGroup(); - - if (g) { - for (int counter = 0; counter < g->GroupCount(); counter++) { - if ( - npc->IsOnHatelist(g->members[counter]) && - g->members[counter]->GetTarget() != npc && g->members[counter]->IsEngaged()) { - result = npc; - break; - } - } + if (IsPBAESpell(spellid)) { + if (spellRange < Distance(botCaster->GetPosition(), m->GetPosition())) { + continue; } } + else { + if (spellRange < Distance(m->GetPosition(), npc->GetPosition())) { + continue; + } + } + + if (botCaster->CastChecks(spellid, m, spellType, true, true)) { + ++targetCount; + } + + if (targetCount >= botCaster->GetSpellTypeAEOrGroupTargetCount(spellType)) { + break; + } } + + if (targetCount < botCaster->GetSpellTypeAEOrGroupTargetCount(spellType)) { + continue; + } + + if (zone->random.Int(1, 100) < RuleI(Bots, AEMezChance)) { + botCaster->SetSpellTypeRecastTimer(spellType, RuleI(Bots, MezFailDelay)); + return result; + } + + result = npc; + } + else { + if (spellRange < Distance(botCaster->GetPosition(), npc->GetPosition())) { + continue; + } + + if (!botCaster->CastChecks(spellid, npc, spellType, true)) { + continue; + } + + if (zone->random.Int(1, 100) < RuleI(Bots, MezChance)) { + botCaster->SetSpellTypeRecastTimer(spellType, RuleI(Bots, MezAEFailDelay)); + + return result; + } + + result = npc; } - if (result) - break; + if (result) { + botCaster->SetHasLoS(true); + + return result; + } } } return result; } -BotSpell Bot::GetBestBotMagicianPetSpell(Bot *botCaster) { +BotSpell Bot::GetBestBotMagicianPetSpell(Bot *botCaster, uint16 spellType) { BotSpell result; result.SpellId = 0; @@ -2429,14 +1476,169 @@ BotSpell Bot::GetBestBotMagicianPetSpell(Bot *botCaster) { result.ManaCost = 0; if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_SummonPet); - + std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_SummonPet); std::string petType = GetBotMagicianPetType(botCaster); for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if (IsSummonPetSpell(botSpellListItr->SpellId) && botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { - if (!strncmp(spells[botSpellListItr->SpellId].teleport_zone, petType.c_str(), petType.length())) { + if ( + IsSummonPetSpell(botSpellListItr->SpellId) && + botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) && + !strncmp(spells[botSpellListItr->SpellId].teleport_zone, petType.c_str(), petType.length()) + ) { + result.SpellId = botSpellListItr->SpellId; + result.SpellIndex = botSpellListItr->SpellIndex; + result.ManaCost = botSpellListItr->ManaCost; + + break; + } + } + } + + return result; +} + +std::string Bot::GetBotMagicianPetType(Bot* botCaster) { + std::string result; + + if (botCaster) { + bool epicAllowed = false; + if (RuleB(Bots, AllowMagicianEpicPet)) { + if (botCaster->GetLevel() >= RuleI(Bots, AllowMagicianEpicPetLevel)) { + if (!RuleI(Bots, RequiredMagicianEpicPetItemID)) { + epicAllowed = true; + } + else { + bool has_item = botCaster->HasBotItem(RuleI(Bots, RequiredMagicianEpicPetItemID)) != INVALID_INDEX; + + if (has_item) { + epicAllowed = true; + } + } + } + } + + if (botCaster->GetPetChooserID() > 0) { + switch (botCaster->GetPetChooserID()) { + case SumWater: + result = std::string("SumWater"); + break; + case SumFire: + result = std::string("SumFire"); + break; + case SumAir: + result = std::string("SumAir"); + break; + case SumEarth: + result = std::string("SumEarth"); + break; + case MonsterSum: + result = std::string("MonsterSum"); + break; + case SumMageMultiElement: + if (epicAllowed) { + result = std::string(RuleS(Bots, EpicPetSpellName)); + + if (result.empty()) { + result = "SumMageMultiElement"; + } + } + else { + result = std::string("MonsterSum"); + } + break; + } + } + else { + if (epicAllowed) { + result = std::string(RuleS(Bots, EpicPetSpellName)); + + if (result.empty()) { + result = "SumMageMultiElement"; + } + } + else { + if (botCaster->GetLevel() == 2) { + result = std::string("SumWater"); + } + else if (botCaster->GetLevel() == 3) { + result = std::string("SumFire"); + } + else if (botCaster->GetLevel() == 4) { + result = std::string("SumAir"); + } + else if (botCaster->GetLevel() == 5) { + result = std::string("SumEarth"); + } + else { + int counter; + + if (botCaster->GetLevel() < 30) { + counter = zone->random.Int(1, 4); + } + else { + counter = zone->random.Int(1, 5); + } + + switch (counter) { + case 1: + result = std::string("SumWater"); + break; + case 2: + result = std::string("SumFire"); + break; + case 3: + result = std::string("SumAir"); + break; + case 4: + result = std::string("SumEarth"); + break; + default: + result = std::string("MonsterSum"); + break; + } + } + } + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* botCaster, SpellTargetType targetType, uint16 spellType, bool AE, Mob* tar) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (tar == nullptr) { + tar = botCaster->GetTarget(); + } + + if (botCaster) { + std::list botSpellList = GetBotSpellsForSpellEffectAndTargetType(botCaster, spellType, SE_CurrentHP, targetType); + + for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if (IsPureNukeSpell(botSpellListItr->SpellId) || IsDamageSpell(botSpellListItr->SpellId)) { + if (!AE && IsAnyAESpell(botSpellListItr->SpellId) && !IsGroupSpell(botSpellListItr->SpellId)) { + continue; + } + else if (AE && !IsAnyAESpell(botSpellListItr->SpellId)) { + continue; + } + + if (!IsPBAESpell(botSpellListItr->SpellId) && !botCaster->CastChecks(botSpellListItr->SpellId, tar, spellType, false, IsGroupBotSpellType(spellType))) { + continue; + } + + + if ( + botCaster->IsCommandedSpell() || + !AE || + (AE && botCaster->HasValidAETarget(botCaster, botSpellListItr->SpellId, spellType, tar)) + ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; @@ -2450,114 +1652,7 @@ BotSpell Bot::GetBestBotMagicianPetSpell(Bot *botCaster) { return result; } -std::string Bot::GetBotMagicianPetType(Bot* botCaster) { - std::string result; - - if (botCaster) { - if (botCaster->IsPetChooser()) { - switch(botCaster->GetPetChooserID()) { - case 0: - result = std::string("SumWater"); - break; - case 1: - result = std::string("SumFire"); - break; - case 2: - result = std::string("SumAir"); - break; - case 3: - result = std::string("SumEarth"); - break; - default: - result = std::string("MonsterSum"); - break; - } - } - else { - if (botCaster->GetLevel() == 2) - result = std::string("SumWater"); - else if (botCaster->GetLevel() == 3) - result = std::string("SumFire"); - else if (botCaster->GetLevel() == 4) - result = std::string("SumAir"); - else if (botCaster->GetLevel() == 5) - result = std::string("SumEarth"); - else if (botCaster->GetLevel() < 30) { - // Under level 30 - int counter = zone->random.Int(0, 3); - - switch(counter) { - case 0: - result = std::string("SumWater"); - break; - case 1: - result = std::string("SumFire"); - break; - case 2: - result = std::string("SumAir"); - break; - case 3: - result = std::string("SumEarth"); - break; - default: - result = std::string("MonsterSum"); - break; - } - } - else { - // Over level 30 - int counter = zone->random.Int(0, 4); - - switch(counter) { - case 0: - result = std::string("SumWater"); - break; - case 1: - result = std::string("SumFire"); - break; - case 2: - result = std::string("SumAir"); - break; - case 3: - result = std::string("SumEarth"); - break; - default: - result = std::string("MonsterSum"); - break; - } - } - } - } - - return result; -} - -BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* botCaster, SpellTargetType targetType) { - BotSpell result; - - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; - - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffectAndTargetType(botCaster, SE_CurrentHP, targetType); - - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if ((IsPureNukeSpell(botSpellListItr->SpellId) || IsDamageSpell(botSpellListItr->SpellId)) && botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; - - break; - } - } - } - - return result; -} - -BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType targetType) +BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType targetType, uint16 spellType, bool AE, Mob* tar) { BotSpell result; @@ -2565,19 +1660,40 @@ BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType result.SpellIndex = 0; result.ManaCost = 0; + if (tar == nullptr) { + tar = botCaster->GetTarget(); + } + if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffectAndTargetType(botCaster, SE_Stun, targetType); + std::list botSpellList = GetBotSpellsForSpellEffectAndTargetType(botCaster, spellType, SE_Stun, targetType); for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if (IsStunSpell(botSpellListItr->SpellId) && botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) - { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; - break; + if (IsStunSpell(botSpellListItr->SpellId)) { + if (!AE && IsAnyAESpell(botSpellListItr->SpellId) && !IsGroupSpell(botSpellListItr->SpellId)) { + continue; + } + else if (AE && !IsAnyAESpell(botSpellListItr->SpellId)) { + continue; + } + + if (!IsPBAESpell(botSpellListItr->SpellId) && !botCaster->CastChecks(botSpellListItr->SpellId, tar, spellType, false, IsGroupBotSpellType(spellType))) { + continue; + } + + if ( + botCaster->IsCommandedSpell() || + !AE || + (AE && botCaster->HasValidAETarget(botCaster, botSpellListItr->SpellId, spellType, tar)) + ) { + result.SpellId = botSpellListItr->SpellId; + result.SpellIndex = botSpellListItr->SpellIndex; + result.ManaCost = botSpellListItr->ManaCost; + + break; + } } } } @@ -2585,7 +1701,7 @@ BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType return result; } -BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* botCaster, Mob* target) { +BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* botCaster, Mob* target, uint16 spellType) { BotSpell result; result.SpellId = 0; @@ -2593,50 +1709,74 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* botCaster, Mob* targ result.ManaCost = 0; if (botCaster && target) { + const int lureResisValue = -100; - const int maxTargetResistValue = 300; + + int32 level_mod = (target->GetLevel() - botCaster->GetLevel()) * (target->GetLevel() - botCaster->GetLevel()) / 2; + + if (target->GetLevel() - botCaster->GetLevel() < 0) { + level_mod = -level_mod; + } + const int maxTargetResistValue = botCaster->GetSpellTypeResistLimit(spellType); bool selectLureNuke = false; - if ((target->GetMR() > maxTargetResistValue) && (target->GetCR() > maxTargetResistValue) && (target->GetFR() > maxTargetResistValue)) + if (((target->GetMR() + level_mod) > maxTargetResistValue) && ((target->GetCR() + level_mod) > maxTargetResistValue) && ((target->GetFR() + level_mod) > maxTargetResistValue)) { selectLureNuke = true; + } - std::list botSpellList = GetBotSpellsForSpellEffectAndTargetType(botCaster, SE_CurrentHP, ST_Target); + std::list botSpellList = GetBotSpellsForSpellEffectAndTargetType(botCaster, spellType, SE_CurrentHP, ST_Target); BotSpell firstWizardMagicNukeSpellFound; firstWizardMagicNukeSpellFound.SpellId = 0; firstWizardMagicNukeSpellFound.SpellIndex = 0; firstWizardMagicNukeSpellFound.ManaCost = 0; + bool spellSelected = false; - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - bool spellSelected = false; + if (!botCaster->IsValidSpellRange(botSpellListItr->SpellId, target)) { + continue; + } - if (botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { - if (selectLureNuke && (spells[botSpellListItr->SpellId].resist_difficulty < lureResisValue)) { + if (selectLureNuke && (spells[botSpellListItr->SpellId].resist_difficulty < lureResisValue)) { + if (botCaster->CastChecks(botSpellListItr->SpellId, target, spellType)) { spellSelected = true; } - else if (IsPureNukeSpell(botSpellListItr->SpellId)) { - if (((target->GetMR() < target->GetCR()) || (target->GetMR() < target->GetFR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_MAGIC) - && (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue)) - { - spellSelected = true; - } - else if (((target->GetCR() < target->GetMR()) || (target->GetCR() < target->GetFR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_COLD) - && (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue)) - { - spellSelected = true; - } - else if (((target->GetFR() < target->GetCR()) || (target->GetFR() < target->GetMR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_FIRE) - && (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue)) - { - spellSelected = true; - } - else if ((GetSpellResistType(botSpellListItr->SpellId) == RESIST_MAGIC) && (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue) && !IsStunSpell(botSpellListItr->SpellId)) { - firstWizardMagicNukeSpellFound.SpellId = botSpellListItr->SpellId; - firstWizardMagicNukeSpellFound.SpellIndex = botSpellListItr->SpellIndex; - firstWizardMagicNukeSpellFound.ManaCost = botSpellListItr->ManaCost; - } + } + else if (!selectLureNuke && IsPureNukeSpell(botSpellListItr->SpellId)) { + if ( + ((target->GetMR() < target->GetCR()) || (target->GetMR() < target->GetFR())) && + (GetSpellResistType(botSpellListItr->SpellId) == RESIST_MAGIC) && + (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue) && + botCaster->CastChecks(botSpellListItr->SpellId, target, spellType) + ) { + spellSelected = true; + } + else if ( + ((target->GetCR() < target->GetMR()) || (target->GetCR() < target->GetFR())) && + (GetSpellResistType(botSpellListItr->SpellId) == RESIST_COLD) && + (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue) && + botCaster->CastChecks(botSpellListItr->SpellId, target, spellType) + ) { + spellSelected = true; + } + else if ( + ((target->GetFR() < target->GetCR()) || (target->GetFR() < target->GetMR())) && + (GetSpellResistType(botSpellListItr->SpellId) == RESIST_FIRE) && + (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue) && + botCaster->CastChecks(botSpellListItr->SpellId, target, spellType) + ) { + spellSelected = true; + } + else if ( + (GetSpellResistType(botSpellListItr->SpellId) == RESIST_MAGIC) && + (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue) && + botCaster->CastChecks(botSpellListItr->SpellId, target, spellType) + ) { + firstWizardMagicNukeSpellFound.SpellId = botSpellListItr->SpellId; + firstWizardMagicNukeSpellFound.SpellIndex = botSpellListItr->SpellIndex; + firstWizardMagicNukeSpellFound.ManaCost = botSpellListItr->ManaCost; } } @@ -2649,6 +1789,25 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* botCaster, Mob* targ } } + if (!spellSelected) { + for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + + if (botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { + if (botCaster->CastChecks(botSpellListItr->SpellId, target, spellType)) { + spellSelected = true; + } + } + if (spellSelected) { + result.SpellId = botSpellListItr->SpellId; + result.SpellIndex = botSpellListItr->SpellIndex; + result.ManaCost = botSpellListItr->ManaCost; + + break; + } + } + } + if (result.SpellId == 0) { result = firstWizardMagicNukeSpellFound; } @@ -2671,13 +1830,11 @@ BotSpell Bot::GetDebuffBotSpell(Bot* botCaster, Mob *tar) { std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + if (!IsValidSpellAndLoS(botSpellList[i].spellid, botCaster->HasLoS())) { continue; } - if (((botSpellList[i].type & SpellType_Debuff) || IsDebuffSpell(botSpellList[i].spellid)) + if (((botSpellList[i].type == BotSpellTypes::Debuff) || IsDebuffSpell(botSpellList[i].spellid)) && (!tar->IsImmuneToSpell(botSpellList[i].spellid, botCaster) && tar->CanBuffStack(botSpellList[i].spellid, botCaster->GetLevel(), true) >= 0) && botCaster->CheckSpellRecastTimer(botSpellList[i].spellid)) { @@ -2719,13 +1876,11 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* botCaster, Mob *tar) { std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + if (!IsValidSpellAndLoS(botSpellList[i].spellid, botCaster->HasLoS())) { continue; } - if (((botSpellList[i].type & SpellType_Debuff) || IsResistDebuffSpell(botSpellList[i].spellid)) + if (((botSpellList[i].type == BotSpellTypes::Debuff) || IsResistDebuffSpell(botSpellList[i].spellid)) && ((needsMagicResistDebuff && (IsEffectInSpell(botSpellList[i].spellid, SE_ResistMagic)) || IsEffectInSpell(botSpellList[i].spellid, SE_ResistAll)) || (needsColdResistDebuff && (IsEffectInSpell(botSpellList[i].spellid, SE_ResistCold)) || IsEffectInSpell(botSpellList[i].spellid, SE_ResistAll)) || (needsFireResistDebuff && (IsEffectInSpell(botSpellList[i].spellid, SE_ResistFire)) || IsEffectInSpell(botSpellList[i].spellid, SE_ResistAll)) @@ -2746,109 +1901,82 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* botCaster, Mob *tar) { return result; } -BotSpell Bot::GetBestBotSpellForCure(Bot* botCaster, Mob *tar) { +BotSpell Bot::GetBestBotSpellForCure(Bot* botCaster, Mob* tar, uint16 spellType) { BotSpell_wPriority result; - bool spellSelected = false; result.SpellId = 0; result.SpellIndex = 0; result.ManaCost = 0; - if (!tar) + if (!tar) { return result; + } - int countNeedsCured = 0; - bool isPoisoned = tar->FindType(SE_PoisonCounter); - bool isDiseased = tar->FindType(SE_DiseaseCounter); - bool isCursed = tar->FindType(SE_CurseCounter); - bool isCorrupted = tar->FindType(SE_CorruptionCounter); + if (botCaster) { + std::list botSpellListItr = GetPrioritizedBotSpellsBySpellType(botCaster, spellType, tar); + + if (IsGroupBotSpellType(spellType)) { + const std::vector v = botCaster->GatherGroupSpellTargets(tar); + int countNeedsCured = 0; + uint16 countPoisoned = 0; + uint16 countDiseased = 0; + uint16 countCursed = 0; + uint16 countCorrupted = 0; - if (botCaster && botCaster->AI_HasSpells()) { - std::list cureList = GetPrioritizedBotSpellsBySpellType(botCaster, SpellType_Cure); + for (std::list::iterator itr = botSpellListItr.begin(); itr != botSpellListItr.end(); ++itr) { + if (!IsValidSpell(itr->SpellId) || !IsGroupSpell(itr->SpellId)) { + continue; + } - if (tar->HasGroup()) { - Group *g = tar->GetGroup(); - - if (g) { - for( int i = 0; imembers[i] && !g->members[i]->qglobal) { - if (botCaster->GetNeedsCured(g->members[i])) - countNeedsCured++; + for (Mob* m : v) { + if (botCaster->IsCommandedSpell() || botCaster->GetNeedsCured(m)) { + if (botCaster->CastChecks(itr->SpellId, m, spellType, true, IsGroupBotSpellType(spellType))) { + if (m->FindType(SE_PoisonCounter)) { + ++countPoisoned; + } + if (m->FindType(SE_DiseaseCounter)) { + ++countDiseased; + } + if (m->FindType(SE_CurseCounter)) { + ++countCursed; + } + if (m->FindType(SE_CorruptionCounter)) { + ++countCorrupted; + } + } } } + + if ( + (countPoisoned >= botCaster->GetSpellTypeAEOrGroupTargetCount(spellType) && IsEffectInSpell(itr->SpellId, SE_PoisonCounter)) || + (countDiseased >= botCaster->GetSpellTypeAEOrGroupTargetCount(spellType) && IsEffectInSpell(itr->SpellId, SE_DiseaseCounter)) || + (countCursed >= botCaster->GetSpellTypeAEOrGroupTargetCount(spellType) && IsEffectInSpell(itr->SpellId, SE_CurseCounter)) || + (countCorrupted >= botCaster->GetSpellTypeAEOrGroupTargetCount(spellType) && IsEffectInSpell(itr->SpellId, SE_CorruptionCounter)) + ) { + result.SpellId = itr->SpellId; + result.SpellIndex = itr->SpellIndex; + result.ManaCost = itr->ManaCost; + + break; + } } } - - //Check for group cure first - if (countNeedsCured > 2) { - for (std::list::iterator itr = cureList.begin(); itr != cureList.end(); ++itr) { - BotSpell selectedBotSpell = *itr; - - if (IsGroupSpell(itr->SpellId) && botCaster->CheckSpellRecastTimer(selectedBotSpell.SpellId)) { - if (selectedBotSpell.SpellId == 0) - continue; - - if (isPoisoned && IsEffectInSpell(selectedBotSpell.SpellId, SE_PoisonCounter)) { - spellSelected = true; - } - else if (isDiseased && IsEffectInSpell(selectedBotSpell.SpellId, SE_DiseaseCounter)) { - spellSelected = true; - } - else if (isCursed && IsEffectInSpell(selectedBotSpell.SpellId, SE_CurseCounter)) { - spellSelected = true; - } - else if (isCorrupted && IsEffectInSpell(selectedBotSpell.SpellId, SE_CorruptionCounter)) { - spellSelected = true; - } - else if (IsEffectInSpell(selectedBotSpell.SpellId, SE_DispelDetrimental)) { - spellSelected = true; - } - - if (spellSelected) - { - result.SpellId = selectedBotSpell.SpellId; - result.SpellIndex = selectedBotSpell.SpellIndex; - result.ManaCost = selectedBotSpell.ManaCost; - - break; - } + else { + for (std::list::iterator itr = botSpellListItr.begin(); itr != botSpellListItr.end(); ++itr) { + if (!IsValidSpell(itr->SpellId) || IsGroupSpell(itr->SpellId)) { + continue; } - } - } - //no group cure for target- try to find single target spell - if (!spellSelected) { - for(std::list::iterator itr = cureList.begin(); itr != cureList.end(); ++itr) { - BotSpell selectedBotSpell = *itr; - - if (botCaster->CheckSpellRecastTimer(selectedBotSpell.SpellId)) { - if (selectedBotSpell.SpellId == 0) - continue; - - if (isPoisoned && IsEffectInSpell(selectedBotSpell.SpellId, SE_PoisonCounter)) { - spellSelected = true; - } - else if (isDiseased && IsEffectInSpell(selectedBotSpell.SpellId, SE_DiseaseCounter)) { - spellSelected = true; - } - else if (isCursed && IsEffectInSpell(selectedBotSpell.SpellId, SE_CurseCounter)) { - spellSelected = true; - } - else if (isCorrupted && IsEffectInSpell(selectedBotSpell.SpellId, SE_CorruptionCounter)) { - spellSelected = true; - } - else if (IsEffectInSpell(selectedBotSpell.SpellId, SE_DispelDetrimental)) { - spellSelected = true; - } - - if (spellSelected) - { - result.SpellId = selectedBotSpell.SpellId; - result.SpellIndex = selectedBotSpell.SpellIndex; - result.ManaCost = selectedBotSpell.ManaCost; - - break; - } + if ( + tar->FindType(SE_PoisonCounter) && IsEffectInSpell(itr->SpellId, SE_PoisonCounter) || + tar->FindType(SE_DiseaseCounter) && IsEffectInSpell(itr->SpellId, SE_DiseaseCounter) || + tar->FindType(SE_CurseCounter) && IsEffectInSpell(itr->SpellId, SE_CurseCounter) || + tar->FindType(SE_CorruptionCounter) && IsEffectInSpell(itr->SpellId, SE_CorruptionCounter) + ) { + result.SpellId = itr->SpellId; + result.SpellIndex = itr->SpellIndex; + result.ManaCost = itr->ManaCost; + break; } } } @@ -2857,108 +1985,69 @@ BotSpell Bot::GetBestBotSpellForCure(Bot* botCaster, Mob *tar) { return result; } -uint8 Bot::GetChanceToCastBySpellType(uint32 spellType) +uint8 Bot::GetChanceToCastBySpellType(uint16 spellType) //TODO bot rewrite - adjust, move AEs to own rule? { - uint8 spell_type_index = SPELL_TYPE_COUNT; switch (spellType) { - case SpellType_Nuke: - spell_type_index = spellTypeIndexNuke; - break; - case SpellType_Heal: - spell_type_index = spellTypeIndexHeal; - break; - case SpellType_Root: - spell_type_index = spellTypeIndexRoot; - break; - case SpellType_Buff: - spell_type_index = spellTypeIndexBuff; - break; - case SpellType_Escape: - spell_type_index = spellTypeIndexEscape; - break; - case SpellType_Pet: - spell_type_index = spellTypeIndexPet; - break; - case SpellType_Lifetap: - spell_type_index = spellTypeIndexLifetap; - break; - case SpellType_Snare: - spell_type_index = spellTypeIndexSnare; - break; - case SpellType_DOT: - spell_type_index = spellTypeIndexDot; - break; - case SpellType_Dispel: - spell_type_index = spellTypeIndexDispel; - break; - case SpellType_InCombatBuff: - spell_type_index = spellTypeIndexInCombatBuff; - break; - case SpellType_Mez: - spell_type_index = spellTypeIndexMez; - break; - case SpellType_Charm: - spell_type_index = spellTypeIndexCharm; - break; - case SpellType_Slow: - spell_type_index = spellTypeIndexSlow; - break; - case SpellType_Debuff: - spell_type_index = spellTypeIndexDebuff; - break; - case SpellType_Cure: - spell_type_index = spellTypeIndexCure; - break; - case SpellType_Resurrect: - spell_type_index = spellTypeIndexResurrect; - break; - case SpellType_HateRedux: - spell_type_index = spellTypeIndexHateRedux; - break; - case SpellType_InCombatBuffSong: - spell_type_index = spellTypeIndexInCombatBuffSong; - break; - case SpellType_OutOfCombatBuffSong: - spell_type_index = spellTypeIndexOutOfCombatBuffSong; - break; - case SpellType_PreCombatBuff: - spell_type_index = spellTypeIndexPreCombatBuff; - break; - case SpellType_PreCombatBuffSong: - spell_type_index = spellTypeIndexPreCombatBuffSong; - break; - default: - break; + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::AEStun: + case BotSpellTypes::Nuke: + return RuleI(Bots, PercentChanceToCastNuke); + case BotSpellTypes::Root: + return RuleI(Bots, PercentChanceToCastRoot); + case BotSpellTypes::Buff: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::DamageShields: + return RuleI(Bots, PercentChanceToCastBuff); + case BotSpellTypes::Escape: + return RuleI(Bots, PercentChanceToCastEscape); + case BotSpellTypes::Lifetap: + return RuleI(Bots, PercentChanceToCastLifetap); + case BotSpellTypes::AESnare: + case BotSpellTypes::Snare: + return RuleI(Bots, PercentChanceToCastSnare); + case BotSpellTypes::DOT: + return RuleI(Bots, PercentChanceToCastDOT); + case BotSpellTypes::Dispel: + return RuleI(Bots, PercentChanceToCastDispel); + case BotSpellTypes::InCombatBuff: + return RuleI(Bots, PercentChanceToCastInCombatBuff); + case BotSpellTypes::AEMez: + case BotSpellTypes::Mez: + return RuleI(Bots, PercentChanceToCastMez); + case BotSpellTypes::AESlow: + case BotSpellTypes::Slow: + return RuleI(Bots, PercentChanceToCastSlow); + case BotSpellTypes::AEDebuff: + case BotSpellTypes::Debuff: + return RuleI(Bots, PercentChanceToCastDebuff); + case BotSpellTypes::Cure: + return RuleI(Bots, PercentChanceToCastCure); + case BotSpellTypes::HateRedux: + return RuleI(Bots, PercentChanceToCastHateRedux); + case BotSpellTypes::AEFear: + case BotSpellTypes::Fear: + return RuleI(Bots, PercentChanceToCastFear); + case BotSpellTypes::RegularHeal: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + return RuleI(Bots, PercentChanceToCastHeal); + default: + return RuleI(Bots, PercentChanceToCastOtherType); } - if (spell_type_index >= SPELL_TYPE_COUNT) - return 0; - - uint8 class_index = GetClass(); - if (class_index > Class::Berserker || class_index < Class::Warrior) - return 0; - --class_index; - - uint32 stance_id = GetBotStance(); - if (!Stance::IsValid(stance_id)) { - return 0; - } - - uint8 stance_index = Stance::GetIndex(stance_id); - uint8 type_index = nHSND; - - if (HasGroup()) { - if (IsGroupHealer()/* || IsRaidHealer()*/) - type_index |= pH; - if (IsGroupSlower()/* || IsRaidSlower()*/) - type_index |= pS; - if (IsGroupNuker()/* || IsRaidNuker()*/) - type_index |= pN; - if (IsGroupDoter()/* || IsRaidDoter()*/) - type_index |= pD; - } - - return database.botdb.GetSpellCastingChance(spell_type_index, class_index, stance_index, type_index); + return RuleI(Bots, PercentChanceToCastOtherType); } bool Bot::AI_AddBotSpells(uint32 bot_spell_id) { @@ -3340,7 +2429,7 @@ DBbotspells_Struct* ZoneDatabase::GetBotSpells(uint32 bot_spell_id) entry.bucket_comparison = e.bucket_comparison; // some spell types don't make much since to be priority 0, so fix that - if (!(entry.type & SPELL_TYPES_INNATE) && entry.priority == 0) { + if (!BOT_SPELL_TYPES_INNATE(entry.type) && entry.priority == 0) { entry.priority = 1; } @@ -3504,6 +2593,460 @@ bool Bot::IsValidSpellRange(uint16 spell_id, Mob const* tar) { if (spellrange >= DistanceSquared(m_Position, tar->GetPosition())) { return true; } + + spellrange = (GetActSpellRange(spell_id, spells[spell_id].aoe_range) * GetActSpellRange(spell_id, spells[spell_id].aoe_range)); + if (spellrange >= DistanceSquared(m_Position, tar->GetPosition())) { + return true; + } } + return false; } + +BotSpell Bot::GetBestBotSpellForNukeByBodyType(Bot* botCaster, uint8 bodyType, uint16 spellType, bool AE, Mob* tar) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (!botCaster || !bodyType) { + return result; + } + + if (tar == nullptr) { + tar = botCaster->GetTarget(); + } + + switch (bodyType) { + case BodyType::Undead: + case BodyType::SummonedUndead: + case BodyType::Vampire: + result = GetBestBotSpellForNukeByTargetType(botCaster, (!AE ? ST_Undead : ST_UndeadAE), spellType, AE, tar); + break; + case BodyType::Summoned: + case BodyType::Summoned2: + case BodyType::Summoned3: + result = GetBestBotSpellForNukeByTargetType(botCaster, (!AE ? ST_Summoned : ST_SummonedAE), spellType, AE, tar); + break; + case BodyType::Animal: + result = GetBestBotSpellForNukeByTargetType(botCaster, ST_Animal, spellType, AE, tar); + break; + case BodyType::Plant: + result = GetBestBotSpellForNukeByTargetType(botCaster, ST_Plant, spellType, AE, tar); + break; + case BodyType::Giant: + result = GetBestBotSpellForNukeByTargetType(botCaster, ST_Giant, spellType, AE, tar); + break; + case BodyType::Dragon: + result = GetBestBotSpellForNukeByTargetType(botCaster, ST_Dragon, spellType, AE, tar); + break; + default: + break; + } + + return result; +} + +void Bot::CheckBotSpells() { + bool valid = false; + uint16 correctType; + auto spellList = BotSpellsEntriesRepository::All(content_db); + uint16 spell_id; + + for (const auto& s : spellList) { + if (!IsValidSpell(s.spell_id)) { + LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id); //deleteme + continue; + } + + spell_id = s.spell_id; + + if (spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] >= 255) { + LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id); //deleteme + } + else { + if (spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] > s.minlevel) { + LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}." + , GetSpellName(spell_id) + , spell_id + , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX) + , s.npc_spells_id + , s.minlevel + ); //deleteme + + LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]" + , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , spell_id + , s.npc_spells_id + , GetSpellName(spell_id) + , spell_id + , s.minlevel + , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX) + , s.npc_spells_id + ); //deleteme + } + + if (spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] < s.minlevel) { + LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}." + , GetSpellName(spell_id) + , spell_id + , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX) + , s.npc_spells_id + , s.minlevel + ); //deleteme + + LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]" + , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , spell_id + , s.npc_spells_id + , GetSpellName(spell_id) + , spell_id + , s.minlevel + , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX) + , s.npc_spells_id + ); //deleteme + } + + + if (spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] > s.maxlevel) { + LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}." + , GetSpellName(spell_id) + , spell_id + , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX) + , s.npc_spells_id + , s.maxlevel + ); //deleteme + } + } + + correctType = UINT16_MAX; + valid = false; + + + switch (s.type) { + case BotSpellTypes::Nuke: //DONE + if (IsAnyNukeOrStunSpell(spell_id) && !IsEffectInSpell(spell_id, SE_Root) && !IsDebuffSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::RegularHeal: //DONE + //if (IsAnyHealSpell(spell_id) && !IsEscapeSpell(spell_id) && (IsRegularPetHealSpell(spell_id) || !IsCureSpell(spell_id))) { + if (IsAnyHealSpell(spell_id) && !IsEscapeSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::Root: //DONE + if (IsEffectInSpell(spell_id, SE_Root)) { + valid = true; + break; + } + break; + case BotSpellTypes::Buff: //DONE + if (IsAnyBuffSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::Pet: //DONE + if (IsSummonPetSpell(spell_id) || IsEffectInSpell(spell_id, SE_TemporaryPets)) { + valid = true; + break; + } + break; + case BotSpellTypes::Lifetap: //DONE + if (IsLifetapSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::Snare: //DONE + if (IsEffectInSpell(spell_id, SE_MovementSpeed) && IsDetrimentalSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::DOT: //DONE + if (IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::Dispel: //DONE + if (IsDispelSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::InCombatBuff: //DONE + if ( + IsSelfConversionSpell(spell_id) || + IsAnyBuffSpell(spell_id) || + (IsEffectInSpell(spell_id, SE_Hate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_Hate)] > 0) || + (IsEffectInSpell(spell_id, SE_InstantHate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_InstantHate)] > 0) + ) { + valid = true; + break; + } + break; + case BotSpellTypes::Mez: //DONE + if (IsMesmerizeSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::Charm: //DONE + if (IsCharmSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::Slow: //DONE + if (IsSlowSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::Debuff: //DONE + if (IsDebuffSpell(spell_id) && !IsEscapeSpell(spell_id) && !IsHateReduxSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::Cure: //DONE + if (IsCureSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::PreCombatBuff: //DONE + if ( + IsBuffSpell(spell_id) && + IsBeneficialSpell(spell_id) && + !IsBardSong(spell_id) && + !IsEscapeSpell(spell_id) && + (!IsSummonPetSpell(spell_id) && !IsEffectInSpell(spell_id, SE_TemporaryPets)) + ) { + valid = true; + break; + } + break; + case BotSpellTypes::InCombatBuffSong: //DONE + case BotSpellTypes::OutOfCombatBuffSong: //DONE + case BotSpellTypes::PreCombatBuffSong: //DONE + if ( + IsBuffSpell(spell_id) && + IsBeneficialSpell(spell_id) && + IsBardSong(spell_id) && + !IsEscapeSpell(spell_id) && + (!IsSummonPetSpell(spell_id) && !IsEffectInSpell(spell_id, SE_TemporaryPets)) + ) { + valid = true; + break; + } + break; + case BotSpellTypes::Fear: //DONE + if (IsFearSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::Escape: + if (IsEscapeSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::HateRedux: + if (IsHateReduxSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::Resurrect: + if (IsEffectInSpell(spell_id, SE_Revive)) { + valid = true; + break; + } + break; + default: + break; + + } + + if (IsAnyNukeOrStunSpell(spell_id) && !IsEffectInSpell(spell_id, SE_Root) && !IsDebuffSpell(spell_id)) { + correctType = BotSpellTypes::Nuke; + } + //else if (IsAnyHealSpell(spell_id) && !IsEscapeSpell(spell_id) && (IsRegularPetHealSpell(spell_id) || !IsCureSpell(spell_id))) { + else if (IsAnyHealSpell(spell_id) && !IsEscapeSpell(spell_id)) { + correctType = BotSpellTypes::RegularHeal; + } + else if (IsEffectInSpell(spell_id, SE_Root)) { + correctType = BotSpellTypes::Root; + } + else if (IsAnyBuffSpell(spell_id)) { + correctType = BotSpellTypes::Buff; + } + else if (IsSummonPetSpell(spell_id) || IsEffectInSpell(spell_id, SE_TemporaryPets)) { + correctType = BotSpellTypes::Pet; + } + else if (IsLifetapSpell(spell_id)) { + correctType = BotSpellTypes::Lifetap; + } + else if (IsEffectInSpell(spell_id, SE_MovementSpeed) && IsDetrimentalSpell(spell_id)) { + correctType = BotSpellTypes::Snare; + } + else if (IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id)) { + correctType = BotSpellTypes::DOT; + } + else if (IsDispelSpell(spell_id)) { + correctType = BotSpellTypes::Dispel; + } + else if ( + IsSelfConversionSpell(spell_id) || + IsAnyBuffSpell(spell_id) || + (IsEffectInSpell(spell_id, SE_Hate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_Hate)] > 0) || + (IsEffectInSpell(spell_id, SE_InstantHate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_InstantHate)] > 0) + ) { + correctType = BotSpellTypes::InCombatBuff; + } + else if (IsMesmerizeSpell(spell_id)) { + correctType = BotSpellTypes::Mez; + } + else if (IsCharmSpell(spell_id)) { + correctType = BotSpellTypes::Charm; + } + else if (IsSlowSpell(spell_id)) { + correctType = BotSpellTypes::Slow; + } + else if (IsDebuffSpell(spell_id) && !IsEscapeSpell(spell_id) && !IsHateReduxSpell(spell_id)) { + correctType = BotSpellTypes::Debuff; + } + else if (IsCureSpell(spell_id)) { + correctType = BotSpellTypes::Cure; + } + else if ( + IsBuffSpell(spell_id) && + IsBeneficialSpell(spell_id) && + IsBardSong(spell_id) && + !IsEscapeSpell(spell_id) && + (!IsSummonPetSpell(spell_id) && !IsEffectInSpell(spell_id, SE_TemporaryPets)) + ) { + if ( + s.type == BotSpellTypes::InCombatBuffSong || + s.type == BotSpellTypes::OutOfCombatBuffSong || + s.type == BotSpellTypes::PreCombatBuffSong + ) { + correctType = s.type; + } + else { + correctType = BotSpellTypes::OutOfCombatBuffSong; + } + } + else if ( + IsBuffSpell(spell_id) && + IsBeneficialSpell(spell_id) && + !IsBardSong(spell_id) && + !IsEscapeSpell(spell_id) && + (!IsSummonPetSpell(spell_id) && !IsEffectInSpell(spell_id, SE_TemporaryPets)) + ) { + correctType = BotSpellTypes::PreCombatBuff; + } + else if (IsFearSpell(spell_id)) { + correctType = BotSpellTypes::Fear; + } + else if (IsEscapeSpell(spell_id)) { + correctType = BotSpellTypes::Escape; + } + else if (IsHateReduxSpell(spell_id)) { + correctType = BotSpellTypes::HateRedux; + } + else if (IsEffectInSpell(spell_id, SE_Revive)) { + correctType = BotSpellTypes::Resurrect; + } + + if (!valid || (correctType == UINT16_MAX) || (s.type != correctType)) { + LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]" + , GetSpellName(spell_id) + , spell_id + , GetSpellTypeNameByID(s.type) + , s.type + , GetSpellTypeNameByID(correctType) + , correctType + ); //deleteme + LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spellid` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]" + , correctType + , spell_id + , GetSpellName(spell_id) + , spell_id + , GetSpellTypeNameByID(s.type) + , s.type + , GetSpellTypeNameByID(correctType) + , correctType + ); //deleteme + } + } +} + +BotSpell Bot::GetBestBotSpellForRez(Bot* botCaster, Mob* target, uint16 spellType) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (botCaster) { + std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_Revive); + + for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if ( + IsResurrectSpell(botSpellListItr->SpellId) && + botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) + ) { + result.SpellId = botSpellListItr->SpellId; + result.SpellIndex = botSpellListItr->SpellIndex; + result.ManaCost = botSpellListItr->ManaCost; + + break; + } + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForCharm(Bot* botCaster, Mob* target, uint16 spellType) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (botCaster) { + std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_Charm); + + for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + // Assuming all the spells have been loaded into this list by level and in descending order + if ( + IsCharmSpell(botSpellListItr->SpellId) && + botCaster->CastChecks(botSpellListItr->SpellId, target, spellType) + ) { + result.SpellId = botSpellListItr->SpellId; + result.SpellIndex = botSpellListItr->SpellIndex; + result.ManaCost = botSpellListItr->ManaCost; + + break; + } + } + } + + return result; +} diff --git a/zone/client.cpp b/zone/client.cpp index d0a8d97b0..a5bc2f387 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -51,6 +51,7 @@ extern volatile bool RunLoops; #include "water_map.h" #include "bot_command.h" #include "string_ids.h" +#include "dialogue_window.h" #include "guild_mgr.h" #include "quest_parser_collection.h" @@ -775,6 +776,8 @@ bool Client::Save(uint8 iCommitNow) { database.SaveCharacterEXPModifier(this); + database.botdb.SaveBotSettings(this); + return true; } @@ -5871,7 +5874,7 @@ void Client::SuspendMinion(int value) { m_suspendedminion.SpellID = SpellID; - m_suspendedminion.HP = CurrentPet->GetHP();; + m_suspendedminion.HP = CurrentPet->GetHP(); m_suspendedminion.Mana = CurrentPet->GetMana(); m_suspendedminion.petpower = CurrentPet->GetPetPower(); @@ -12896,11 +12899,11 @@ void Client::AddMoneyToPPWithOverflow(uint64 copper, bool update_client) SaveCurrency(); LogDebug("Client::AddMoneyToPPWithOverflow() [{}] should have: plat:[{}] gold:[{}] silver:[{}] copper:[{}]", - GetName(), - m_pp.platinum, - m_pp.gold, - m_pp.silver, - m_pp.copper + GetName(), + m_pp.platinum, + m_pp.gold, + m_pp.silver, + m_pp.copper ); } @@ -13056,8 +13059,8 @@ void Client::ClientToNpcAggroProcess() { if (zone->CanDoCombat() && !GetFeigned() && m_client_npc_aggro_scan_timer.Check()) { int npc_scan_count = 0; - for (auto &close_mob: GetCloseMobList()) { - Mob *mob = close_mob.second; + for (auto& close_mob : GetCloseMobList()) { + Mob* mob = close_mob.second; if (!mob) { continue; } @@ -13075,3 +13078,259 @@ void Client::ClientToNpcAggroProcess() LogAggro("Checking Reverse Aggro (client->npc) scanned_npcs ([{}])", npc_scan_count); } } + +void Client::LoadDefaultBotSettings() { + // Only illusion block supported currently + SetBotSetting(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock, GetDefaultBotSettings(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock)); + LogBotSettingsDetail("{} says, 'Setting default {} [{}] to [{}]'", GetCleanName(), CastToBot()->GetBotSettingCategoryName(BotBaseSettings::IllusionBlock), BotBaseSettings::IllusionBlock, GetDefaultBotSettings(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock)); //deleteme + + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + BotSpellSettings_Struct t; + + t.spellType = i; + t.shortName = GetSpellTypeShortNameByID(i); + t.name = GetSpellTypeNameByID(i); + t.hold = GetDefaultSpellHold(i); + t.delay = GetDefaultSpellDelay(i); + t.minThreshold = GetDefaultSpellMinThreshold(i); + t.maxThreshold = GetDefaultSpellMaxThreshold(i); + + _spellSettings.push_back(t); + + LogBotSettingsDetail("{} says, 'Setting defaults for {} ({}) [#{}]'", GetCleanName(), t.name, t.shortName, t.spellType); //deleteme + LogBotSettingsDetail("{} says, 'Hold = [{}] | Delay = [{}ms] | MinThreshold = [{}\%] | MaxThreshold = [{}\%]'", GetCleanName(), GetDefaultSpellHold(i), GetDefaultSpellDelay(i), GetDefaultSpellMinThreshold(i), GetDefaultSpellMaxThreshold(i)); //deleteme + } +} + +int Client::GetDefaultBotSettings(uint8 settingType, uint16 botSetting) { + switch (settingType) { + case BotSettingCategories::BaseSetting: + return false; + case BotSettingCategories::SpellHold: + return GetDefaultSpellHold(botSetting); + case BotSettingCategories::SpellDelay: + return GetDefaultSpellDelay(botSetting); + case BotSettingCategories::SpellMinThreshold: + return GetDefaultSpellMinThreshold(botSetting); + case BotSettingCategories::SpellMaxThreshold: + return GetDefaultSpellMaxThreshold(botSetting); + } +} + +int Client::GetBotSetting(uint8 settingType, uint16 botSetting) { + switch (settingType) { + case BotSettingCategories::SpellHold: + return GetSpellHold(botSetting); + case BotSettingCategories::SpellDelay: + return GetSpellDelay(botSetting); + case BotSettingCategories::SpellMinThreshold: + return GetSpellMinThreshold(botSetting); + case BotSettingCategories::SpellMaxThreshold: + return GetSpellMaxThreshold(botSetting); + } +} + +void Client::SetBotSetting(uint8 settingType, uint16 botSetting, uint32 settingValue) { + switch (settingType) { + case BotSettingCategories::BaseSetting: + SetBaseSetting(botSetting, settingValue); + break; + case BotSettingCategories::SpellHold: + SetSpellHold(botSetting, settingValue); + break; + case BotSettingCategories::SpellDelay: + SetSpellDelay(botSetting, settingValue); + break; + case BotSettingCategories::SpellMinThreshold: + SetSpellMinThreshold(botSetting, settingValue); + break; + case BotSettingCategories::SpellMaxThreshold: + SetSpellMaxThreshold(botSetting, settingValue); + break; + } +} + +std::string Client::SendCommandHelpWindow( + Client* c, + std::vector description, + std::vector notes, + std::vector example_format, + std::vector examples_one, std::vector examples_two, std::vector examples_three, + std::vector actionables, + std::vector options, + std::vector options_one, std::vector options_two, std::vector options_three +) { + + unsigned stringLength = 0; + unsigned currentPlace = 0; + uint16 maxLength = RuleI(Command, MaxHelpLineLength); //character length of a line before splitting in to multiple lines + const std::string& description_color = RuleS(Command, DescriptionColor); + const std::string& description_header_color = RuleS(Command, DescriptionHeaderColor); + const std::string& alt_description_color = RuleS(Command, AltDescriptionColor); + const std::string& note_color = RuleS(Command, NoteColor); + const std::string& note_header_color = RuleS(Command, NoteHeaderColor); + const std::string& alt_note_color = RuleS(Command, AltNoteColor); + const std::string& example_color = RuleS(Command, ExampleColor); + const std::string& example_header_color = RuleS(Command, ExampleHeaderColor); + const std::string& sub_example_color = RuleS(Command, SubExampleColor); + const std::string& alt_example_color = RuleS(Command, AltExampleColor); + const std::string& sub_alt_example_color = RuleS(Command, SubAltExampleColor); + const std::string& option_color = RuleS(Command, OptionColor); + const std::string& option_header_color = RuleS(Command, OptionHeaderColor); + const std::string& sub_option_color = RuleS(Command, SubOptionColor); + const std::string& alt_option_color = RuleS(Command, AltOptionColor); + const std::string& sub_alt_option_color = RuleS(Command, SubAltOptionColor); + const std::string& actionable_color = RuleS(Command, ActionableColor); + const std::string& actionable_header_color = RuleS(Command, ActionableHeaderColor); + const std::string& alt_actionable_color = RuleS(Command, AltActionableColor); + const std::string& header_color = RuleS(Command, HeaderColor); + const std::string& secondary_header_color = RuleS(Command, SecondaryHeaderColor); + const std::string& alt_header_color = RuleS(Command, AltHeaderColor); + const std::string& filler_line_color = RuleS(Command, FillerLineColor); + + + + std::string fillerLine = "--------------------------------------------------------------------"; + std::string fillerDia = DialogueWindow::TableRow(DialogueWindow::TableCell(fmt::format("{}", DialogueWindow::ColorMessage(filler_line_color, fillerLine)))); + std::string breakLine = DialogueWindow::Break(); + std::string indent = "        "; + std::string bullet = "- "; + std::string popup_text = ""; + + /* + maxLength is how long you want lines to be before splitting them. This will look for the last space before the count and split there so words are not split mid sentence + Any SplitCommandHelpText can be be have the first string from a vector differ in color from the next strings by setting secondColor to true and assigning a color. + ex: SplitCommandHelpText(examples_one, example_color, maxLength, true, alt_example_color) + - This will make the first string from examples_one vector be the color of example_color and all following strings the color of alt_example_color + ex: SplitCommandHelpText(examples_one, example_color, maxLength) + - This will apply the color example_color to everything in examples_one vector + */ + + if (!description.empty()) { + popup_text += GetCommandHelpHeader(description_header_color, "[Description]"); + popup_text += SplitCommandHelpText(description, description_color, maxLength, true, alt_description_color); + } + + if (!notes.empty()) { + popup_text += breakLine; + popup_text += breakLine; + popup_text += GetCommandHelpHeader(note_header_color, "[Notes]"); + popup_text += SplitCommandHelpText(notes, note_color, maxLength, true, alt_note_color); + } + + if (!example_format.empty()) { + popup_text += fillerDia; + popup_text += GetCommandHelpHeader(example_header_color, "[Examples]"); + popup_text += SplitCommandHelpText(example_format, example_color, maxLength, true, alt_example_color); + } + + if (!examples_one.empty()) { + popup_text += breakLine; + popup_text += breakLine; + popup_text += SplitCommandHelpText(examples_one, sub_example_color, maxLength, true, sub_alt_example_color); + } + + if (!examples_two.empty()) { + popup_text += SplitCommandHelpText(examples_two, sub_example_color, maxLength, true, sub_alt_example_color); + } + + if (!examples_three.empty()) { + popup_text += SplitCommandHelpText(examples_three, sub_example_color, maxLength, true, sub_alt_example_color); + } + + if (!options.empty()) { + popup_text += fillerDia; + popup_text += GetCommandHelpHeader(option_header_color, "[Options]"); + popup_text += SplitCommandHelpText(options, option_color, maxLength, true, alt_option_color); + } + + if (!options_one.empty()) { + popup_text += breakLine; + popup_text += breakLine; + popup_text += SplitCommandHelpText(options_one, sub_option_color, maxLength, true, sub_alt_option_color); + } + + if (!options_two.empty()) { + popup_text += SplitCommandHelpText(options_two, sub_option_color, maxLength, true, sub_alt_option_color); + } + + if (!options_three.empty()) { + popup_text += SplitCommandHelpText(options_three, secondary_header_color, maxLength, true, sub_alt_option_color); + } + + if (!actionables.empty()) { + popup_text += fillerDia; + popup_text += GetCommandHelpHeader(actionable_header_color, "[Actionables]"); + popup_text += SplitCommandHelpText(actionables, actionable_color, maxLength, true, alt_actionable_color); + } + + popup_text = DialogueWindow::Table(popup_text); + + return popup_text; +} + +std::string Client::GetCommandHelpHeader(std::string color, std::string header) { + std::string returnText = DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(color, header) + ) + ) + ); + + return returnText; +} + +std::string Client::SplitCommandHelpText(std::vector msg, std::string color, uint16 maxLength, bool secondColor, std::string secondaryColor) { + std::string returnText; + + for (int i = 0; i < msg.size(); i++) { + std::vector msg_split; + int stringLength = msg[i].length() + 1; + int endCount = 0; + int newCount = 0; + int splitCount = 0; + + for (int x = 0; x < stringLength; x = endCount) { + endCount = std::min(int(stringLength), (int(x) + std::min(int(stringLength), int(maxLength)))); + + if ((stringLength - (x + 1)) > maxLength) { + for (int y = endCount; y >= x; --y) { + if (msg[i][y] == ' ') { + splitCount = y - x; + msg_split.emplace_back(msg[i].substr(x, splitCount)); + endCount = y + 1; + + break; + } + + if (y == x) { + msg_split.emplace_back(msg[i].substr(x, maxLength)); + + break; + } + } + } + else { + msg_split.emplace_back(msg[i].substr(x, (stringLength - 1) - x)); + + break; + } + } + + for (const auto& s : msg_split) { + returnText += DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(((secondColor && i == 0) ? color : secondaryColor), s) + ) + ) + ); + + } + } + + return returnText; +} diff --git a/zone/client.h b/zone/client.h index 5acc388b3..71310dd84 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1250,6 +1250,20 @@ public: PendingTranslocate_Struct PendingTranslocateData; void SendOPTranslocateConfirm(Mob *Caster, uint16 SpellID); + // Help Window + std::string SendCommandHelpWindow( + Client* c, + std::vector description, + std::vector notes, + std::vector example_format, + std::vector examples_one, std::vector examples_two, std::vector examples_three, + std::vector actionables, + std::vector options, + std::vector options_one, std::vector options_two, std::vector options_three + ); + std::string GetCommandHelpHeader(std::string color, std::string header); + std::string SplitCommandHelpText(std::vector msg, std::string color, uint16 maxLength, bool secondColor = false, std::string secondaryColor = ""); + // Task System Methods void LoadClientTaskState(); void RemoveClientTaskState(); @@ -2219,11 +2233,29 @@ public: void CampAllBots(uint8 class_id = Class::None); void SpawnRaidBotsOnConnect(Raid* raid); + void LoadDefaultBotSettings(); + int GetDefaultBotSettings(uint8 settingType, uint16 botSetting); + int GetBotSetting(uint8 settingType, uint16 botSetting); + void SetBotSetting(uint8 settingType, uint16 botSetting, uint32 settingValue); + private: bool bot_owner_options[_booCount]; bool m_bot_pulling; bool m_bot_precombat; + uint8 fast_heal_threshold; + uint8 heal_threshold; + uint8 complete_heal_threshold; + uint8 hot_heal_threshold; + uint32 fast_heal_delay; + uint32 heal_delay; + uint32 complete_heal_delay; + uint32 hot_heal_delay; + uint32 cure_delay; + uint8 cure_min_threshold; + uint8 cure_threshold; + bool illusion_block; + bool CanTradeFVNoDropItem(); void SendMobPositions(); void PlayerTradeEventLog(Trade *t, Trade *t2); diff --git a/zone/client_bot.cpp b/zone/client_bot.cpp index 1f3f809dd..bfe4cdb58 100644 --- a/zone/client_bot.cpp +++ b/zone/client_bot.cpp @@ -19,6 +19,10 @@ uint32 Client::GetBotCreationLimit(uint8 class_id) { uint32 bot_creation_limit = RuleI(Bots, CreationLimit); + if (Admin() >= RuleI(Bots, MinStatusToBypassCreateLimit)) { + return RuleI(Bots, MinStatusToBypassCreateLimit); + } + const auto bucket_name = fmt::format( "bot_creation_limit{}", ( @@ -67,6 +71,10 @@ int Client::GetBotSpawnLimit(uint8 class_id) { int bot_spawn_limit = RuleI(Bots, SpawnLimit); + if (Admin() >= RuleI(Bots, MinStatusToBypassSpawnLimit)) { + return RuleI(Bots, MinStatusToBypassSpawnLimit); + } + const auto bucket_name = fmt::format( "bot_spawn_limit{}", ( diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 0387b795b..23ef13454 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -812,7 +812,7 @@ int32 Client::CalcSTR() int32 Client::CalcSTA() { - int32 val = m_pp.STA + itembonuses.STA + spellbonuses.STA + CalcAlcoholPhysicalEffect();; + int32 val = m_pp.STA + itembonuses.STA + spellbonuses.STA + CalcAlcoholPhysicalEffect(); int32 mod = aabonuses.STA; STA = val + mod; if (STA < 1) { @@ -827,7 +827,7 @@ int32 Client::CalcSTA() int32 Client::CalcAGI() { - int32 val = m_pp.AGI + itembonuses.AGI + spellbonuses.AGI - CalcAlcoholPhysicalEffect();; + int32 val = m_pp.AGI + itembonuses.AGI + spellbonuses.AGI - CalcAlcoholPhysicalEffect(); int32 mod = aabonuses.AGI; int32 str = GetSTR(); //Encumbered penalty @@ -852,7 +852,7 @@ int32 Client::CalcAGI() int32 Client::CalcDEX() { - int32 val = m_pp.DEX + itembonuses.DEX + spellbonuses.DEX - CalcAlcoholPhysicalEffect();; + int32 val = m_pp.DEX + itembonuses.DEX + spellbonuses.DEX - CalcAlcoholPhysicalEffect(); int32 mod = aabonuses.DEX; DEX = val + mod; if (DEX < 1) { diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index c880e310f..67e49d815 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -412,7 +412,7 @@ void MapOpcodes() ConnectedOpcodes[OP_TributeUpdate] = &Client::Handle_OP_TributeUpdate; ConnectedOpcodes[OP_VetClaimRequest] = &Client::Handle_OP_VetClaimRequest; ConnectedOpcodes[OP_VoiceMacroIn] = &Client::Handle_OP_VoiceMacroIn; - ConnectedOpcodes[OP_UpdateAura] = &Client::Handle_OP_UpdateAura;; + ConnectedOpcodes[OP_UpdateAura] = &Client::Handle_OP_UpdateAura; ConnectedOpcodes[OP_WearChange] = &Client::Handle_OP_WearChange; ConnectedOpcodes[OP_WhoAllRequest] = &Client::Handle_OP_WhoAllRequest; ConnectedOpcodes[OP_WorldUnknown001] = &Client::Handle_OP_Ignore; @@ -667,6 +667,10 @@ void Client::CompleteConnect() for (int x1 = 0; x1 < EFFECT_COUNT; x1++) { switch (spell.effect_id[x1]) { case SE_Illusion: { + if (GetIllusionBlock()) { + break; + } + if (buffs[j1].persistant_buff) { Mob *caster = entity_list.GetMobID(buffs[j1].casterid); ApplySpellEffectIllusion(spell.id, caster, j1, spell.base_value[x1], spell.limit_value[x1], spell.max_value[x1]); @@ -1487,6 +1491,12 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) LogError("Error loading AA points for [{}]", GetName()); } + /* Load Bots */ + + LoadDefaultBotSettings(); + + database.botdb.LoadBotSettings(this); + if (SPDAT_RECORDS > 0) { for (uint32 z = 0; z < EQ::spells::SPELL_GEM_COUNT; z++) { if (m_pp.mem_spells[z] >= (uint32)SPDAT_RECORDS) @@ -1583,7 +1593,6 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) LFG = false; } - /* Load Bots */ if (RuleB(Bots, Enabled)) { database.botdb.LoadOwnerOptions(this); // TODO: mod below function for loading spawned botgroups diff --git a/zone/command.cpp b/zone/command.cpp index 513cd0218..21f57a709 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -147,6 +147,7 @@ int command_init(void) command_add("help", "[Search Criteria] - List available commands and their description, specify partial command as argument to search", AccountStatus::Player, command_help) || command_add("hotfix", "[hotfix_name] - Reloads shared memory into a hotfix, equiv to load_shared_memory followed by apply_shared_memory", AccountStatus::GMImpossible, command_hotfix) || command_add("hp", "Refresh your HP bar from the server.", AccountStatus::Player, command_hp) || + command_add("illusionblock", "Controls whether or not illusion effects will land on you when cast by other players or bots", AccountStatus::Player, command_illusion_block) || command_add("instance", "Modify Instances", AccountStatus::GMMgmt, command_instance) || command_add("interrogateinv", "use [help] argument for available options", AccountStatus::Player, command_interrogateinv) || command_add("interrupt", "[Message ID] [Color] - Interrupt your casting. Arguments are optional.", AccountStatus::Guide, command_interrupt) || @@ -217,6 +218,10 @@ int command_init(void) command_add("spawn", "[name] [race] [level] [material] [hp] [gender] [class] [priweapon] [secweapon] [merchantid] - Spawn an NPC", AccountStatus::Steward, command_spawn) || command_add("spawneditmass", "[Search Criteria] [Edit Option] [Edit Value] [Apply] Mass editing spawn command (Apply is optional, 0 = False, 1 = True, default is False)", AccountStatus::GMLeadAdmin, command_spawneditmass) || command_add("spawnfix", "Find targeted NPC in database based on its X/Y/heading and update the database to make it spawn at your current location/heading.", AccountStatus::GMAreas, command_spawnfix) || + command_add("spelldelays", "Controls the delay between casts for a specific spell type", AccountStatus::Player, command_spell_delays) || + command_add("spellholds", "Controls whether a bot holds the specified spell type or not", AccountStatus::Player, command_spell_holds) || + command_add("spellmaxthresholds", "Controls the minimum target HP threshold for a spell to be cast for a specific type", AccountStatus::Player, command_spell_max_thresholds) || + command_add("spellminthresholds", "Controls the maximum target HP threshold for a spell to be cast for a specific type", AccountStatus::Player, command_spell_min_thresholds) || command_add("stun", "[duration] - Stuns you or your target for duration", AccountStatus::GMAdmin, command_stun) || command_add("summon", "[Character Name] - Summons your corpse, NPC, or player target, or by character name if specified", AccountStatus::QuestTroupe, command_summon) || command_add("summonburiedplayercorpse", "Summons the target's oldest buried corpse, if any exist.", AccountStatus::GMAdmin, command_summonburiedplayercorpse) || @@ -841,6 +846,7 @@ void command_bot(Client *c, const Seperator *sep) #include "gm_commands/grid.cpp" #include "gm_commands/guild.cpp" #include "gm_commands/hp.cpp" +#include "gm_commands/illusion_block.cpp" #include "gm_commands/instance.cpp" #include "gm_commands/interrogateinv.cpp" #include "gm_commands/interrupt.cpp" @@ -909,6 +915,10 @@ void command_bot(Client *c, const Seperator *sep) #include "gm_commands/spawn.cpp" #include "gm_commands/spawneditmass.cpp" #include "gm_commands/spawnfix.cpp" +#include "gm_commands/spell_delays.cpp" +#include "gm_commands/spell_holds.cpp" +#include "gm_commands/spell_max_thresholds.cpp" +#include "gm_commands/spell_min_thresholds.cpp" #include "gm_commands/faction_association.cpp" #include "gm_commands/stun.cpp" #include "gm_commands/summon.cpp" diff --git a/zone/command.h b/zone/command.h index d59fdf9a5..aa5771da9 100644 --- a/zone/command.h +++ b/zone/command.h @@ -100,6 +100,7 @@ void command_guild(Client *c, const Seperator *sep); void command_help(Client *c, const Seperator *sep); void command_hotfix(Client *c, const Seperator *sep); void command_hp(Client *c, const Seperator *sep); +void command_illusion_block(Client* c, const Seperator* sep); void command_instance(Client *c, const Seperator *sep); void command_interrogateinv(Client *c, const Seperator *sep); void command_interrupt(Client *c, const Seperator *sep); @@ -170,6 +171,10 @@ void command_shutdown(Client *c, const Seperator *sep); void command_spawn(Client *c, const Seperator *sep); void command_spawneditmass(Client *c, const Seperator *sep); void command_spawnfix(Client *c, const Seperator *sep); +void command_spell_delays(Client* c, const Seperator* sep); +void command_spell_holds(Client* c, const Seperator* sep); +void command_spell_max_thresholds(Client* c, const Seperator* sep); +void command_spell_min_thresholds(Client* c, const Seperator* sep); void command_stun(Client *c, const Seperator *sep); void command_summon(Client *c, const Seperator *sep); void command_summonburiedplayercorpse(Client *c, const Seperator *sep); diff --git a/zone/entity.cpp b/zone/entity.cpp index c21856d37..f0b33363c 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -2951,6 +2951,7 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob) for (auto &e : mob_list) { auto mob = e.second; + if (mob->GetID() <= 0) { continue; } diff --git a/zone/entity.h b/zone/entity.h index 33313f14e..814a5feb5 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -628,7 +628,7 @@ private: Client* GetBotOwnerByBotID(const uint32 bot_id); std::list GetBotsByBotOwnerCharacterID(uint32 botOwnerCharacterID); - bool Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint32 iSpellTypes); // TODO: Evaluate this closesly in hopes to eliminate + bool Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint16 spellType); // TODO: Evaluate this closesly in hopes to eliminate void ShowSpawnWindow(Client* client, int Distance, bool NamedOnly); // TODO: Implement ShowSpawnWindow in the bot class but it needs entity list stuff void ScanCloseClientMobs(std::unordered_map& close_mobs, Mob* scanning_mob); diff --git a/zone/exp.cpp b/zone/exp.cpp index be6b8af2b..70f72cacb 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -1004,6 +1004,7 @@ void Client::SetLevel(uint8 set_level, bool command) } } else { SetHP(CalcMaxHP()); // Why not, lets give them a free heal + SetMana(CalcMaxMana()); } if (RuleI(World, PVPMinLevel) > 0 && level >= RuleI(World, PVPMinLevel) && m_pp.pvp == 0) { diff --git a/zone/gm_commands/illusion_block.cpp b/zone/gm_commands/illusion_block.cpp new file mode 100644 index 000000000..cd05b965a --- /dev/null +++ b/zone/gm_commands/illusion_block.cpp @@ -0,0 +1,31 @@ +#include "../client.h" + +void command_illusion_block(Client* c, const Seperator* sep) +{ + int arguments = sep->argnum; + if (!arguments || !strcasecmp(sep->arg[1], "help")) { + c->Message(Chat::White, "usage: #illusionblock [help | current | value]."); + c->Message(Chat::White, "note: Used to control whether or not illusion effects will land on you."); + c->Message(Chat::White, "note: A value of 0 is disabled (Allow Illusions), 1 is enabled (Block Illusions)."); + c->Message(Chat::White, "note: Use [current] to check the current setting."); + return; + } + + if (sep->IsNumber(1)) { + int setStatus = atoi(sep->arg[1]); + if (setStatus == 0 || setStatus == 1) { + c->SetIllusionBlock(setStatus); + c->Message(Chat::White, "Your Illusion Block has been %s.", (setStatus ? "enabled" : "disabled")); + } + else { + c->Message(Chat::White, "You must enter 0 for disabled or 1 for enabled."); + return; + } + } + else if (!strcasecmp(sep->arg[1], "current")) { + c->Message(Chat::White, "You're currently %s illusions.", (c->GetIllusionBlock() ? "blocking" : "allowing")); + } + else { + c->Message(Chat::White, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); + } +} diff --git a/zone/gm_commands/spell_delays.cpp b/zone/gm_commands/spell_delays.cpp new file mode 100644 index 000000000..09b2b0c5d --- /dev/null +++ b/zone/gm_commands/spell_delays.cpp @@ -0,0 +1,215 @@ +#include "../command.h" + +void command_spell_delays(Client* c, const Seperator* sep) +{ + const int arguments = sep->argnum; + if (arguments) { + const bool is_help = !strcasecmp(sep->arg[1], "help"); + + if (is_help) { + c->Message(Chat::White, "usage: %s [spelltype ID | spelltype Shortname] [current | value: 0-1].", sep->arg[0]); + c->Message(Chat::White, "example: [%s 15 4000] or [%s cures 4000] would allow bots to cast cures on you every 4 seconds.", sep->arg[0], sep->arg[0]); + c->Message(Chat::White, "note: Use [current] to check your current setting."); + c->Message( + Chat::White, + fmt::format( + "note: Use {} for a list of spell types by ID or {} for a list of spell types by short name.", + Saylink::Silent( + fmt::format("{} listid", sep->arg[0]) + ), + Saylink::Silent( + fmt::format("{} listname", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + const std::string& color_red = "red_1"; + const std::string& color_blue = "royal_blue"; + const std::string& color_green = "forest_green"; + const std::string& bright_green = "green"; + const std::string& bright_red = "red"; + const std::string& heroic_color = "gold"; + + std::string fillerLine = "-----------"; + std::string spellTypeField = "Spell Type"; + std::string pluralS = "s"; + std::string idField = "ID"; + std::string shortnameField = "Short Name"; + + std::string popup_text = DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(bright_green, spellTypeField) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + (!arg1.compare("listid") ? DialogueWindow::ColorMessage(bright_green, idField) : DialogueWindow::ColorMessage(bright_green, shortnameField)) + ) + ) + ); + + popup_text += DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(heroic_color, fillerLine) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(heroic_color, fillerLine) + ) + ) + ); + + for (int i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + if (!IsClientBotSpellType(i)) { + continue; + } + + popup_text += DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}{}", + DialogueWindow::ColorMessage(color_green, c->GetSpellTypeNameByID(i)), + DialogueWindow::ColorMessage(color_green, pluralS) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + (!arg1.compare("listid") ? DialogueWindow::ColorMessage(color_blue, std::to_string(i)) : DialogueWindow::ColorMessage(color_blue, c->GetSpellTypeShortNameByID(i))) + ) + ) + ); + } + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient("Spell Types", popup_text.c_str()); + + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (!IsClientBotSpellType(spellType)) { + c->Message( + Chat::White, + fmt::format( + "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", + Saylink::Silent( + fmt::format("{} listid", sep->arg[0]) + ), + Saylink::Silent( + fmt::format("{} listname", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + + if (!IsClientBotSpellType(spellType)) { + c->Message( + Chat::White, + fmt::format( + "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", + Saylink::Silent( + fmt::format("{} listid", sep->arg[0]) + ), + Saylink::Silent( + fmt::format("{} listname", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 1 || typeValue > 60000) { + c->Message(Chat::Yellow, "You must enter a value between 1-60000 (1ms to 60s)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "Your current {} delay is {} seconds.", + c->GetSpellTypeNameByID(spellType), + c->GetSpellDelay(spellType) / 1000.00 + ).c_str() + ); + } + else { + c->SetSpellDelay(spellType, typeValue); + c->Message( + Chat::Green, + fmt::format( + "Your {} delay was set to {} seconds.", + c->GetSpellTypeNameByID(spellType), + c->GetSpellDelay(spellType) / 1000.00 + ).c_str() + ); + } +} diff --git a/zone/gm_commands/spell_holds.cpp b/zone/gm_commands/spell_holds.cpp new file mode 100644 index 000000000..90a725dfb --- /dev/null +++ b/zone/gm_commands/spell_holds.cpp @@ -0,0 +1,218 @@ +#include "../command.h" + +void command_spell_holds(Client *c, const Seperator *sep) +{ + const int arguments = sep->argnum; + if (arguments) { + const bool is_help = !strcasecmp(sep->arg[1], "help"); + + if (is_help) { + c->Message(Chat::White, "usage: %s [spelltype ID | spelltype Shortname] [current | value: 0-1].", sep->arg[0]); + c->Message(Chat::White, "example: [%s 15 1] or [%s cures 1] would prevent bots from casting cures on you.", sep->arg[0], sep->arg[0]); + c->Message(Chat::White, "note: Use [current] to check your current setting."); + c->Message(Chat::White, "note: Set to 0 to unhold the given spell type."); + c->Message(Chat::White, "note: Set to 1 to hold the given spell type."); + c->Message( + Chat::White, + fmt::format( + "note: Use {} for a list of spell types by ID or {} for a list of spell types by short name.", + Saylink::Silent( + fmt::format("{} listid", sep->arg[0]) + ), + Saylink::Silent( + fmt::format("{} listname", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + const std::string& color_red = "red_1"; + const std::string& color_blue = "royal_blue"; + const std::string& color_green = "forest_green"; + const std::string& bright_green = "green"; + const std::string& bright_red = "red"; + const std::string& heroic_color = "gold"; + + std::string fillerLine = "-----------"; + std::string spellTypeField = "Spell Type"; + std::string pluralS = "s"; + std::string idField = "ID"; + std::string shortnameField = "Short Name"; + + std::string popup_text = DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(bright_green, spellTypeField) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + (!arg1.compare("listid") ? DialogueWindow::ColorMessage(bright_green, idField) : DialogueWindow::ColorMessage(bright_green, shortnameField)) + ) + ) + ); + + popup_text += DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(heroic_color, fillerLine) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(heroic_color, fillerLine) + ) + ) + ); + + for (int i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + if (!IsClientBotSpellType(i)) { + continue; + } + + popup_text += DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}{}", + DialogueWindow::ColorMessage(color_green, c->GetSpellTypeNameByID(i)), + DialogueWindow::ColorMessage(color_green, pluralS) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + (!arg1.compare("listid") ? DialogueWindow::ColorMessage(color_blue, std::to_string(i)) : DialogueWindow::ColorMessage(color_blue, c->GetSpellTypeShortNameByID(i))) + ) + ) + ); + } + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient("Spell Types", popup_text.c_str()); + + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (!IsClientBotSpellType(spellType)) { + c->Message( + Chat::White, + fmt::format( + "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", + Saylink::Silent( + fmt::format("{} listid", sep->arg[0]) + ), + Saylink::Silent( + fmt::format("{} listname", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + + if (!IsClientBotSpellType(spellType)) { + c->Message( + Chat::White, + fmt::format( + "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", + Saylink::Silent( + fmt::format("{} listid", sep->arg[0]) + ), + Saylink::Silent( + fmt::format("{} listname", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + // Enable/Disable/Current checks + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "Your current Hold {}s status is {}.", + c->GetSpellTypeNameByID(spellType), + c->GetSpellHold(spellType) ? "enabled" : "disabled" + ).c_str() + ); + } + else { + c->SetSpellHold(spellType, typeValue); + c->Message( + Chat::Green, + fmt::format( + "Your Hold {}s status was {}.", + c->GetSpellTypeNameByID(spellType), + c->GetSpellHold(spellType) ? "enabled" : "disabled" + ).c_str() + ); + } +} diff --git a/zone/gm_commands/spell_max_thresholds.cpp b/zone/gm_commands/spell_max_thresholds.cpp new file mode 100644 index 000000000..a67660d2e --- /dev/null +++ b/zone/gm_commands/spell_max_thresholds.cpp @@ -0,0 +1,216 @@ +#include "../command.h" + +void command_spell_max_thresholds(Client* c, const Seperator* sep) +{ + const int arguments = sep->argnum; + if (arguments) { + const bool is_help = !strcasecmp(sep->arg[1], "help"); + + if (is_help) { + c->Message(Chat::White, "usage: %s [spelltype ID | spelltype Shortname] [current | value: 0-1].", sep->arg[0]); + c->Message(Chat::White, "example: [%s 15 95] or [%s cures 95] would allow bots to cast cures on you when you are under 95%% health.", sep->arg[0], sep->arg[0]); + c->Message(Chat::White, "note: Use [current] to check your current setting."); + c->Message( + Chat::White, + fmt::format( + "note: Use {} for a list of spell types by ID or {} for a list of spell types by short name.", + Saylink::Silent( + fmt::format("{} listid", sep->arg[0]) + ), + Saylink::Silent( + fmt::format("{} listname", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + const std::string& color_red = "red_1"; + const std::string& color_blue = "royal_blue"; + const std::string& color_green = "forest_green"; + const std::string& bright_green = "green"; + const std::string& bright_red = "red"; + const std::string& heroic_color = "gold"; + + std::string fillerLine = "-----------"; + std::string spellTypeField = "Spell Type"; + std::string pluralS = "s"; + std::string idField = "ID"; + std::string shortnameField = "Short Name"; + + std::string popup_text = DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(bright_green, spellTypeField) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + (!arg1.compare("listid") ? DialogueWindow::ColorMessage(bright_green, idField) : DialogueWindow::ColorMessage(bright_green, shortnameField)) + ) + ) + ); + + popup_text += DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(heroic_color, fillerLine) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(heroic_color, fillerLine) + ) + ) + ); + + for (int i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + if (!IsClientBotSpellType(i)) { + continue; + } + + popup_text += DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}{}", + DialogueWindow::ColorMessage(color_green, c->GetSpellTypeNameByID(i)), + DialogueWindow::ColorMessage(color_green, pluralS) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + (!arg1.compare("listid") ? DialogueWindow::ColorMessage(color_blue, std::to_string(i)) : DialogueWindow::ColorMessage(color_blue, c->GetSpellTypeShortNameByID(i))) + ) + ) + ); + } + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient("Spell Types", popup_text.c_str()); + + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (!IsClientBotSpellType(spellType)) { + c->Message( + Chat::White, + fmt::format( + "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", + Saylink::Silent( + fmt::format("{} listid", sep->arg[0]) + ), + Saylink::Silent( + fmt::format("{} listname", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + + if (!IsClientBotSpellType(spellType)) { + c->Message( + Chat::White, + fmt::format( + "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", + Saylink::Silent( + fmt::format("{} listid", sep->arg[0]) + ), + Saylink::Silent( + fmt::format("{} listname", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + // Enable/Disable/Current checks + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 150) { + c->Message(Chat::Yellow, "You must enter a value between 0-150 (0%% to 150%% of health)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "Your current max threshold for {}s is {}%%.", + c->GetSpellTypeNameByID(spellType), + c->GetSpellMaxThreshold(spellType) + ).c_str() + ); + } + else { + c->SetSpellMaxThreshold(spellType, typeValue); + c->Message( + Chat::Green, + fmt::format( + "Your max threshold for {}s was set to {}%%.", + c->GetSpellTypeNameByID(spellType), + c->GetSpellMaxThreshold(spellType) + ).c_str() + ); + } +} diff --git a/zone/gm_commands/spell_min_thresholds.cpp b/zone/gm_commands/spell_min_thresholds.cpp new file mode 100644 index 000000000..1b0fcfdf2 --- /dev/null +++ b/zone/gm_commands/spell_min_thresholds.cpp @@ -0,0 +1,216 @@ +#include "../command.h" + +void command_spell_min_thresholds(Client* c, const Seperator* sep) +{ + const int arguments = sep->argnum; + if (arguments) { + const bool is_help = !strcasecmp(sep->arg[1], "help"); + + if (is_help) { + c->Message(Chat::White, "usage: %s [spelltype ID | spelltype Shortname] [current | value: 0-1].", sep->arg[0]); + c->Message(Chat::White, "example: [%s 15 15] or [%s cures 15] would prevent bots from casting cures on you when you are under 15%% health.", sep->arg[0], sep->arg[0]); + c->Message(Chat::White, "note: Use [current] to check your current setting."); + c->Message( + Chat::White, + fmt::format( + "note: Use {} for a list of spell types by ID or {} for a list of spell types by short name.", + Saylink::Silent( + fmt::format("{} listid", sep->arg[0]) + ), + Saylink::Silent( + fmt::format("{} listname", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("listid") || !arg1.compare("listname")) { + const std::string& color_red = "red_1"; + const std::string& color_blue = "royal_blue"; + const std::string& color_green = "forest_green"; + const std::string& bright_green = "green"; + const std::string& bright_red = "red"; + const std::string& heroic_color = "gold"; + + std::string fillerLine = "-----------"; + std::string spellTypeField = "Spell Type"; + std::string pluralS = "s"; + std::string idField = "ID"; + std::string shortnameField = "Short Name"; + + std::string popup_text = DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(bright_green, spellTypeField) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + (!arg1.compare("listid") ? DialogueWindow::ColorMessage(bright_green, idField) : DialogueWindow::ColorMessage(bright_green, shortnameField)) + ) + ) + ); + + popup_text += DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(heroic_color, fillerLine) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(heroic_color, fillerLine) + ) + ) + ); + + for (int i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + if (!IsClientBotSpellType(i)) { + continue; + } + + popup_text += DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}{}", + DialogueWindow::ColorMessage(color_green, c->GetSpellTypeNameByID(i)), + DialogueWindow::ColorMessage(color_green, pluralS) + ) + ) + + DialogueWindow::TableCell( + fmt::format( + "{}", + (!arg1.compare("listid") ? DialogueWindow::ColorMessage(color_blue, std::to_string(i)) : DialogueWindow::ColorMessage(color_blue, c->GetSpellTypeShortNameByID(i))) + ) + ) + ); + } + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient("Spell Types", popup_text.c_str()); + + return; + } + + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spellType = 0; + uint32 typeValue = 0; + + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); + + if (!IsClientBotSpellType(spellType)) { + c->Message( + Chat::White, + fmt::format( + "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", + Saylink::Silent( + fmt::format("{} listid", sep->arg[0]) + ), + Saylink::Silent( + fmt::format("{} listname", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + + if (!IsClientBotSpellType(spellType)) { + c->Message( + Chat::White, + fmt::format( + "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", + Saylink::Silent( + fmt::format("{} listid", sep->arg[0]) + ), + Saylink::Silent( + fmt::format("{} listname", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + // Enable/Disable/Current checks + if (sep->IsNumber(2)) { + typeValue = atoi(sep->arg[2]); + ++ab_arg; + if (typeValue < 0 || typeValue > 150) { + c->Message(Chat::Yellow, "You must enter a value between 0-150 (0%% to 150%% of health)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "Your current min threshold for {}s is {}%%.", + c->GetSpellTypeNameByID(spellType), + c->GetSpellMinThreshold(spellType) + ).c_str() + ); + } + else { + c->SetSpellMinThreshold(spellType, typeValue); + c->Message( + Chat::Green, + fmt::format( + "Your min threshold for {}s was set to {}%%.", + c->GetSpellTypeNameByID(spellType), + c->GetSpellMinThreshold(spellType) + ).c_str() + ); + } +} diff --git a/zone/lua_bot.cpp b/zone/lua_bot.cpp index 2cf0e7db3..8003e1388 100644 --- a/zone/lua_bot.cpp +++ b/zone/lua_bot.cpp @@ -105,11 +105,6 @@ void Lua_Bot::SetExpansionBitmask(int expansion_bitmask) { self->SetExpansionBitmask(expansion_bitmask); } -void Lua_Bot::SetExpansionBitmask(int expansion_bitmask, bool save) { - Lua_Safe_Call_Void(); - self->SetExpansionBitmask(expansion_bitmask, save); -} - bool Lua_Bot::ReloadBotDataBuckets() { Lua_Safe_Call_Bool(); return DataBucket::GetDataBuckets(self); @@ -762,7 +757,6 @@ luabind::scope lua_register_bot() { .def("SetBucket", (void(Lua_Bot::*)(std::string,std::string))&Lua_Bot::SetBucket) .def("SetBucket", (void(Lua_Bot::*)(std::string,std::string,std::string))&Lua_Bot::SetBucket) .def("SetExpansionBitmask", (void(Lua_Bot::*)(int))&Lua_Bot::SetExpansionBitmask) - .def("SetExpansionBitmask", (void(Lua_Bot::*)(int,bool))&Lua_Bot::SetExpansionBitmask) .def("SetDisciplineReuseTimer", (void(Lua_Bot::*)(uint16))&Lua_Bot::SetDisciplineReuseTimer) .def("SetDisciplineReuseTimer", (void(Lua_Bot::*)(uint16, uint32))&Lua_Bot::SetDisciplineReuseTimer) .def("SetItemReuseTimer", (void(Lua_Bot::*)(uint32))&Lua_Bot::SetItemReuseTimer) diff --git a/zone/lua_bot.h b/zone/lua_bot.h index b07d14abc..7a038b7dc 100644 --- a/zone/lua_bot.h +++ b/zone/lua_bot.h @@ -52,7 +52,6 @@ public: void ReloadBotSpellSettings(); void RemoveBotItem(uint32 item_id); void SetExpansionBitmask(int expansion_bitmask); - void SetExpansionBitmask(int expansion_bitmask, bool save); void Signal(int signal_id); bool HasBotSpellEntry(uint16 spellid); void SendPayload(int payload_id); diff --git a/zone/merc.cpp b/zone/merc.cpp index 6200e5b89..d66501efa 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -1500,7 +1500,7 @@ bool Merc::AI_IdleCastCheck() { bool EntityList::Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { - if((iSpellTypes & SPELL_TYPES_DETRIMENTAL) != 0) { + if (BOT_SPELL_TYPES_DETRIMENTAL(iSpellTypes)) { //according to live, you can buff and heal through walls... //now with PCs, this only applies if you can TARGET the target, but // according to Rogean, Live NPCs will just cast through walls/floors, no problem.. @@ -1569,7 +1569,7 @@ bool Merc::AIDoSpellCast(uint16 spellid, Mob* tar, int32 mana_cost, uint32* oDon float dist2 = 0; - if (mercSpell.type & SpellType_Escape) { + if (mercSpell.type == SpellType_Escape) { dist2 = 0; } else dist2 = DistanceSquared(m_Position, tar->GetPosition()); diff --git a/zone/mob.cpp b/zone/mob.cpp index a1d9b5d38..7182bbfb4 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -1692,6 +1692,11 @@ void Mob::StopMoving() void Mob::StopMoving(float new_heading) { + if (IsBot()) { + CastToBot()->SetCombatJitterFlag(false); + CastToBot()->SetCombatOutOfRangeJitterFlag(false); + } + StopNavigation(); RotateTo(new_heading); @@ -4664,7 +4669,7 @@ bool Mob::CanThisClassDoubleAttack(void) const bool Mob::CanThisClassTripleAttack() const { - if (!IsClient()) { + if (!IsOfClientBot()) { return false; // When they added the real triple attack skill, mobs lost the ability to triple } else { if (RuleB(Combat, ClassicTripleAttack)) { @@ -4678,7 +4683,12 @@ bool Mob::CanThisClassTripleAttack() const ) ); } else { - return CastToClient()->HasSkill(EQ::skills::SkillTripleAttack); + if (IsClient()) { + return CastToClient()->HasSkill(EQ::skills::SkillTripleAttack); + } + else { + return GetSkill(EQ::skills::SkillTripleAttack) > 0; + } } } } @@ -4812,6 +4822,88 @@ bool Mob::PlotPositionAroundTarget(Mob* target, float &x_dest, float &y_dest, fl return Result; } +bool Mob::PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behindOnly, bool frontOnly, bool bypassLoS) { + bool Result = false; + + if (target) { + float look_heading = 0; + + min_distance = min_distance; + max_distance = max_distance; + float tempX = 0; + float tempY = 0; + float tempZ = target->GetZ(); + float bestZ = 0; + auto offset = GetZOffset(); + const float tarX = target->GetX(); + const float tarY = target->GetY(); + float tar_distance = 0; + + glm::vec3 temp_z_Position; + glm::vec4 temp_m_Position; + + const uint16 maxIterationsAllowed = 50; + uint16 counter = 0; + //LogTestDebug("Plotting for {} - Min: [{}] - Max: [{}] - BehindMob: [{}] - Taunt [{}] -- LosReq [{}]", GetCleanName(), min_distance, max_distance, behindOnly, frontOnly, bypassLoS ? "bypassed" : CastToBot()->RequiresLoSForPositioning() ? "true" : "false"); //deleteme + while (counter < maxIterationsAllowed) { + tempX = tarX + zone->random.Real(-max_distance, max_distance); + tempY = tarY + zone->random.Real(-max_distance, max_distance); + + temp_z_Position.x = tempX; + temp_z_Position.y = tempY; + temp_z_Position.z = tempZ; + bestZ = GetFixedZ(temp_z_Position); + + if (bestZ != BEST_Z_INVALID) { + tempZ = bestZ; + } + else { + //LogTestDebug("{} - Plot Failed GetFixedZ - Try #[{}].", GetCleanName(), (counter + 1)); //deleteme + counter++; + continue; + } + + temp_m_Position.x = tempX; + temp_m_Position.y = tempY; + temp_m_Position.z = tempZ; + //tar_distance = DistanceNoZ(target->GetPosition(), temp_m_Position); + tar_distance = Distance(target->GetPosition(), temp_m_Position); + + if (tar_distance > max_distance || tar_distance < min_distance) { + //LogTestDebug("{} - Plot Failed Distance - Try #[{}]. Target LOCs XYZ - [{}], [{}], [{}] - Temp LOCs [{}], [{}], [{}] - Distance between = [{}] - Melee Distance = [{}] - Difference = [{}]", GetCleanName(), (counter + 1), target->GetX(), target->GetY(), target->GetZ(), tempX, tempY, tempZ, tar_distance, max_distance, (tar_distance / max_distance)); + counter++; + continue; + } + if (frontOnly && !InFrontMob(target, tempX, tempY)) { + //LogTestDebug("{} - Plot Failed frontOnly - Try #[{}]. Target LOCs XYZ - [{}], [{}], [{}] - Temp LOCs [{}], [{}], [{}] - Distance between = [{}] - Melee Distance = [{}] - Difference = [{}]", GetCleanName(), (counter + 1), target->GetX(), target->GetY(), target->GetZ(), tempX, tempY, tempZ, tar_distance, max_distance, (tar_distance / max_distance)); + counter++; + continue; + } + else if (behindOnly && !BehindMob(target, tempX, tempY)) { + //LogTestDebug("{} - Plot Failed BehindMob - Try #[{}]. Target LOCs XYZ - [{}], [{}], [{}] - Temp LOCs [{}], [{}], [{}] - Distance between = [{}] - Melee Distance = [{}] - Difference = [{}]", GetCleanName(), (counter + 1), target->GetX(), target->GetY(), target->GetZ(), tempX, tempY, tempZ, tar_distance, max_distance, (tar_distance / max_distance)); + counter++; + continue; + } + if (!bypassLoS && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(target, tempX, tempY, tempZ)) { + //LogTestDebug("{} - Plot Failed LoS - Try #[{}]. Target LOCs XYZ - [{}], [{}], [{}] - Temp LOCs [{}], [{}], [{}] - Distance between = [{}] - Melee Distance = [{}] - Difference = [{}]", GetCleanName(), (counter + 1), target->GetX(), target->GetY(), target->GetZ(), tempX, tempY, tempZ, tar_distance, max_distance, (tar_distance / max_distance)); + counter++; + continue; + } + //LogTestDebug("{} - Plot PASSED! - Try #[{}]. Target LOCs XYZ - [{}], [{}], [{}] - Temp LOCs [{}], [{}], [{}] - Distance between = [{}] - Melee Distance = [{}] - Difference = [{}]", GetCleanName(), (counter + 1), target->GetX(), target->GetY(), target->GetZ(), tempX, tempY, tempZ, tar_distance, max_distance, (tar_distance / max_distance)); + Result = true; + break; + } + + if (Result) { + x_dest = tempX; + y_dest = tempY; + z_dest = tempZ; + } + } + + return Result; +} + bool Mob::HateSummon() { // check if mob has ability to summon // 97% is the offical % that summoning starts on live, not 94 @@ -8581,8 +8673,9 @@ bool Mob::HasBotAttackFlag(Mob* tar) { return false; } + const uint16 scan_close_mobs_timer_moving = 6000; // 6 seconds -const uint16 scan_close_mobs_timer_idle = 60000; // 60 seconds +const uint16 scan_close_mobs_timer_idle = 60000; // 60 seconds void Mob::CheckScanCloseMobsMovingTimer() { @@ -8621,6 +8714,732 @@ void Mob::ScanCloseMobProcess() } } +uint16 Mob::GetSpellTypeIDByShortName(std::string spellTypeString) { + + for (int i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + if (!Strings::ToLower(spellTypeString).compare(GetSpellTypeShortNameByID(i))) { + return i; + } + } + + return UINT16_MAX; +} + +std::string Mob::GetSpellTypeNameByID(uint16 spellType) { + std::string spellTypeName = "null"; + + switch (spellType) { + case BotSpellTypes::Nuke: + spellTypeName = "Nuke"; + break; + case BotSpellTypes::RegularHeal: + spellTypeName = "Regular Heal"; + break; + case BotSpellTypes::Root: + spellTypeName = "Root"; + break; + case BotSpellTypes::Buff: + spellTypeName = "Buff"; + break; + case BotSpellTypes::Escape: + spellTypeName = "Escape"; + break; + case BotSpellTypes::Pet: + spellTypeName = "Pet"; + break; + case BotSpellTypes::Lifetap: + spellTypeName = "Lifetap"; + break; + case BotSpellTypes::Snare: + spellTypeName = "Snare"; + break; + case BotSpellTypes::DOT: + spellTypeName = "DoT"; + break; + case BotSpellTypes::Dispel: + spellTypeName = "Dispel"; + break; + case BotSpellTypes::InCombatBuff: + spellTypeName = "In-Combat Buff"; + break; + case BotSpellTypes::Mez: + spellTypeName = "Mez"; + break; + case BotSpellTypes::Charm: + spellTypeName = "Charm"; + break; + case BotSpellTypes::Slow: + spellTypeName = "Slow"; + break; + case BotSpellTypes::Debuff: + spellTypeName = "Debuff"; + break; + case BotSpellTypes::Cure: + spellTypeName = "Cure"; + break; + case BotSpellTypes::GroupCures: + spellTypeName = "Group Cure"; + break; + case BotSpellTypes::Resurrect: + spellTypeName = "Resurrect"; + break; + case BotSpellTypes::HateRedux: + spellTypeName = "Hate Reduction"; + break; + case BotSpellTypes::InCombatBuffSong: + spellTypeName = "In-Combat Buff Song"; + break; + case BotSpellTypes::OutOfCombatBuffSong: + spellTypeName = "Out-of-Combat Buff Song"; + break; + case BotSpellTypes::PreCombatBuff: + spellTypeName = "Pre-Combat Buff"; + break; + case BotSpellTypes::PreCombatBuffSong: + spellTypeName = "Pre-Combat Buff Song"; + break; + case BotSpellTypes::Fear: + spellTypeName = "Fear"; + break; + case BotSpellTypes::Stun: + spellTypeName = "Stun"; + break; + case BotSpellTypes::CompleteHeal: + spellTypeName = "Complete Heal"; + break; + case BotSpellTypes::FastHeals: + spellTypeName = "Fast Heal"; + break; + case BotSpellTypes::VeryFastHeals: + spellTypeName = "Very Fast Heal"; + break; + case BotSpellTypes::GroupHeals: + spellTypeName = "Group Heal"; + break; + case BotSpellTypes::GroupCompleteHeals: + spellTypeName = "Group Complete Heal"; + break; + case BotSpellTypes::GroupHoTHeals: + spellTypeName = "Group HoT Heal"; + break; + case BotSpellTypes::HoTHeals: + spellTypeName = "HoT Heal"; + break; + case BotSpellTypes::AENukes: + spellTypeName = "AE Nuke"; + break; + case BotSpellTypes::AERains: + spellTypeName = "AE Rain"; + break; + case BotSpellTypes::AEMez: + spellTypeName = "AE Mez"; + break; + case BotSpellTypes::AEStun: + spellTypeName = "AE Stun"; + break; + case BotSpellTypes::AEDebuff: + spellTypeName = "AE Debuff"; + break; + case BotSpellTypes::AESlow: + spellTypeName = "AE Slow"; + break; + case BotSpellTypes::AESnare: + spellTypeName = "AE Snare"; + break; + case BotSpellTypes::AEFear: + spellTypeName = "AE Fear"; + break; + case BotSpellTypes::AEDispel: + spellTypeName = "AE Dispel"; + break; + case BotSpellTypes::AERoot: + spellTypeName = "AE Root"; + break; + case BotSpellTypes::AEDoT: + spellTypeName = "AE DoT"; + break; + case BotSpellTypes::AELifetap: + spellTypeName = "AE Lifetap"; + break; + case BotSpellTypes::PBAENuke: + spellTypeName = "PBAE Nuke"; + break; + case BotSpellTypes::PetBuffs: + spellTypeName = "Pet Buff"; + break; + case BotSpellTypes::PetRegularHeals: + spellTypeName = "Pet Regular Heal"; + break; + case BotSpellTypes::PetCompleteHeals: + spellTypeName = "Pet Complete Heal"; + break; + case BotSpellTypes::PetFastHeals: + spellTypeName = "Pet Fast Heal"; + break; + case BotSpellTypes::PetVeryFastHeals: + spellTypeName = "Pet Very Fast Heal"; + break; + case BotSpellTypes::PetHoTHeals: + spellTypeName = "Pet HoT Heal"; + break; + case BotSpellTypes::DamageShields: + spellTypeName = "Damage Shield"; + break; + case BotSpellTypes::ResistBuffs: + spellTypeName = "Resist Buff"; + break; + default: + break; + } + + return spellTypeName; +} + +std::string Mob::GetSpellTypeShortNameByID(uint16 spellType) { + std::string spellTypeName = "null"; + + switch (spellType) { + case BotSpellTypes::Nuke: + spellTypeName = "nukes"; + break; + case BotSpellTypes::RegularHeal: + spellTypeName = "regularheals"; + break; + case BotSpellTypes::Root: + spellTypeName = "roots"; + break; + case BotSpellTypes::Buff: + spellTypeName = "buffs"; + break; + case BotSpellTypes::Escape: + spellTypeName = "escapes"; + break; + case BotSpellTypes::Pet: + spellTypeName = "pets"; + break; + case BotSpellTypes::Lifetap: + spellTypeName = "lifetaps"; + break; + case BotSpellTypes::Snare: + spellTypeName = "snares"; + break; + case BotSpellTypes::DOT: + spellTypeName = "dots"; + break; + case BotSpellTypes::Dispel: + spellTypeName = "dispels"; + break; + case BotSpellTypes::InCombatBuff: + spellTypeName = "incombatbuffs"; + break; + case BotSpellTypes::Mez: + spellTypeName = "mez"; + break; + case BotSpellTypes::Charm: + spellTypeName = "charms"; + break; + case BotSpellTypes::Slow: + spellTypeName = "slows"; + break; + case BotSpellTypes::Debuff: + spellTypeName = "debuffs"; + break; + case BotSpellTypes::Cure: + spellTypeName = "cures"; + break; + case BotSpellTypes::GroupCures: + spellTypeName = "groupcures"; + break; + case BotSpellTypes::Resurrect: + spellTypeName = "resurrect"; + break; + case BotSpellTypes::HateRedux: + spellTypeName = "hateredux"; + break; + case BotSpellTypes::InCombatBuffSong: + spellTypeName = "incombatbuffsongs"; + break; + case BotSpellTypes::OutOfCombatBuffSong: + spellTypeName = "outofcombatbuffsongs"; + break; + case BotSpellTypes::PreCombatBuff: + spellTypeName = "precombatbuffs"; + break; + case BotSpellTypes::PreCombatBuffSong: + spellTypeName = "precombatbuffsongs"; + break; + case BotSpellTypes::Fear: + spellTypeName = "fears"; + break; + case BotSpellTypes::Stun: + spellTypeName = "stuns"; + break; + case BotSpellTypes::CompleteHeal: + spellTypeName = "completeheals"; + break; + case BotSpellTypes::FastHeals: + spellTypeName = "fastheals"; + break; + case BotSpellTypes::VeryFastHeals: + spellTypeName = "veryfastheals"; + break; + case BotSpellTypes::GroupHeals: + spellTypeName = "groupheals"; + break; + case BotSpellTypes::GroupCompleteHeals: + spellTypeName = "groupcompleteheals"; + break; + case BotSpellTypes::GroupHoTHeals: + spellTypeName = "grouphotheals"; + break; + case BotSpellTypes::HoTHeals: + spellTypeName = "hotheals"; + break; + case BotSpellTypes::AENukes: + spellTypeName = "aenukes"; + break; + case BotSpellTypes::AERains: + spellTypeName = "aerains"; + break; + case BotSpellTypes::AEMez: + spellTypeName = "aemez"; + break; + case BotSpellTypes::AEStun: + spellTypeName = "aestuns"; + break; + case BotSpellTypes::AEDebuff: + spellTypeName = "aedebuffs"; + break; + case BotSpellTypes::AESlow: + spellTypeName = "aeslows"; + break; + case BotSpellTypes::AESnare: + spellTypeName = "aesnares"; + break; + case BotSpellTypes::AEFear: + spellTypeName = "aefears"; + break; + case BotSpellTypes::AEDispel: + spellTypeName = "aedispels"; + break; + case BotSpellTypes::AERoot: + spellTypeName = "aeroots"; + break; + case BotSpellTypes::AEDoT: + spellTypeName = "aedots"; + break; + case BotSpellTypes::AELifetap: + spellTypeName = "aelifetaps"; + break; + case BotSpellTypes::PBAENuke: + spellTypeName = "pbaenukes"; + break; + case BotSpellTypes::PetBuffs: + spellTypeName = "petbuffs"; + break; + case BotSpellTypes::PetRegularHeals: + spellTypeName = "petregularheals"; + break; + case BotSpellTypes::PetCompleteHeals: + spellTypeName = "petcompleteheals"; + break; + case BotSpellTypes::PetFastHeals: + spellTypeName = "petfastheals"; + break; + case BotSpellTypes::PetVeryFastHeals: + spellTypeName = "petveryfastheals"; + break; + case BotSpellTypes::PetHoTHeals: + spellTypeName = "pethotheals"; + break; + case BotSpellTypes::DamageShields: + spellTypeName = "damageshields"; + break; + case BotSpellTypes::ResistBuffs: + spellTypeName = "resistbuffs"; + break; + default: + break; + } + + return spellTypeName; +} + +bool Mob::GetDefaultSpellHold(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::AEMez: + case BotSpellTypes::AEStun: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::AESlow: + case BotSpellTypes::AESnare: + case BotSpellTypes::AEDoT: + case BotSpellTypes::AELifetap: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::AERoot: + case BotSpellTypes::Root: + case BotSpellTypes::AEDispel: + case BotSpellTypes::Dispel: + case BotSpellTypes::AEFear: + case BotSpellTypes::Fear: + return true; + case BotSpellTypes::Snare: + if (GetClass() == Class::Wizard) { + return true; + } + else { + return false; + } + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::OutOfCombatBuffSong: + case BotSpellTypes::PreCombatBuffSong: + if (GetClass() == Class::Bard) { + return true; + } + else { + return false; + } + default: + return false; + } +} + +uint16 Mob::GetDefaultSpellDelay(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::PetVeryFastHeals: + return 1500; + case BotSpellTypes::FastHeals: + case BotSpellTypes::PetFastHeals: + return 2500; + case BotSpellTypes::AEDoT: + case BotSpellTypes::DOT: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::RegularHeal: + case BotSpellTypes::PetRegularHeals: + return 4000; + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Nuke: + case BotSpellTypes::AESnare: + case BotSpellTypes::Snare: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::Debuff: + case BotSpellTypes::AESlow: + case BotSpellTypes::Slow: + case BotSpellTypes::AEStun: + case BotSpellTypes::Stun: + return 6000; + case BotSpellTypes::AERoot: + case BotSpellTypes::Root: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::PetCompleteHeals: + return 8000; + case BotSpellTypes::Fear: + case BotSpellTypes::AEFear: + return 15000; + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetHoTHeals: + return 22000; + default: + return 1; + } +} + +uint8 Mob::GetDefaultSpellMinThreshold(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Nuke: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::Debuff: + case BotSpellTypes::AEDispel: + case BotSpellTypes::Dispel: + case BotSpellTypes::AESlow: + case BotSpellTypes::Slow: + return 20; + case BotSpellTypes::AEDoT: + case BotSpellTypes::DOT: + return 35; + case BotSpellTypes::Charm: + return 50; + case BotSpellTypes::Mez: + case BotSpellTypes::AEMez: + return 85; + default: + return 0; + } +} + +uint8 Mob::GetDefaultSpellMaxThreshold(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::Escape: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::PetVeryFastHeals: + return 25; + case BotSpellTypes::AELifetap: + case BotSpellTypes::Lifetap: + case BotSpellTypes::FastHeals: + case BotSpellTypes::PetFastHeals: + return 40; + case BotSpellTypes::GroupHeals: + case BotSpellTypes::RegularHeal: + case BotSpellTypes::PetRegularHeals: + return 60; + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::PetCompleteHeals: + return 80; + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::AEStun: + case BotSpellTypes::Nuke: + case BotSpellTypes::AEDoT: + case BotSpellTypes::DOT: + case BotSpellTypes::AERoot: + case BotSpellTypes::Root: + case BotSpellTypes::AESlow: + case BotSpellTypes::Slow: + case BotSpellTypes::AESnare: + case BotSpellTypes::Snare: + case BotSpellTypes::AEFear: + case BotSpellTypes::Fear: + case BotSpellTypes::Dispel: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::Debuff: + case BotSpellTypes::Stun: + return 99; + case BotSpellTypes::Buff: + case BotSpellTypes::Charm: + case BotSpellTypes::Cure: + case BotSpellTypes::DamageShields: + case BotSpellTypes::HateRedux: + case BotSpellTypes::InCombatBuff: + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::Mez: + case BotSpellTypes::AEMez: + case BotSpellTypes::OutOfCombatBuffSong: + case BotSpellTypes::Pet: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::PreCombatBuffSong: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::Resurrect: + return 100; + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetHoTHeals: + if (GetClass() == Class::Necromancer || GetClass() == Class::Shaman) { + return 60; + } + else { + return 90; + } + default: + return 100; + } +} + +void Mob::SetSpellHold(uint16 spellType, bool holdStatus) { + _spellSettings[spellType].hold = holdStatus; +} + +void Mob::SetSpellDelay(uint16 spellType, uint16 delayValue) { + _spellSettings[spellType].delay = delayValue; +} + +void Mob::SetSpellMinThreshold(uint16 spellType, uint8 thresholdValue) { + _spellSettings[spellType].minThreshold = thresholdValue; +} + +void Mob::SetSpellMaxThreshold(uint16 spellType, uint8 thresholdValue) { + _spellSettings[spellType].maxThreshold = thresholdValue; +} + +void Mob::SetSpellTypeRecastTimer(uint16 spellType, uint32 recastTime) { + _spellSettings[spellType].recastTimer.Start(recastTime); + LogBotDelayChecksDetail("{} says, 'My {} Delay was to {} seconds.'" + , GetCleanName() + , GetSpellTypeNameByID(spellType) + , (recastTime / 1000.00) + ); //deleteme +} + +void Mob::StartBotSpellTimers() { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + _spellSettings[i].recastTimer.Start(); + } +} + +void Mob::DisableBotSpellTimers() { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + _spellSettings[i].recastTimer.Disable(); + } +} + +bool Mob::GetUltimateSpellHold(uint16 spellType, Mob* tar) { + if (!tar) { + return GetSpellHold(spellType); + } + + if (tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot()) { + return tar->GetOwner()->GetSpellHold(GetPetSpellType(spellType)); + } + + if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType) && tar->IsOfClientBot()) { + return tar->GetSpellHold(spellType); + } + + return GetSpellHold(spellType); +} + +uint16 Mob::GetUltimateSpellDelay(uint16 spellType, Mob* tar) { + if (!tar) { + return GetSpellDelay(spellType); + } + + if (tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot()) { + return tar->GetOwner()->GetSpellDelay(GetPetSpellType(spellType)); + } + + if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType) && tar->IsOfClientBot()) { + return tar->GetSpellDelay(spellType); + } + + return GetSpellDelay(spellType); +} + +bool Mob::GetUltimateSpellDelayCheck(uint16 spellType, Mob* tar) { + if (!tar) { + return SpellTypeRecastCheck(spellType); + } + + if (tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot()) { + return tar->GetOwner()->SpellTypeRecastCheck(GetPetSpellType(spellType)); + } + + if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType) && tar->IsOfClientBot()) { + return tar->SpellTypeRecastCheck(spellType); + } + + return SpellTypeRecastCheck(spellType); +} + +uint8 Mob::GetUltimateSpellMinThreshold(uint16 spellType, Mob* tar) { + if (!tar) { + return GetSpellMinThreshold(spellType); + } + + if (tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot()) { + return tar->GetOwner()->GetSpellMinThreshold(GetPetSpellType(spellType)); + } + + if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType) && tar->IsOfClientBot()) { + return tar->GetSpellMinThreshold(spellType); + } + + return GetSpellMinThreshold(spellType); +} + +uint8 Mob::GetUltimateSpellMaxThreshold(uint16 spellType, Mob* tar) { + if (!tar) { + return GetSpellMaxThreshold(spellType); + } + + if (tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot()) { + return tar->GetOwner()->GetSpellMaxThreshold(GetPetSpellType(spellType)); + } + + if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType) && tar->IsOfClientBot()) { + return tar->GetSpellMaxThreshold(spellType); + } + + return GetSpellMaxThreshold(spellType); +} + +uint16 Mob::GetPetSpellType(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::VeryFastHeals: + return BotSpellTypes::PetVeryFastHeals; + case BotSpellTypes::FastHeals: + return BotSpellTypes::PetFastHeals; + case BotSpellTypes::RegularHeal: + return BotSpellTypes::PetRegularHeals; + case BotSpellTypes::CompleteHeal: + return BotSpellTypes::PetCompleteHeals; + case BotSpellTypes::HoTHeals: + return BotSpellTypes::PetHoTHeals; + case BotSpellTypes::Buff: + return BotSpellTypes::PetBuffs; + default: + break; + } + + return spellType; +} + +uint8 Mob::GetHPRatioForSpellType(uint16 spellType, Mob* tar) { + switch (spellType) { + case BotSpellTypes::Escape: + case BotSpellTypes::HateRedux: + case BotSpellTypes::InCombatBuff: + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::AELifetap: + case BotSpellTypes::Lifetap: + case BotSpellTypes::OutOfCombatBuffSong: + case BotSpellTypes::Pet: + case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::PreCombatBuffSong: + return GetHPRatio(); + default: + return tar->GetHPRatio(); + } + + return tar->GetHPRatio(); +} + +void Mob::SetBotSetting(uint8 settingType, uint16 botSetting, int settingValue) { + if (!IsOfClientBot()) { + return; + } + + if (IsClient()) { + CastToClient()->SetBotSetting(settingType, botSetting, settingValue); + return; + } + + if (IsBot()) { + CastToBot()->SetBotSetting(settingType, botSetting, settingValue); + return; + } + + return; +} + +void Mob::SetBaseSetting(uint16 baseSetting, int settingValue) { + switch (baseSetting) { + case BotBaseSettings::IllusionBlock: + SetIllusionBlock(settingValue); + break; + default: + break; + } +} + +bool Mob::TargetValidation(Mob* other) { + if (!other || GetAppearance() == eaDead) { + return false; + } + + return true; +} + std::unordered_map &Mob::GetCloseMobList(float distance) { return entity_list.GetCloseMobList(this, distance); diff --git a/zone/mob.h b/zone/mob.h index 8914534cb..65b37d3c1 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -93,6 +93,28 @@ struct AppearanceStruct { uint8 texture = UINT8_MAX; }; +struct BotSpellSettings_Struct +{ + uint16 spellType; // type ID of bot category + std::string shortName; // type short name of bot category + std::string name; // type name of bot category + bool hold; // 0 = allow spell type, 1 = hold spell type + uint16 delay; // delay between casts of spell type, 1ms-60,000ms + uint8 minThreshold; // minimum target health threshold to allow casting of spell type + uint8 maxThreshold; // maximum target health threshold to allow casting of spell type + uint16 resistLimit; // resist limit to skip spell type + bool aggroCheck; // whether or not to check for possible aggro before casting + uint8 minManaPct; // lower mana percentage limit to allow spell cast + uint8 maxManaPct; // upper mana percentage limit to allow spell cast + uint8 minHPPct; // lower HP percentage limit to allow spell cast + uint8 maxHPPct; // upper HP percentage limit to allow spell cast + uint16 idlePriority; // idle priority of the spell type + uint16 engagedPriority; // engaged priority of the spell type + uint16 pursuePriority; // pursue priority of the spell type + uint16 AEOrGroupTargetCount; // require target count to cast an AE or Group spell type + Timer recastTimer; // recast timer based off delay +}; + class DataBucketKey; class Mob : public Entity { public: @@ -208,6 +230,8 @@ public: // Bot attack flag Timer bot_attack_flag_timer; + std::vector _spellSettings; + //Somewhat sorted: needs documenting! //Attack @@ -403,6 +427,51 @@ public: virtual bool CheckFizzle(uint16 spell_id); virtual bool CheckSpellLevelRestriction(Mob *caster, uint16 spell_id); virtual bool IsImmuneToSpell(uint16 spell_id, Mob *caster); + + virtual bool IsImmuneToBotSpell(uint16 spell_id, Mob* caster); + + inline bool SpellTypeRecastCheck(uint16 spellType) { return (IsClient() ? true : _spellSettings[spellType].recastTimer.GetRemainingTime() > 0 ? false : true); } + + uint16 GetSpellTypeIDByShortName(std::string spellTypeString); + + std::string GetSpellTypeNameByID(uint16 spellType); + std::string GetSpellTypeShortNameByID(uint16 spellType); + + bool GetDefaultSpellHold(uint16 spellType); + uint16 GetDefaultSpellDelay(uint16 spellType); + uint8 GetDefaultSpellMinThreshold(uint16 spellType); + uint8 GetDefaultSpellMaxThreshold(uint16 spellType); + + inline bool GetSpellHold(uint16 spellType) const { return _spellSettings[spellType].hold; } + void SetSpellHold(uint16 spellType, bool holdStatus); + inline uint16 GetSpellDelay(uint16 spellType) const { return _spellSettings[spellType].delay; } + void SetSpellDelay(uint16 spellType, uint16 delayValue); + inline uint8 GetSpellMinThreshold(uint16 spellType) const { return _spellSettings[spellType].minThreshold; } + void SetSpellMinThreshold(uint16 spellType, uint8 thresholdValue); + inline uint8 GetSpellMaxThreshold(uint16 spellType) const { return _spellSettings[spellType].maxThreshold; } + void SetSpellMaxThreshold(uint16 spellType, uint8 thresholdValue); + + inline uint16 GetSpellTypeRecastTimer(uint16 spellType) { return _spellSettings[spellType].recastTimer.GetRemainingTime(); } + void SetSpellTypeRecastTimer(uint16 spellType, uint32 recastTime); + + uint8 GetHPRatioForSpellType(uint16 spellType, Mob* tar); + bool GetUltimateSpellHold(uint16 spellType, Mob* tar); + uint16 GetUltimateSpellDelay(uint16 spellType, Mob* tar); + bool GetUltimateSpellDelayCheck(uint16 spellType, Mob* tar); + uint8 GetUltimateSpellMinThreshold(uint16 spellType, Mob* tar); + uint8 GetUltimateSpellMaxThreshold(uint16 spellType, Mob* tar); + + uint16 GetPetSpellType(uint16 spellType); + + void DisableBotSpellTimers(); + void StartBotSpellTimers(); + + void SetBotSetting(uint8 settingType, uint16 botSetting, int settingValue); + void SetBaseSetting(uint16 baseSetting, int settingValue); + + void SetIllusionBlock(bool value) { _illusionBlock = value; } + bool GetIllusionBlock() const { return _illusionBlock; } + virtual float GetAOERange(uint16 spell_id); void InterruptSpell(uint16 spellid = SPELL_UNKNOWN); void InterruptSpell(uint16, uint16, uint16 spellid = SPELL_UNKNOWN); @@ -791,6 +860,11 @@ public: bool CheckLosFN(float posX, float posY, float posZ, float mobSize); static bool CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarget, float sizeTarget); virtual bool CheckWaterLoS(Mob* m); + bool CheckPositioningLosFN(Mob* other, float posX, float posY, float posZ); + bool CheckLosCheat(Mob* who, Mob* other); + bool CheckLosCheatExempt(Mob* who, Mob* other); + bool DoLosChecks(Mob* who, Mob* other); + bool TargetValidation(Mob* other); inline void SetLastLosState(bool value) { last_los_check = value; } inline bool CheckLastLosState() const { return last_los_check; } std::string GetMobDescription(); @@ -854,6 +928,7 @@ public: void ShowStats(Client* client); void ShowBuffs(Client* c); bool PlotPositionAroundTarget(Mob* target, float &x_dest, float &y_dest, float &z_dest, bool lookForAftArc = true); + bool PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behindOnly = false, bool frontOnly = false, bool bypassLoS = false); virtual int GetKillExpMod() const { return 100; } // aura functions @@ -1252,13 +1327,30 @@ public: void NPCSpecialAttacks(const char* parse, int permtag, bool reset = true, bool remove = false); inline uint32 DontHealMeBefore() const { return pDontHealMeBefore; } + inline uint32 DontGroupHealMeBefore() const { return pDontGroupHealMeBefore; } + inline uint32 DontGroupHoTHealMeBefore() const { return pDontGroupHoTHealMeBefore; } + inline uint32 DontRegularHealMeBefore() const { return pDontRegularHealMeBefore; } + inline uint32 DontVeryFastHealMeBefore() const { return pDontVeryFastHealMeBefore; } + inline uint32 DontFastHealMeBefore() const { return pDontFastHealMeBefore; } + inline uint32 DontCompleteHealMeBefore() const { return pDontCompleteHealMeBefore; } + inline uint32 DontGroupCompleteHealMeBefore() const { return pDontGroupCompleteHealMeBefore; } + inline uint32 DontHotHealMeBefore() const { return pDontHotHealMeBefore; } inline uint32 DontBuffMeBefore() const { return pDontBuffMeBefore; } inline uint32 DontDotMeBefore() const { return pDontDotMeBefore; } inline uint32 DontRootMeBefore() const { return pDontRootMeBefore; } inline uint32 DontSnareMeBefore() const { return pDontSnareMeBefore; } inline uint32 DontCureMeBefore() const { return pDontCureMeBefore; } + void SetDontRootMeBefore(uint32 time) { pDontRootMeBefore = time; } void SetDontHealMeBefore(uint32 time) { pDontHealMeBefore = time; } + void SetDontGroupHealMeBefore(uint32 time) { pDontGroupHealMeBefore = time; } + void SetDontGroupHoTHealMeBefore(uint32 time) { pDontGroupHoTHealMeBefore = time; } + void SetDontRegularHealMeBefore(uint32 time) { pDontRegularHealMeBefore = time; } + void SetDontVeryFastHealMeBefore(uint32 time) { pDontVeryFastHealMeBefore = time; } + void SetDontFastHealMeBefore(uint32 time) { pDontFastHealMeBefore = time; } + void SetDontCompleteHealMeBefore(uint32 time) { pDontCompleteHealMeBefore = time; } + void SetDontGroupCompleteHealMeBefore(uint32 time) { pDontGroupCompleteHealMeBefore = time; } + void SetDontHotHealMeBefore(uint32 time) { pDontHotHealMeBefore = time; } void SetDontBuffMeBefore(uint32 time) { pDontBuffMeBefore = time; } void SetDontDotMeBefore(uint32 time) { pDontDotMeBefore = time; } void SetDontSnareMeBefore(uint32 time) { pDontSnareMeBefore = time; } @@ -1857,6 +1949,14 @@ protected: bool pause_timer_complete; bool DistractedFromGrid; uint32 pDontHealMeBefore; + uint32 pDontGroupHealMeBefore; + uint32 pDontGroupHoTHealMeBefore; + uint32 pDontRegularHealMeBefore; + uint32 pDontVeryFastHealMeBefore; + uint32 pDontFastHealMeBefore; + uint32 pDontCompleteHealMeBefore; + uint32 pDontGroupCompleteHealMeBefore; + uint32 pDontHotHealMeBefore; uint32 pDontBuffMeBefore; uint32 pDontDotMeBefore; uint32 pDontRootMeBefore; @@ -1879,6 +1979,9 @@ protected: //bot attack flags std::vector bot_attack_flags; + //bot related settings + bool _illusionBlock; + glm::vec3 m_TargetRing; GravityBehavior flymode; diff --git a/zone/perl_bot.cpp b/zone/perl_bot.cpp index 86f104565..f24134c6a 100644 --- a/zone/perl_bot.cpp +++ b/zone/perl_bot.cpp @@ -425,11 +425,6 @@ void Perl_Bot_SetExpansionBitmask(Bot* self, int expansion_bitmask) self->SetExpansionBitmask(expansion_bitmask); } -void Perl_Bot_SetExpansionBitmask(Bot* self, int expansion_bitmask, bool save) -{ - self->SetExpansionBitmask(expansion_bitmask, save); -} - void Perl_Bot_SetSpellDuration(Bot* self, int spell_id) { self->SetSpellDuration(spell_id); @@ -716,7 +711,6 @@ void perl_register_bot() package.add("SendPayload", (void(*)(Bot*, int, std::string))&Perl_Bot_SendPayload); package.add("SendSpellAnim", &Perl_Bot_SendSpellAnim); package.add("SetExpansionBitmask", (void(*)(Bot*, int))&Perl_Bot_SetExpansionBitmask); - package.add("SetExpansionBitmask", (void(*)(Bot*, int, bool))&Perl_Bot_SetExpansionBitmask); package.add("SetDisciplineReuseTimer", (void(*)(Bot*, uint16))&Perl_Bot_SetDisciplineReuseTimer); package.add("SetDisciplineReuseTimer", (void(*)(Bot*, uint16, uint32))&Perl_Bot_SetDisciplineReuseTimer); package.add("SetItemReuseTimer", (void(*)(Bot*, uint32))&Perl_Bot_SetItemReuseTimer); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 8bb39afda..528445d41 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -1144,7 +1144,7 @@ void Perl_Client_SetStartZone(Client* self, uint32 zone_id, float x, float y, fl void Perl_Client_KeyRingAdd(Client* self, uint32 item_id) // @categories Account and Character, Inventory and Items { - self->KeyRingAdd(item_id);; + self->KeyRingAdd(item_id); } bool Perl_Client_KeyRingCheck(Client* self, uint32 item_id) // @categories Account and Character, Inventory and Items @@ -1154,7 +1154,7 @@ bool Perl_Client_KeyRingCheck(Client* self, uint32 item_id) // @categories Accou void Perl_Client_AddPVPPoints(Client* self, uint32 points) // @categories Currency and Points { - self->AddPVPPoints(points);; + self->AddPVPPoints(points); } void Perl_Client_AddCrystals(Client* self, uint32 radiant_count, uint32 ebon_count) // @categories Currency and Points diff --git a/zone/perl_npc.cpp b/zone/perl_npc.cpp index b47b8c0e8..4f0999376 100644 --- a/zone/perl_npc.cpp +++ b/zone/perl_npc.cpp @@ -892,7 +892,7 @@ void perl_register_npc() package.add("IsGuarding", &Perl_NPC_IsGuarding); package.add("IsLDoNLocked", &Perl_NPC_IsLDoNLocked); package.add("IsLDoNTrapped", &Perl_NPC_IsLDoNTrapped); - package.add("IsLDoNTrapDetected", &Perl_NPC_IsLDoNTrapDetected);; + package.add("IsLDoNTrapDetected", &Perl_NPC_IsLDoNTrapDetected); package.add("IsOnHatelist", &Perl_NPC_IsOnHatelist); package.add("IsRaidTarget", &Perl_NPC_IsRaidTarget); package.add("IsRareSpawn", &Perl_NPC_IsRareSpawn); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index cf77e503e..afae9367b 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -2896,7 +2896,7 @@ bool QuestManager::createBot(const char *name, const char *lastname, uint8 level initiator->Message( Chat::White, fmt::format( - "{} has invalid characters. You can use only the A-Z, a-z and _ characters in a bot name.", + "{} has invalid characters. You can use only the A-Z, a-z and _ characters in a bot name and it must be between 4 and 15 characters long.", new_bot->GetCleanName() ).c_str() ); diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 93721f8c9..6e9f33d05 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -1483,6 +1483,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Illusion: race %d", effect_value); #endif + if (caster && caster->IsOfClientBot() && GetIllusionBlock()) { + break; + } + ApplySpellEffectIllusion(spell_id, caster, buffslot, spells[spell_id].base_value[i], spells[spell_id].limit_value[i], spells[spell_id].max_value[i]); break; } @@ -1492,6 +1496,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Illusion Copy"); #endif + if (caster && caster->IsOfClientBot() && GetIllusionBlock()) { + break; + } + if(caster && caster->GetTarget()){ SendIllusionPacket ( @@ -10658,7 +10666,7 @@ int Mob::GetBuffStatValueBySlot(uint8 slot, const char* stat_identifier) if (id == "caster_level") { return buffs[slot].casterlevel; } else if (id == "spell_id") { return buffs[slot].spellid; } - else if (id == "caster_id") { return buffs[slot].spellid;; } + else if (id == "caster_id") { return buffs[slot].spellid; } else if (id == "ticsremaining") { return buffs[slot].ticsremaining; } else if (id == "counters") { return buffs[slot].counters; } else if (id == "hit_number") { return buffs[slot].hit_number; } diff --git a/zone/spells.cpp b/zone/spells.cpp index 7b644cd3a..375ab1571 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -473,7 +473,6 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, CastedSpellFinished(spell_id, target_id, slot, mana_cost, item_slot, resist_adjust); // return true; } - // ok we know it has a cast time so we can start the timer now spellend_timer.Start(cast_time); @@ -1280,6 +1279,17 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid) EQApplicationPacket *outapp = nullptr; uint16 message_other; bool bard_song_mode = false; //has the bard song gone to auto repeat mode + + if (IsBot()) { + CastToBot()->SetCastedSpellType(UINT16_MAX); + } + + if (IsBot() && IsValidSpell(spellid)) { + if (CastToBot()->CheckSpellRecastTimer(spellid)) { + CastToBot()->ClearSpellRecastTimer(spellid); + } + } + if (!IsValidSpell(spellid)) { if (bardsong) { spellid = bardsong; @@ -2088,7 +2098,17 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce } case ST_Pet: { - spell_target = GetPet(); + if ( + !( + IsBot() && + spell_target && + spell_target->GetOwner() != this && + RuleB(Bots, CanCastPetOnlyOnOthersPets) + ) + ) { + + spell_target = GetPet(); + } if(!spell_target) { LogSpells("Spell [{}] canceled: invalid target (no pet)", spell_id); @@ -2835,6 +2855,13 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in } } } + + if (IsBot() && !isproc && !IsFromTriggeredSpell(slot, inventory_slot) && IsValidSpell(spell_id)) { + if (spells[spell_id].recast_time > 1000 && !spells[spell_id].is_discipline) { + CastToBot()->SetSpellRecastTimer(spell_id); + } + } + /* Set Recast Timer on item clicks, including augmenets. */ @@ -3426,12 +3453,17 @@ bool Mob::CheckSpellLevelRestriction(Mob *caster, uint16 spell_id) bool can_cast = true; // NON GM clients might be restricted by rule setting - if (caster->IsClient()) { + if (caster->IsOfClientBot()) { if (IsClient()) { // Only restrict client on client for this rule if (RuleB(Spells, BuffLevelRestrictions)) { check_for_restrictions = true; } } + else if (IsBot()) { + if (RuleB(Bots, BotBuffLevelRestrictions)) { + check_for_restrictions = true; + } + } } // NPCS might be restricted by rule setting else if (RuleB(Spells, NPCBuffLevelRestrictions)) { @@ -3578,6 +3610,19 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid ); } + if (caster->IsBot() && RuleB(Bots, BotsUseLiveBlockedMessage) && caster->GetClass() != Class::Bard) { + caster->GetOwner()->Message( + Chat::Red, + fmt::format( + "{}'s {} did not take hold on {}. (Blocked by {}.)", + caster->GetCleanName(), + spells[spell_id].name, + GetName(), + spells[curbuf.spellid].name + ).c_str() + ); + } + std::function f = [&]() { return fmt::format( "{} {}", @@ -3772,9 +3817,15 @@ int Mob::CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite) continue; } - if(curbuf.spellid == spellid) - return(-1); //do not recast a buff we already have on, we recast fast enough that we dont need to refresh our buffs - + if (IsBot() && (GetClass() == Class::Bard) && curbuf.spellid == spellid && curbuf.ticsremaining == 0 && curbuf.casterid == GetID()) { + LogAI("Bard check for song, spell [{}] has [{}] ticks remaining.", spellid, curbuf.ticsremaining); + firstfree = i; + return firstfree; + } + else { + if (curbuf.spellid == spellid) + return(-1); //do not recast a buff we already have on, we recast fast enough that we dont need to refresh our buffs + } // there's a buff in this slot ret = CheckStackConflict(curbuf.spellid, curbuf.casterlevel, spellid, caster_level, nullptr, nullptr, i); if(ret == 1) { @@ -4424,6 +4475,18 @@ bool Mob::SpellOnTarget( } else { MessageString(Chat::SpellFailure, TARGET_RESISTED, spells[spell_id].name); spelltar->MessageString(Chat::SpellFailure, YOU_RESIST, spells[spell_id].name); + + if (IsBot() && RuleB(Bots, ShowResistMessagesToOwner)) { + CastToBot()->GetBotOwner()->Message + (Chat::SpellFailure, + fmt::format( + "{} resisted {}'s spell: {}.", + spelltar->GetCleanName(), + GetCleanName(), + spells[spell_id].name + ).c_str() + ); + } } if (spelltar->IsAIControlled()) { @@ -4642,6 +4705,14 @@ bool Mob::SpellOnTarget( LogSpells("Cast of [{}] by [{}] on [{}] complete successfully", spell_id, GetName(), spelltar->GetName()); + if (IsBot() && (CastToBot()->GetCastedSpellType() != UINT16_MAX)) { + if (!CastToBot()->IsCommandedSpell()) { + CastToBot()->SetBotSpellRecastTimer(CastToBot()->GetCastedSpellType(), spelltar); + } + + CastToBot()->SetCastedSpellType(UINT16_MAX); + } + return true; } @@ -7444,3 +7515,123 @@ bool Mob::CheckWaterLoS(Mob* m) zone->watermap->InLiquid(m->GetPosition()) ); } + +bool Mob::IsImmuneToBotSpell(uint16 spell_id, Mob* caster) +{ + int effect_index; + + if (caster == nullptr) + return(false); + + //TODO: this function loops through the effect list for + //this spell like 10 times, this could easily be consolidated + //into one loop through with a switch statement. + + LogSpells("Checking to see if we are immune to spell [{}] cast by [{}]", spell_id, caster->GetName()); + + if (!IsValidSpell(spell_id)) + return true; + + if (IsBeneficialSpell(spell_id) && (caster->GetNPCTypeID())) //then skip the rest, stop NPCs aggroing each other with buff spells. 2013-03-05 + return false; + + if (IsMesmerizeSpell(spell_id)) + { + if (GetSpecialAbility(SpecialAbility::MesmerizeImmunity)) { + return true; + } + + // check max level for spell + effect_index = GetSpellEffectIndex(spell_id, SE_Mez); + assert(effect_index >= 0); + // NPCs get to ignore the max level + if ((GetLevel() > spells[spell_id].max_value[effect_index]) && + (!caster->IsNPC() || (caster->IsNPC() && !RuleB(Spells, NPCIgnoreBaseImmunity)))) + { + return true; + } + } + + // slow and haste spells + if (GetSpecialAbility(SpecialAbility::SlowImmunity) && IsEffectInSpell(spell_id, SE_AttackSpeed)) + { + return true; + } + + // client vs client fear + if (IsEffectInSpell(spell_id, SE_Fear)) + { + effect_index = GetSpellEffectIndex(spell_id, SE_Fear); + if (GetSpecialAbility(SpecialAbility::FearImmunity)) { + return true; + } + else if (IsClient() && caster->IsClient() && (caster->CastToClient()->GetGM() == false)) + { + LogSpells("Clients cannot fear eachother!"); + caster->MessageString(Chat::Red, IMMUNE_FEAR); // need to verify message type, not in MQ2Cast for easy look up + return true; + } + else if (GetLevel() > spells[spell_id].max_value[effect_index] && spells[spell_id].max_value[effect_index] != 0) + { + return true; + } + else if (CheckAATimer(aaTimerWarcry)) + { + return true; + } + } + + if (IsCharmSpell(spell_id)) + { + if (GetSpecialAbility(SpecialAbility::CharmImmunity)) + { + return true; + } + + if (this == caster) + { + return true; + } + + //let npcs cast whatever charm on anyone + if (!caster->IsNPC()) + { + // check level limit of charm spell + effect_index = GetSpellEffectIndex(spell_id, SE_Charm); + assert(effect_index >= 0); + if (GetLevel() > spells[spell_id].max_value[effect_index] && spells[spell_id].max_value[effect_index] != 0) + { + return true; + } + } + } + + if + ( + IsEffectInSpell(spell_id, SE_Root) || + IsEffectInSpell(spell_id, SE_MovementSpeed) + ) + { + if (GetSpecialAbility(SpecialAbility::SnareImmunity)) { + return true; + } + } + + if (IsLifetapSpell(spell_id)) + { + if (this == caster) + { + return true; + } + } + + if (IsSacrificeSpell(spell_id)) + { + if (this == caster) + { + return true; + } + } + + return false; +} From 4aa7a18b4fb99fa30147b705cde06ae7f8d19f62 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 31 Oct 2024 07:32:16 -0500 Subject: [PATCH 02/97] More fixes TGB, ^cast, group/ae checks, in group/raid checks, inviting others bots to group, group disband fix, prevent rogue bs spam, ^follow fixes and cleanup, follow owner only by default when joining raid/group, group buff fixes for bots, range fixes for group buffs --- common/ruletypes.h | 4 +- common/spdat.cpp | 76 ++- common/spdat.h | 3 + zone/bot.cpp | 901 +++++++++++++++----------------- zone/bot.h | 8 +- zone/bot_commands/bot.cpp | 23 +- zone/bot_commands/cast.cpp | 12 +- zone/bot_commands/follow.cpp | 254 ++++++--- zone/bot_commands/item_use.cpp | 8 +- zone/bot_commands/mesmerize.cpp | 2 +- zone/bot_raid.cpp | 12 +- zone/botspellsai.cpp | 73 ++- zone/groups.cpp | 138 ++++- zone/groups.h | 2 + zone/mob.cpp | 158 +++--- zone/mob.h | 1 + zone/spells.cpp | 48 +- 17 files changed, 1053 insertions(+), 670 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index aec9bcb8b..576d5e474 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -830,7 +830,7 @@ RULE_INT(Bots, PullSpellID, 5225, "Default 5225 - Throw Stone. Spell that will b RULE_BOOL(Bots, AllowBotEquipAnyClassGear, false, "Allows Bots to wear Equipment even if their class is not valid") RULE_BOOL(Bots, BotArcheryConsumesAmmo, true, "Set to false to disable Archery Ammo Consumption") RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption") -RULE_INT(Bots, StackSizeMin, 100, "100 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).") +RULE_INT(Bots, StackSizeMin, 20, "20 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).") RULE_INT(Bots, HasOrMayGetAggroThreshold, 90, "90 Default. Percent threshold of total hate where bots will stop casting spells that generate hate if they are set to try to not pull aggro via spells.") RULE_BOOL(Bots, UseFlatNormalMeleeRange, false, "False Default. If true, bots melee distance will be a flat distance set by Bots:NormalMeleeRangeDistance.") RULE_REAL(Bots, NormalMeleeRangeDistance, 0.75, "Multiplier of the max melee range at which a bot will stand in melee combat. 0.75 Recommended, max melee for all abilities to land.") @@ -865,6 +865,8 @@ RULE_INT(Bots, StatusSpawnLimit, 120, "Minimum status to bypass spawn limit. Def RULE_INT(Bots, MinStatusToBypassCreateLimit, 100, "Minimum status to bypass the anti-spam system") RULE_INT(Bots, StatusCreateLimit, 120, "Minimum status to bypass spawn limit. Default 120.") RULE_BOOL(Bots, BardsAnnounceCasts, false, "This determines whether or not Bard bots will announce that they're casting songs (Buffs, Heals, Nukes, Slows, etc.) they will always announce Mez.") +RULE_BOOL(Bots, EnableBotTGB, true, "If enabled bots will cast group buffs as TGB.") +RULE_BOOL(Bots, DoResponseAnimations, true, "If enabled bots will do animations to certain responses or commands.") RULE_CATEGORY_END() RULE_CATEGORY(Chat) diff --git a/common/spdat.cpp b/common/spdat.cpp index 53bf03f6d..48b6351fb 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -2782,15 +2782,16 @@ int8 SpellEffectsCount(uint16 spell_id) { if (!IsValidSpell(spell_id)) { return false; } - int8 i = 0; + + int8 x = 0; for (int i = 0; i < EFFECT_COUNT; i++) { if (!IsBlankSpellEffect(spell_id, i)) { - ++i; + ++x; } } - return i; + return x; } bool IsLichSpell(uint16 spell_id) @@ -3175,3 +3176,72 @@ bool RequiresStackCheck(uint16 spellType) { return true; } + +bool IsResistanceOnlySpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + for (int i = 0; i < EFFECT_COUNT; i++) { + if (IsBlankSpellEffect(spell_id, i)) { + continue; + } + + if ( + spell.effect_id[i] == SE_ResistFire || + spell.effect_id[i] == SE_ResistCold || + spell.effect_id[i] == SE_ResistPoison || + spell.effect_id[i] == SE_ResistDisease || + spell.effect_id[i] == SE_ResistMagic || + spell.effect_id[i] == SE_ResistCorruption || + spell.effect_id[i] == SE_ResistAll + ) { + continue; + } + + return false; + } + + return true; +} + +bool IsDamageShieldOnlySpell(uint16 spell_id) { + if (SpellEffectsCount(spell_id) == 1 && IsEffectInSpell(spell_id, SE_DamageShield)) { + return true; + } + + return false; +} + +bool IsDamageShieldAndResistanceSpellOnly(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + for (int i = 0; i < EFFECT_COUNT; i++) { + if (IsBlankSpellEffect(spell_id, i)) { + continue; + } + + if ( + spell.effect_id[i] == SE_DamageShield || + spell.effect_id[i] == SE_ResistFire || + spell.effect_id[i] == SE_ResistCold || + spell.effect_id[i] == SE_ResistPoison || + spell.effect_id[i] == SE_ResistDisease || + spell.effect_id[i] == SE_ResistMagic || + spell.effect_id[i] == SE_ResistCorruption || + spell.effect_id[i] == SE_ResistAll + ) { + continue; + } + + return false; + } + + return true; +} diff --git a/common/spdat.h b/common/spdat.h index 432774b95..b07bf7d96 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -1722,5 +1722,8 @@ bool IsLichSpell(uint16 spell_id); bool IsInstantHealSpell(uint32 spell_id); bool IsResurrectSpell(uint16 spell_id); bool RequiresStackCheck(uint16 spellType); +bool IsResistanceOnlySpell(uint16 spell_id); +bool IsDamageShieldOnlySpell(uint16 spell_id); +bool IsDamageShieldAndResistanceSpellOnly(uint16 spell_id); #endif diff --git a/zone/bot.cpp b/zone/bot.cpp index f634eb43e..16f4b9db3 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -87,6 +87,7 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm SetGuardFlag(false); SetHoldFlag(false); SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); @@ -210,6 +211,7 @@ Bot::Bot( SetGuardFlag(false); SetHoldFlag(false); SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); @@ -268,146 +270,146 @@ Bot::Bot( for (int x1 = 0; x1 < EFFECT_COUNT; x1++) { switch (spell.effect_id[x1]) { - case SE_IllusionCopy: - case SE_Illusion: { - if (GetIllusionBlock()) { - break; - } - - if (spell.base_value[x1] == -1) { - if (gender == Gender::Female) { - gender = Gender::Male; - } else if (gender == Gender::Male) { - gender = Gender::Female; + case SE_IllusionCopy: + case SE_Illusion: { + if (GetIllusionBlock()) { + break; } - SendIllusionPacket( - AppearanceStruct{ - .gender_id = gender, - .race_id = GetRace(), + if (spell.base_value[x1] == -1) { + if (gender == Gender::Female) { + gender = Gender::Male; + } else if (gender == Gender::Male) { + gender = Gender::Female; } - ); - } else if (spell.base_value[x1] == -2) // WTF IS THIS - { - if (GetRace() == IKSAR || GetRace() == VAHSHIR || GetRace() <= GNOME) { + SendIllusionPacket( AppearanceStruct{ - .gender_id = GetGender(), - .helmet_texture = static_cast(spell.max_value[x1]), + .gender_id = gender, .race_id = GetRace(), + } + ); + } else if (spell.base_value[x1] == -2) // WTF IS THIS + { + if (GetRace() == IKSAR || GetRace() == VAHSHIR || GetRace() <= GNOME) { + SendIllusionPacket( + AppearanceStruct{ + .gender_id = GetGender(), + .helmet_texture = static_cast(spell.max_value[x1]), + .race_id = GetRace(), + .texture = static_cast(spell.limit_value[x1]), + } + ); + } + } else if (spell.max_value[x1] > 0) { + SendIllusionPacket( + AppearanceStruct{ + .helmet_texture = static_cast(spell.max_value[x1]), + .race_id = static_cast(spell.base_value[x1]), + .texture = static_cast(spell.limit_value[x1]), + } + ); + } else { + SendIllusionPacket( + AppearanceStruct{ + .helmet_texture = static_cast(spell.max_value[x1]), + .race_id = static_cast(spell.base_value[x1]), .texture = static_cast(spell.limit_value[x1]), } ); } - } else if (spell.max_value[x1] > 0) { - SendIllusionPacket( - AppearanceStruct{ - .helmet_texture = static_cast(spell.max_value[x1]), - .race_id = static_cast(spell.base_value[x1]), - .texture = static_cast(spell.limit_value[x1]), - } - ); - } else { - SendIllusionPacket( - AppearanceStruct{ - .helmet_texture = static_cast(spell.max_value[x1]), - .race_id = static_cast(spell.base_value[x1]), - .texture = static_cast(spell.limit_value[x1]), - } - ); - } - switch (spell.base_value[x1]) { - case OGRE: - SendAppearancePacket(AppearanceType::Size, 9); - break; - case TROLL: - SendAppearancePacket(AppearanceType::Size, 8); - break; - case VAHSHIR: - case BARBARIAN: - SendAppearancePacket(AppearanceType::Size, 7); - break; - case HALF_ELF: - case WOOD_ELF: - case DARK_ELF: - case FROGLOK: - SendAppearancePacket(AppearanceType::Size, 5); - break; - case DWARF: - SendAppearancePacket(AppearanceType::Size, 4); - break; - case HALFLING: - case GNOME: - SendAppearancePacket(AppearanceType::Size, 3); - break; - default: - SendAppearancePacket(AppearanceType::Size, 6); + switch (spell.base_value[x1]) { + case OGRE: + SendAppearancePacket(AppearanceType::Size, 9); + break; + case TROLL: + SendAppearancePacket(AppearanceType::Size, 8); + break; + case VAHSHIR: + case BARBARIAN: + SendAppearancePacket(AppearanceType::Size, 7); + break; + case HALF_ELF: + case WOOD_ELF: + case DARK_ELF: + case FROGLOK: + SendAppearancePacket(AppearanceType::Size, 5); + break; + case DWARF: + SendAppearancePacket(AppearanceType::Size, 4); + break; + case HALFLING: + case GNOME: + SendAppearancePacket(AppearanceType::Size, 3); + break; + default: + SendAppearancePacket(AppearanceType::Size, 6); + break; + } break; } - 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(AppearanceType::Invisibility, 1); - break; - } - case SE_Levitate: - { - if (!zone->CanLevitate()) + case SE_Silence: { - SendAppearancePacket(AppearanceType::FlyMode, 0); - BuffFadeByEffect(SE_Levitate); + Silence(true); + break; } - else { - SendAppearancePacket(AppearanceType::FlyMode, 2); + case SE_Amnesia: + { + Amnesia(true); + break; + } + case SE_DivineAura: + { + invulnerable = true; + break; + } + case SE_Invisibility2: + case SE_Invisibility: + { + invisible = true; + SendAppearancePacket(AppearanceType::Invisibility, 1); + break; + } + case SE_Levitate: + { + if (!zone->CanLevitate()) + { + SendAppearancePacket(AppearanceType::FlyMode, 0); + BuffFadeByEffect(SE_Levitate); + } + else { + SendAppearancePacket(AppearanceType::FlyMode, 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].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel); + break; + } + case SE_DefensiveProc: + { + AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); + break; + } + case SE_RangedProc: + { + AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); + break; } - 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].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel); - break; - } - case SE_DefensiveProc: - { - AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); - break; - } - case SE_RangedProc: - { - AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); - break; - } } } } @@ -1794,7 +1796,9 @@ void Bot::BotRangedAttack(Mob* other, bool CanDoubleAttack) { ) ) { if (!Ammo || ammoItem->GetCharges() < 1) { - GetOwner()->Message(Chat::Yellow, "I do not have enough any ammo."); + if (!GetCombatRoundForAlerts()) { + GetOwner()->Message(Chat::Yellow, "I do not have enough any ammo."); + } } return; @@ -1914,20 +1918,20 @@ bool Bot::CheckTripleAttack() ) ) { switch (GetClass()) { - case Class::Warrior: - chance = RuleI(Combat, ClassicTripleAttackChanceWarrior); - break; - case Class::Ranger: - chance = RuleI(Combat, ClassicTripleAttackChanceRanger); - break; - case Class::Monk: - chance = RuleI(Combat, ClassicTripleAttackChanceMonk); - break; - case Class::Berserker: - chance = RuleI(Combat, ClassicTripleAttackChanceBerserker); - break; - default: - break; + case Class::Warrior: + chance = RuleI(Combat, ClassicTripleAttackChanceWarrior); + break; + case Class::Ranger: + chance = RuleI(Combat, ClassicTripleAttackChanceRanger); + break; + case Class::Monk: + chance = RuleI(Combat, ClassicTripleAttackChanceMonk); + break; + case Class::Berserker: + chance = RuleI(Combat, ClassicTripleAttackChanceBerserker); + break; + default: + break; } } } @@ -2011,15 +2015,26 @@ void Bot::AI_Process() return; } - auto leash_owner = SetLeashOwner(bot_owner, bot_group, raid, r_group); + Client* leash_owner = bot_owner; if (!leash_owner) { return; } - SetFollowID(leash_owner->GetID()); + Mob* follow_mob = nullptr; - auto follow_mob = SetFollowMob(leash_owner); + if (!GetFollowID()) { + follow_mob = leash_owner; + } + else { + follow_mob = entity_list.GetMob(GetFollowID()); + + if (!follow_mob || !IsInGroupOrRaid(follow_mob)) { + follow_mob = leash_owner; + } + } + + SetFollowID(follow_mob->GetID()); SetBerserkState(); @@ -2091,6 +2106,7 @@ void Bot::AI_Process() // ATTACKING FLAG (HATE VALIDATION) if (GetAttackingFlag() && tar->CheckAggro(this)) { + SetCombatRoundForAlerts(true); SetAttackingFlag(false); } @@ -2261,6 +2277,7 @@ void Bot::AI_Process() } else { // Out-of-combat behavior SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); if (!bot_owner->GetBotPulling()) { @@ -2405,6 +2422,7 @@ bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) { AddToHateList(hater, 1); SetTarget(hater); SetAttackingFlag(); + SetCombatRoundForAlerts(); if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { GetPet()->AddToHateList(hater, 1); @@ -2866,6 +2884,7 @@ bool Bot::IsValidTarget( SetTarget(nullptr); SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); if (PULLING_BOT) { @@ -2899,6 +2918,7 @@ Mob* Bot::GetBotTarget(Client* bot_owner) WipeHateList(); SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); if (PULLING_BOT) { @@ -3117,6 +3137,7 @@ void Bot::SetOwnerTarget(Client* bot_owner) { } SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); @@ -3131,6 +3152,7 @@ void Bot::SetOwnerTarget(Client* bot_owner) { WipeHateList(); AddToHateList(attack_target, 1); SetTarget(attack_target); + SetCombatRoundForAlerts(); SetAttackingFlag(); if (GetPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { GetPet()->WipeHateList(); @@ -3143,6 +3165,7 @@ void Bot::SetOwnerTarget(Client* bot_owner) { void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) { SetAttackFlag(false); + SetCombatRoundForAlerts(true); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); @@ -3834,7 +3857,6 @@ bool Bot::AddBotToGroup(Bot* bot, Group* group) { if (bot && group->AddMember(bot)) { if (group->GetLeader()) { - bot->SetFollowID(group->GetLeader()->GetID()); // Need to send this only once when a group is formed with a bot so the client knows it is also the group leader if (group->GroupCount() == 2 && group->GetLeader()->IsClient()) { group->UpdateGroupAAs(); @@ -4631,15 +4653,15 @@ float Bot::GetProcChances(float ProcBonus, uint16 hand) { float ProcChance = 0.0f; uint32 weapon_speed = 0; switch (hand) { - case EQ::invslot::slotPrimary: - weapon_speed = attack_timer.GetDuration(); - break; - case EQ::invslot::slotSecondary: - weapon_speed = attack_dw_timer.GetDuration(); - break; - case EQ::invslot::slotRange: - weapon_speed = ranged_timer.GetDuration(); - break; + case EQ::invslot::slotPrimary: + weapon_speed = attack_timer.GetDuration(); + break; + case EQ::invslot::slotSecondary: + weapon_speed = attack_dw_timer.GetDuration(); + break; + case EQ::invslot::slotRange: + weapon_speed = ranged_timer.GetDuration(); + break; } if (weapon_speed < RuleI(Combat, MinHastedDelay)) @@ -4748,84 +4770,84 @@ int Bot::GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target) int base = EQ::skills::GetBaseDamage(skill); auto skill_level = GetSkill(skill); switch (skill) { - case EQ::skills::SkillDragonPunch: - case EQ::skills::SkillEagleStrike: - case EQ::skills::SkillTigerClaw: - if (skill_level >= 25) - base++; - if (skill_level >= 75) - base++; - if (skill_level >= 125) - base++; - if (skill_level >= 175) - base++; - return base; - case EQ::skills::SkillFrenzy: - if (GetBotItem(EQ::invslot::slotPrimary)) { - if (GetLevel() > 15) - base += GetLevel() - 15; - if (base > 23) - base = 23; - if (GetLevel() > 50) - base += 2; - if (GetLevel() > 54) + case EQ::skills::SkillDragonPunch: + case EQ::skills::SkillEagleStrike: + case EQ::skills::SkillTigerClaw: + if (skill_level >= 25) base++; - if (GetLevel() > 59) + if (skill_level >= 75) base++; - } - return base; - case EQ::skills::SkillFlyingKick: { - float skill_bonus = skill_level / 9.0f; - float ac_bonus = 0.0f; - auto inst = GetBotItem(EQ::invslot::slotFeet); - if (inst) - ac_bonus = inst->GetItemArmorClass(true) / 25.0f; - if (ac_bonus > skill_bonus) - ac_bonus = skill_bonus; - return static_cast(ac_bonus + skill_bonus); - } - case EQ::skills::SkillKick: { - float skill_bonus = skill_level / 10.0f; - float ac_bonus = 0.0f; - auto inst = GetBotItem(EQ::invslot::slotFeet); - if (inst) - ac_bonus = inst->GetItemArmorClass(true) / 25.0f; - if (ac_bonus > skill_bonus) - ac_bonus = skill_bonus; - return static_cast(ac_bonus + skill_bonus); - } - case EQ::skills::SkillBash: { - float skill_bonus = skill_level / 10.0f; - float ac_bonus = 0.0f; - const EQ::ItemInstance *inst = nullptr; - if (HasShieldEquipped()) - inst = GetBotItem(EQ::invslot::slotSecondary); - else if (HasTwoHanderEquipped()) - inst = GetBotItem(EQ::invslot::slotPrimary); - if (inst) - ac_bonus = inst->GetItemArmorClass(true) / 25.0f; - if (ac_bonus > skill_bonus) - ac_bonus = skill_bonus; - return static_cast(ac_bonus + skill_bonus); - } - case EQ::skills::SkillBackstab: { - float skill_bonus = static_cast(skill_level) * 0.02f; - auto inst = GetBotItem(EQ::invslot::slotPrimary); - if (inst && inst->GetItem() && inst->GetItem()->ItemType == EQ::item::ItemType1HPiercing) { - base = inst->GetItemBackstabDamage(true); - if (!inst->GetItemBackstabDamage()) - base += inst->GetItemWeaponDamage(true); - if (target) { - if (inst->GetItemElementalFlag(true) && inst->GetItemElementalDamage(true)) - base += target->ResistElementalWeaponDmg(inst); - if (inst->GetItemBaneDamageBody(true) || inst->GetItemBaneDamageRace(true)) - base += target->CheckBaneDamage(inst); + if (skill_level >= 125) + base++; + if (skill_level >= 175) + base++; + return base; + case EQ::skills::SkillFrenzy: + if (GetBotItem(EQ::invslot::slotPrimary)) { + if (GetLevel() > 15) + base += GetLevel() - 15; + if (base > 23) + base = 23; + if (GetLevel() > 50) + base += 2; + if (GetLevel() > 54) + base++; + if (GetLevel() > 59) + base++; } + return base; + case EQ::skills::SkillFlyingKick: { + float skill_bonus = skill_level / 9.0f; + float ac_bonus = 0.0f; + auto inst = GetBotItem(EQ::invslot::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); } - return static_cast(static_cast(base) * (skill_bonus + 2.0f)); - } - default: - return 0; + case EQ::skills::SkillKick: { + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + auto inst = GetBotItem(EQ::invslot::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQ::skills::SkillBash: { + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + const EQ::ItemInstance *inst = nullptr; + if (HasShieldEquipped()) + inst = GetBotItem(EQ::invslot::slotSecondary); + else if (HasTwoHanderEquipped()) + inst = GetBotItem(EQ::invslot::slotPrimary); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQ::skills::SkillBackstab: { + float skill_bonus = static_cast(skill_level) * 0.02f; + auto inst = GetBotItem(EQ::invslot::slotPrimary); + if (inst && inst->GetItem() && inst->GetItem()->ItemType == EQ::item::ItemType1HPiercing) { + base = inst->GetItemBackstabDamage(true); + if (!inst->GetItemBackstabDamage()) + base += inst->GetItemWeaponDamage(true); + if (target) { + if (inst->GetItemElementalFlag(true) && inst->GetItemElementalDamage(true)) + base += target->ResistElementalWeaponDmg(inst); + if (inst->GetItemBaneDamageBody(true) || inst->GetItemBaneDamageRace(true)) + base += target->CheckBaneDamage(inst); + } + } + return static_cast(static_cast(base) * (skill_bonus + 2.0f)); + } + default: + return 0; } } @@ -4892,7 +4914,10 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) { botpiercer = inst->GetItem(); if (!botpiercer || (botpiercer->ItemType != EQ::item::ItemType1HPiercing)) { - BotGroupSay(this, "I can't backstab with this weapon!"); + if (!GetCombatRoundForAlerts()) { + BotGroupSay(this, "I can't backstab with this weapon!"); + } + return; } @@ -5828,7 +5853,7 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe GetClass() != Class::Bard && (IsGrouped() || (IsRaidGrouped() && GetRaid()->GetGroup(GetCleanName()) != RAID_GROUPLESS)) && (spellTarget->IsBot() || spellTarget->IsClient()) && - (RuleB(Bots, GroupBuffing) || RuleB(Bots, CrossRaidBuffingAndHealing)) + (RuleB(Bots, GroupBuffing) || RuleB(Bots, RaidBuffing)) ) { bool noGroupSpell = false; uint16 thespell = spell_id; @@ -5857,7 +5882,8 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe if (!noGroupSpell) { std::vector v; - if (RuleB(Bots, CrossRaidBuffingAndHealing)) { + + if (RuleB(Bots, RaidBuffing)) { v = GatherSpellTargets(true); } else { @@ -5916,8 +5942,20 @@ bool Bot::DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, EQ::spel } if (spellTarget->IsOfClientBotMerc()) { - const std::vector v = GatherGroupSpellTargets(spellTarget); + std::vector v; + + if (RuleB(Bots, RaidBuffing)) { + v = GatherSpellTargets(true); + } + else { + v = GatherGroupSpellTargets(spellTarget); + } + for (Mob* m : v) { + if (m == this && spellTarget != this) { + continue; + } + SpellOnTarget(spell_id, m); if (m->GetPetID() && (!RuleB(Bots, RequirePetAffinity) || m->HasPetAffinity())) { @@ -6605,40 +6643,6 @@ void Bot::DoEnduranceUpkeep() { } void Bot::Camp(bool save_to_database) { - - if (RuleB(Bots, PreventBotCampOnFD) && GetBotOwner()->GetFeigned()) { - GetBotOwner()->Message(Chat::White, "You cannot camp bots while feigned."); - return; - } - - if (RuleB(Bots, PreventBotCampOnEngaged)) { - Raid* raid = entity_list.GetRaidByClient(GetBotOwner()->CastToClient()); - if (raid && raid->IsEngaged()) { - GetBotOwner()->Message(Chat::White, "You cannot spawn bots while your raid is engaged."); - return; - } - - auto* owner_group = GetBotOwner()->GetGroup(); - if (owner_group) { - std::list member_list; - owner_group->GetClientList(member_list); - member_list.remove(nullptr); - - for (auto member_iter : member_list) { - if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) { - GetBotOwner()->Message(Chat::White, "You cannot spawn bots while your group is engaged,"); - return; - } - } - } - else { - if (GetBotOwner()->CastToClient()->GetAggroCount() > 0) { - GetBotOwner()->Message(Chat::White, "You cannot spawn bots while you are engaged,"); - return; - } - } - } - Sit(); LeaveHealRotationMemberPool(); @@ -6710,107 +6714,107 @@ void Bot::UpdateGroupCastingRoles(const Group* group, bool disband) // GroupHealer switch (iter->GetClass()) { - case Class::Cleric: - if (!healer) - healer = iter; - else - switch (healer->GetClass()) { - case Class::Cleric: - break; - default: + case Class::Cleric: + if (!healer) healer = iter; - } + else + switch (healer->GetClass()) { + case Class::Cleric: + break; + default: + healer = iter; + } - break; - case Class::Druid: - if (!healer) - healer = iter; - else - switch (healer->GetClass()) { - case Class::Cleric: - case Class::Druid: - break; - default: + break; + case Class::Druid: + if (!healer) healer = iter; - } - break; - case Class::Shaman: - if (!healer) - healer = iter; - else - switch (healer->GetClass()) { - case Class::Cleric: - case Class::Druid: - case Class::Shaman: - break; - default: + else + switch (healer->GetClass()) { + case Class::Cleric: + case Class::Druid: + break; + default: + healer = iter; + } + break; + case Class::Shaman: + if (!healer) healer = iter; - } - break; - case Class::Paladin: - case Class::Ranger: - case Class::Beastlord: - if (!healer) - healer = iter; - break; - default: - break; - } + else + switch (healer->GetClass()) { + case Class::Cleric: + case Class::Druid: + case Class::Shaman: + break; + default: + healer = iter; + } + break; + case Class::Paladin: + case Class::Ranger: + case Class::Beastlord: + if (!healer) + healer = iter; + break; + default: + break; + } - // GroupSlower - switch (iter->GetClass()) { - case Class::Shaman: - if (!slower) - slower = iter; - else - switch (slower->GetClass()) { + // GroupSlower + switch (iter->GetClass()) { case Class::Shaman: + if (!slower) + slower = iter; + else + switch (slower->GetClass()) { + case Class::Shaman: + break; + default: + slower = iter; + } break; - default: - slower = iter; - } - break; - case Class::Enchanter: - if (!slower) - slower = iter; - else - switch (slower->GetClass()) { - case Class::Shaman: case Class::Enchanter: + if (!slower) + slower = iter; + else + switch (slower->GetClass()) { + case Class::Shaman: + case Class::Enchanter: + break; + default: + slower = iter; + } + break; + case Class::Beastlord: + if (!slower) + slower = iter; break; default: - slower = iter; - } - break; - case Class::Beastlord: - if (!slower) - slower = iter; - break; - default: - break; - } + break; + } - // GroupNuker - switch (iter->GetClass()) { - // wizard - // magician - // necromancer - // enchanter - // druid - // cleric - // shaman - // shadowknight - // paladin - // ranger - // beastlord - default: - break; - } + // GroupNuker + switch (iter->GetClass()) { + // wizard + // magician + // necromancer + // enchanter + // druid + // cleric + // shaman + // shadowknight + // paladin + // ranger + // beastlord + default: + break; + } - // GroupDoter - switch (iter->GetClass()) { - default: - break; + // GroupDoter + switch (iter->GetClass()) { + default: + break; } } @@ -6838,11 +6842,26 @@ Bot* Bot::GetBotByBotClientOwnerAndBotName(Client* c, const std::string& botName void Bot::ProcessBotGroupInvite(Client* c, std::string const& botName) { if (c && !c->HasRaid()) { - Bot* invitedBot = GetBotByBotClientOwnerAndBotName(c, botName); + Bot* invitedBot = entity_list.GetBotByBotName(botName); + if (!invitedBot) { return; } + if ( + invitedBot->GetBotOwnerCharacterID() != c->CharacterID() && + !( + c->GetGroup() && + !invitedBot->HasGroup() && + invitedBot->GetOwner()->HasGroup() && + c->GetGroup() == invitedBot->GetOwner()->GetGroup() + ) + ) { + c->Message(Chat::Red, "%s's owner needs to be in your group to be able to invite them.", invitedBot->GetCleanName()); + + return; + } + if (!invitedBot->HasGroup() && !invitedBot->HasRaid()) { if (!c->IsGrouped()) { auto g = new Group(c); @@ -7084,7 +7103,6 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl else { v = caster->GatherGroupSpellTargets(); } - v = caster->GatherSpellTargets(); } Mob* tar = nullptr; @@ -8443,17 +8461,19 @@ int32 Bot::CalcItemATKCap() return RuleI(Character, ItemATKCap) + itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap; } -bool Bot::CheckSpawnConditions(Client* c) { +bool Bot::CheckCampSpawnConditions(Client* c) { if (RuleB(Bots, PreventBotSpawnOnFD) && c->GetFeigned()) { - c->Message(Chat::White, "You cannot spawn bots while feigned."); + c->Message(Chat::White, "You cannot camp or spawn bots while feigned."); + return false; } if (RuleB(Bots, PreventBotSpawnOnEngaged)) { Raid* raid = entity_list.GetRaidByClient(c); if (raid && raid->IsEngaged()) { - c->Message(Chat::White, "You cannot spawn bots while your raid is engaged."); + c->Message(Chat::White, "You cannot camp or spawn bots while your raid is engaged."); + return false; } @@ -8465,14 +8485,14 @@ bool Bot::CheckSpawnConditions(Client* c) { for (auto member_iter : member_list) { if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) { - c->Message(Chat::White, "You cannot spawn bots while your group is engaged,"); + c->Message(Chat::White, "You cannot camp or spawn bots while your group is engaged,"); return false; } } } else { if (c->GetAggroCount() > 0) { - c->Message(Chat::White, "You cannot spawn bots while you are engaged,"); + c->Message(Chat::White, "You cannot camp or spawn bots while you are engaged,"); return false; } } @@ -9407,6 +9427,10 @@ bool Bot::CastChecks(uint16 spellid, Mob* tar, uint16 spellType, bool doPrecheck return false; } + if (IsBeneficialSpell(spellid) && tar->BuffCount() >= tar->GetCurrentBuffSlots() && CalcBuffDuration(this, tar, spellid) != 0) { + return false; + } + LogBotPreChecksDetail("{} says, 'Doing CanCastSpellType checks of {} on {}.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme if (!CanCastSpellType(spellType, spellid, tar)) { return false; @@ -9436,8 +9460,7 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { spells[spellid].target_type == ST_Pet || (tar == this && spells[spellid].target_type != ST_TargetsTarget) || spells[spellid].target_type == ST_Group || - spells[spellid].target_type == ST_GroupTeleport //|| - //(botClass == Class::Bard && spells[spellid].target_type == ST_AEBard) //TODO bot rewrite - is this needed? + spells[spellid].target_type == ST_GroupTeleport ) ) { LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to target_type checks.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme @@ -9481,9 +9504,9 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { tar->IsBot() && tar->GetLevel() > tar->CastToBot()->GetStopMeleeLevel() && ( IsEffectInSpell(spellid, SE_AttackSpeed) || IsEffectInSpell(spellid, SE_ReverseDS)) || - (SpellEffectsCount(spellid) == 1 && IsEffectInSpell(spellid, SE_ATK) || IsEffectInSpell(spellid, SE_STR) - ) - ) { + (SpellEffectsCount(spellid) == 1 && (IsEffectInSpell(spellid, SE_ATK) || IsEffectInSpell(spellid, SE_STR)) + ) + ) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Caster.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme return false; } @@ -9526,18 +9549,6 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { case BotSpellTypes::PreCombatBuffSong: case BotSpellTypes::InCombatBuffSong: case BotSpellTypes::OutOfCombatBuffSong: - //switch (spells[spellid].target_type) { //TODO bot rewrite - is this needed? - // case ST_AEBard: - // case ST_AECaster: - // case ST_GroupTeleport: - // case ST_Group: - // case ST_Self: - // break; - // default: - // LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to target_type checks.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme - // return false; - //} - if (!IsCommandedSpell() && IsTargetAlreadyReceivingSpell(tar, spellid)) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsTargetAlreadyReceivingSpell.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme return false; @@ -9550,9 +9561,9 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { tar->IsBot() && tar->GetLevel() > tar->CastToBot()->GetStopMeleeLevel() && ( IsEffectInSpell(spellid, SE_AttackSpeed) || IsEffectInSpell(spellid, SE_ReverseDS)) || - (SpellEffectsCount(spellid) == 1 && IsEffectInSpell(spellid, SE_ATK) || IsEffectInSpell(spellid, SE_STR) - ) - ) { + (SpellEffectsCount(spellid) == 1 && (IsEffectInSpell(spellid, SE_ATK) || IsEffectInSpell(spellid, SE_STR)) + ) + ) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Caster.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme return false; } @@ -9607,7 +9618,15 @@ bool Bot::IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spellid) { return true; } - const std::vector v = GatherSpellTargets(); + std::vector v; + + if (RuleB(Bots, CrossRaidBuffingAndHealing)) { + v = GatherSpellTargets(true); + } + else { + v = GatherGroupSpellTargets(); + } + for (Mob* m : v) { if ( m->IsBot() && @@ -9628,9 +9647,10 @@ bool Bot::IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spellid) { m->CastingSpellID() == spellid ) { - const std::vector v = GatherGroupSpellTargets(); - for (Mob* m : v) { - if (entity_list.GetMobID(m->CastToBot()->casting_spell_targetid) == entity_list.GetMobID(m->GetID())) { + std::vector x = GatherGroupSpellTargets(); + + for (Mob* t : x) { + if (entity_list.GetMobID(t->CastToBot()->casting_spell_targetid) == entity_list.GetMobID(t->GetID())) { return true; } } @@ -10274,60 +10294,56 @@ uint16 Bot::GetDefaultSpellTypeIdlePriority(uint16 spellType, uint8 botClass) { priority = 10; break; - case BotSpellTypes::InCombatBuff: // this has a check at the end to decrement everything below if it's not a SK (SK use InCombatBuffs as it is their hate line so it's not use when Idle) + case BotSpellTypes::PetVeryFastHeals: priority = 11; break; - case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetFastHeals: priority = 12; break; - case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetRegularHeals: priority = 13; break; - case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: priority = 14; break; - case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetHoTHeals: priority = 15; break; - case BotSpellTypes::PetHoTHeals: + case BotSpellTypes::Pet: priority = 16; break; - case BotSpellTypes::Pet: + case BotSpellTypes::Buff: priority = 17; break; - case BotSpellTypes::Buff: + case BotSpellTypes::OutOfCombatBuffSong: priority = 18; break; - case BotSpellTypes::OutOfCombatBuffSong: + case BotSpellTypes::ResistBuffs: priority = 19; break; - case BotSpellTypes::ResistBuffs: + case BotSpellTypes::DamageShields: priority = 20; break; - case BotSpellTypes::DamageShields: + case BotSpellTypes::PetBuffs: priority = 21; break; - case BotSpellTypes::PetBuffs: + case BotSpellTypes::PreCombatBuff: priority = 22; - break; - case BotSpellTypes::PreCombatBuff: - priority = 23; - break; case BotSpellTypes::PreCombatBuffSong: - priority = 24; + priority = 23; break; default: @@ -10336,14 +10352,6 @@ uint16 Bot::GetDefaultSpellTypeIdlePriority(uint16 spellType, uint8 botClass) { break; } - if ( - priority >= 11 && - botClass && botClass == Class::ShadowKnight && - spellType != BotSpellTypes::InCombatBuff - ) { - --priority; - } - return priority; } @@ -10849,39 +10857,19 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spellType, uint16 spellid) { switch (spellType) { //TODO bot rewrite - fix Buff/ResistBuff case BotSpellTypes::Buff: - if (IsEffectInSpell(spellid, SE_DamageShield)) { - return false; - } - - if ( - IsEffectInSpell(spellid, SE_ResistMagic) || - IsEffectInSpell(spellid, SE_ResistFire) || - IsEffectInSpell(spellid, SE_ResistCold) || - IsEffectInSpell(spellid, SE_ResistPoison) || - IsEffectInSpell(spellid, SE_ResistDisease) || - IsEffectInSpell(spellid, SE_ResistCorruption) || - IsEffectInSpell(spellid, SE_ResistAll) - ) { + if (IsResistanceOnlySpell(spellid) || IsDamageShieldOnlySpell(spellid) || IsDamageShieldAndResistanceSpellOnly(spellid)) { return false; } return true; case BotSpellTypes::ResistBuffs: - if ( - IsEffectInSpell(spellid, SE_ResistMagic) || - IsEffectInSpell(spellid, SE_ResistFire) || - IsEffectInSpell(spellid, SE_ResistCold) || - IsEffectInSpell(spellid, SE_ResistPoison) || - IsEffectInSpell(spellid, SE_ResistDisease) || - IsEffectInSpell(spellid, SE_ResistCorruption) || - IsEffectInSpell(spellid, SE_ResistAll) - ) { + if (IsResistanceOnlySpell(spellid)) { return true; } return false; case BotSpellTypes::DamageShields: - if (IsEffectInSpell(spellid, SE_DamageShield)) { + if (IsDamageShieldOnlySpell(spellid) || IsDamageShieldAndResistanceSpellOnly(spellid)) { return true; } @@ -11096,55 +11084,6 @@ bool Bot::HasRequiredLoSForPositioning(Mob* tar) { return true; } -bool Bot::IsInGroupOrRaid(bool announce) { - if (!GetOwner()) { - return false; - } - - Mob* c = GetOwner(); - - if ( - (!GetRaid() && !GetGroup()) || - (!c->GetRaid() && !c->GetGroup()) - ) { - return false; - } - - if ( - c->GetRaid() && - ( - !GetRaid() || - c->GetRaid() != GetRaid() || - GetRaid()->GetGroup(GetCleanName()) == RAID_GROUPLESS - ) - ) { - return false; - } - - if ( - c->GetGroup() && - ( - !GetGroup() || - c->GetGroup() != GetGroup() - ) - ) { - return false; - } - - - if (announce) { - c->Message( - Chat::Yellow, - fmt::format( - "{} says, 'I am not currently in your group or raid.", - GetCleanName() - ).c_str() - ); - } - - return true; -} - bool Bot::HasValidAETarget(Bot* botCaster, uint16 spellid, uint16 spellType, Mob* tar) { int spellRange = botCaster->GetActSpellRange(spellid, spells[spellid].range); int spellAERange = botCaster->GetActSpellRange(spellid, spells[spellid].aoe_range); diff --git a/zone/bot.h b/zone/bot.h index 97d987e56..5836af133 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -278,6 +278,7 @@ public: void SetHoldFlag(bool flag = true) { m_hold_flag = flag; } bool GetAttackFlag() const { return m_attack_flag; } void SetAttackFlag(bool flag = true) { m_attack_flag = flag; } + bool GetCombatRoundForAlerts() const { return m_combat_round_alert_flag; } bool GetAttackingFlag() const { return m_attacking_flag; } bool GetPullFlag() const { return m_pull_flag; } void SetPullFlag(bool flag = true) { m_pull_flag = flag; } @@ -403,7 +404,7 @@ public: void SetGuardMode(); void SetHoldMode(); - bool IsValidSpellRange(uint16 spell_id, Mob const* tar); + bool IsValidSpellRange(uint16 spell_id, Mob* tar); // Bot AI Methods void AI_Bot_Init(); @@ -519,7 +520,6 @@ public: void SetHasLoS(bool hasLoS) { _hasLoS = hasLoS; } bool HasLoS() const { return _hasLoS; } - bool IsInGroupOrRaid(bool announce = false); void SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, std::string arg2, bool helpPrompt = false); std::list GetSpellTypesPrioritized(uint8 priorityType); @@ -981,7 +981,7 @@ public: bool CheckDoubleRangedAttack(); // Public "Refactor" Methods - static bool CheckSpawnConditions(Client* c); + static bool CheckCampSpawnConditions(Client* c); inline bool CommandedDoSpellCast(int32 i, Mob* tar, int32 mana_cost) { return AIDoSpellCast(i, tar, mana_cost); } @@ -1047,6 +1047,7 @@ private: bool m_guard_flag; bool m_hold_flag; bool m_attack_flag; + bool m_combat_round_alert_flag; bool m_attacking_flag; bool m_pull_flag; bool m_pulling_flag; @@ -1104,6 +1105,7 @@ private: int32 GenerateBaseManaPoints(); void GenerateSpecialAttacks(); void SetBotID(uint32 botID); + void SetCombatRoundForAlerts(bool flag = true) { m_combat_round_alert_flag; } void SetAttackingFlag(bool flag = true) { m_attacking_flag = flag; } void SetPullingFlag(bool flag = true) { m_pulling_flag = flag; } void SetReturningFlag(bool flag = true) { m_returning_flag = flag; } diff --git a/zone/bot_commands/bot.cpp b/zone/bot_commands/bot.cpp index 7da91504d..ccf1e51ff 100644 --- a/zone/bot_commands/bot.cpp +++ b/zone/bot_commands/bot.cpp @@ -30,12 +30,19 @@ void bot_command_bot(Client *c, const Seperator *sep) void bot_command_camp(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_command_camp", sep->arg[0], "botcamp")) + if (helper_command_alias_fail(c, "bot_command_camp", sep->arg[0], "botcamp")) { return; + } + if (helper_is_help_or_usage(sep->arg[1])) { c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } + + if (!Bot::CheckCampSpawnConditions(c)) { + return; + } + const int ab_mask = ActionableBots::ABM_Type1; std::string class_race_arg = sep->arg[1]; @@ -49,8 +56,14 @@ void bot_command_camp(Client *c, const Seperator *sep) return; } - for (auto bot_iter : sbl) + uint16 campCount; + + for (auto bot_iter : sbl) { bot_iter->Camp(); + ++campCount; + } + + c->Message(Chat::White, "%i of your bots have been camped.", campCount); } void bot_command_clone(Client *c, const Seperator *sep) @@ -428,6 +441,10 @@ void bot_command_delete(Client *c, const Seperator *sep) return; } + if (!Bot::CheckCampSpawnConditions(c)) { + return; + } + auto my_bot = ActionableBots::AsTarget_ByBot(c); if (!my_bot) { c->Message(Chat::White, "You must a bot that you own to use this command"); @@ -829,7 +846,7 @@ void bot_command_spawn(Client *c, const Seperator *sep) return; } - if (!Bot::CheckSpawnConditions(c)) { + if (!Bot::CheckCampSpawnConditions(c)) { return; } diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index 77d3ab23b..70b6e659c 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -174,7 +174,7 @@ void bot_command_cast(Client* c, const Seperator* sep) if (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) { c->Message(Chat::Yellow, "[%s] is an invalid target.", tar->GetCleanName()); return; - } + } } } LogTestDebug("{}: Attempting {} on {}", __LINE__, c->GetSpellTypeNameByID(spellType), (tar ? tar->GetCleanName() : "NOBODY")); //deleteme @@ -223,7 +223,7 @@ void bot_command_cast(Client* c, const Seperator* sep) Bot* firstFound = nullptr; for (auto bot_iter : sbl) { - if (!bot_iter->IsInGroupOrRaid()) { + if (!bot_iter->IsInGroupOrRaid(c)) { continue; } @@ -248,6 +248,14 @@ void bot_command_cast(Client* c, const Seperator* sep) continue; } + if ( + BOT_SPELL_TYPES_BENEFICIAL(spellType) && + !RuleB(Bots, CrossRaidBuffingAndHealing) && + !bot_iter->IsInGroupOrRaid(newTar, true) + ) { + continue; + } + if (BOT_SPELL_TYPES_DETRIMENTAL(spellType, bot_iter->GetClass()) && !bot_iter->IsAttackAllowed(newTar)) { bot_iter->BotGroupSay( bot_iter, diff --git a/zone/bot_commands/follow.cpp b/zone/bot_commands/follow.cpp index 3ff0cb44b..9e7c33eaf 100644 --- a/zone/bot_commands/follow.cpp +++ b/zone/bot_commands/follow.cpp @@ -2,47 +2,112 @@ void bot_command_follow(Client* c, const Seperator* sep) { - if (helper_command_alias_fail(c, "bot_command_follow", sep->arg[0], "follow")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s ([option: reset]) [actionable: byname | ownergroup | ownerraid | namesgroup | mmr | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); - c->Message(Chat::White, "usage: %s chain", sep->arg[0]); + if (helper_command_alias_fail(c, "bot_command_follow", sep->arg[0], "follow")) { return; } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Sets bots of your choosing to follow your target, view their current following state or reset their following state." + }; + + std::vector notes = + { + "- You can only follow players, bots or mercenaries belonging to your group or raid." + }; + + std::vector example_format = + { + fmt::format( + "{} [optional] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all Clerics to follow your target:", + fmt::format( + "{} byclass {}", + sep->arg[0], + Class::Cleric + ) + }; + std::vector examples_two = + { + "To check the current state of all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + std::vector examples_three = + { + "To reset all bots:", + fmt::format( + "{} reset spawned", + sep->arg[0] + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; + } + const int ab_mask = ActionableBots::ABM_Type2; + bool chain = false; bool reset = false; + bool currentCheck = false; int ab_arg = 1; - int name_arg = 2; Mob* target_mob = nullptr; std::string optional_arg = sep->arg[1]; - if (!optional_arg.compare("chain")) { - - auto chain_count = helper_bot_follow_option_chain(c); - c->Message(Chat::White, "%i of your bots %s now chain following you", chain_count, (chain_count == 1 ? "is" : "are")); - - return; - } - else if (!optional_arg.compare("reset")) { + + if (!optional_arg.compare("reset")) { + target_mob = c; reset = true; ++ab_arg; - ++name_arg ; + } + else if (!optional_arg.compare("current")) { + currentCheck = true; + ++ab_arg; } else { - //target_mob = ActionableTarget::VerifyFriendly(c, BCEnum::TT_Single); target_mob = c->GetTarget(); - if (!target_mob) { - c->Message(Chat::White, "You must a friendly player or bot within your group or raid to use this command"); + + if (!target_mob || !target_mob->IsOfClientBotMerc() || !c->IsInGroupOrRaid(target_mob)) { + c->Message(Chat::Yellow, "You must a friendly player, bot or merc within your group or raid to use this command"); return; } - else if (!target_mob->IsBot() && !target_mob->IsClient()) { - c->Message(Chat::White, "You must a friendly player or bot within your group or raid to use this command"); - return; - } - else if ((target_mob->GetGroup() && target_mob->GetGroup() != c->GetGroup()) || (target_mob->GetRaid() && target_mob->GetRaid() != c->GetRaid())) { - c->Message(Chat::White, "You must a friendly player or bot within your group or raid to use this command"); - return; + + if (!optional_arg.compare("chain")) { + chain = true; + ++ab_arg; } } @@ -53,12 +118,66 @@ void bot_command_follow(Client* c, const Seperator* sep) } std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[name_arg] : nullptr, class_race_check ? atoi(sep->arg[name_arg]) : 0) == ActionableBots::ABT_None) { + //if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg] : nullptr, class_race_check ? atoi(sep->arg[ab_arg]) : 0) == ActionableBots::ABT_None) { + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } sbl.remove(nullptr); + + auto botCount = sbl.size(); + Mob* follow_mob = nullptr; + std::list chainList; + std::list::const_iterator it = chainList.begin(); + uint16 count = 0; for (auto bot_iter : sbl) { + if (currentCheck) { + follow_mob = entity_list.GetMob(bot_iter->GetFollowID()); + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I am currently following {}.'", + bot_iter->GetCleanName(), + follow_mob ? follow_mob->GetCleanName() : "no one" + ).c_str() + ); + + if (!follow_mob && RuleB(Bots, DoResponseAnimations)) { + bot_iter->DoAnim(28); + } + + continue; + } + + if (bot_iter == target_mob) { + if (botCount == 1) { + c->Message( + Chat::Yellow, + fmt::format( + "{} says, 'I cannot follow myself, you want me to run circles?", + bot_iter->GetCleanName() + ).c_str() + ); + + if (RuleB(Bots, DoResponseAnimations)) { + bot_iter->DoAnim(60); + } + + return; + } + + bot_iter->WipeHateList(); + --botCount; + + continue; + } + + if (!bot_iter->IsInGroupOrRaid(target_mob)) { + --botCount; + + continue; + } + bot_iter->WipeHateList(); if (!bot_iter->GetGroup() && !bot_iter->GetRaid()) { @@ -71,57 +190,52 @@ void bot_command_follow(Client* c, const Seperator* sep) bot_iter->SetManualFollow(false); } else { - if (target_mob->IsGrouped() || target_mob->IsRaidGrouped()) { - bot_iter->SetFollowID(target_mob->GetID()); - bot_iter->SetManualFollow(true); - } - else if (bot_iter == target_mob) { - bot_iter->SetFollowID(c->GetID()); - bot_iter->SetManualFollow(true); + if (chain) { + Mob* nextTar = target_mob; + + if (count > 0) { + nextTar = *it; + + if (!nextTar) { + nextTar = target_mob; + } + } + LogTestDebug("{} is now following {}.", bot_iter->GetCleanName(), nextTar->GetCleanName()); //deleteme + chainList.push_back(bot_iter); + ++it; + ++count; + bot_iter->SetFollowID(nextTar->GetID()); } else { - bot_iter->SetFollowID(0); - bot_iter->SetManualFollow(false); + bot_iter->SetFollowID(target_mob->GetID()); } + + bot_iter->SetManualFollow(true); } } - //auto my_group = bot_iter->GetGroup(); - //if (my_group) { - // if (reset) { - // if (!my_group->GetLeader() || my_group->GetLeader() == bot_iter) - // bot_iter->SetFollowID(c->GetID()); - // else - // bot_iter->SetFollowID(my_group->GetLeader()->GetID()); - // - // bot_iter->SetManualFollow(false); - // } - // else { - // if (bot_iter == target_mob) - // bot_iter->SetFollowID(c->GetID()); - // else - // bot_iter->SetFollowID(target_mob->GetID()); - // - // bot_iter->SetManualFollow(true); - // } - //} - //else { - // bot_iter->SetFollowID(0); - // bot_iter->SetManualFollow(false); - //} - if (!bot_iter->GetPet()) + + if (!bot_iter->GetPet()) { continue; + } bot_iter->GetPet()->WipeHateList(); bot_iter->GetPet()->SetFollowID(bot_iter->GetID()); } - Mob* follow_mob = nullptr; - if (sbl.size() == 1) { + if (currentCheck || !botCount) { + return; + } + + follow_mob = target_mob; + + if (botCount == 1) { follow_mob = entity_list.GetMob(sbl.front()->GetFollowID()); + c->Message( - Chat::White, + Chat::Green, fmt::format( - "Following {}.", + "{} says, 'Following {}.'", + sbl.front()->GetCleanName(), follow_mob ? follow_mob->GetCleanName() : "you" ).c_str() ); @@ -129,22 +243,20 @@ void bot_command_follow(Client* c, const Seperator* sep) else { if (reset) { c->Message( - Chat::White, + Chat::Green, fmt::format( "{} of your bots are following you.", - sbl.size() + botCount ).c_str() ); } else { - if (!sbl.empty()) { - follow_mob = entity_list.GetMob(sbl.front()->GetFollowID()); - } c->Message( - Chat::White, + Chat::Green, fmt::format( - "{} of your bots are following {}.", - sbl.size(), + "{} of your bots are {} {}.", + botCount, + chain ? "chain following" : "following", follow_mob ? follow_mob->GetCleanName() : "you" ).c_str() ); diff --git a/zone/bot_commands/item_use.cpp b/zone/bot_commands/item_use.cpp index 1640ff0e3..975d30b90 100644 --- a/zone/bot_commands/item_use.cpp +++ b/zone/bot_commands/item_use.cpp @@ -179,7 +179,9 @@ void bot_command_item_use(Client* c, const Seperator* sep) ).c_str() ); - bot_iter->DoAnim(29); + if (RuleB(Bots, DoResponseAnimations)) { + bot_iter->DoAnim(29); + } } else if (!equipped_item) { c->Message( @@ -204,7 +206,9 @@ void bot_command_item_use(Client* c, const Seperator* sep) ).c_str() ); - bot_iter->DoAnim(29); + if (RuleB(Bots, DoResponseAnimations)) { + bot_iter->DoAnim(29); + } } } } diff --git a/zone/bot_commands/mesmerize.cpp b/zone/bot_commands/mesmerize.cpp index 1f51a70d3..d86fba9f7 100644 --- a/zone/bot_commands/mesmerize.cpp +++ b/zone/bot_commands/mesmerize.cpp @@ -20,7 +20,7 @@ void bot_command_mesmerize(Client *c, const Seperator *sep) continue; } - if (!bot_iter->IsInGroupOrRaid()) { + if (!bot_iter->IsInGroupOrRaid(c)) { continue; } diff --git a/zone/bot_raid.cpp b/zone/bot_raid.cpp index 25d98ef86..458ae03c4 100644 --- a/zone/bot_raid.cpp +++ b/zone/bot_raid.cpp @@ -176,13 +176,7 @@ void Bot::ProcessRaidInvite(Mob* invitee, Client* invitor, bool group_invite) { // If the Bot Owner is in our raid we need to be able to invite their Bots } else if (invitee->IsBot() && (invitee->CastToBot()->GetBotOwnerCharacterID() != invitor->CharacterID())) { - invitor->Message( - Chat::Red, - fmt::format( - "{} is not your Bot. You can only invite your own Bots, or Bots that belong to a Raid member.", - invitee->GetCleanName() - ).c_str() - ); + invitor->Message(Chat::Red, "%s's owner needs to be in your raid to be able to invite them.", invitee->GetCleanName()); return; } @@ -257,10 +251,6 @@ void Bot::CreateBotRaid(Mob* invitee, Client* invitor, bool group_invite, Raid* } else { raid->AddBot(b); } - - if (new_raid) { - invitee->SetFollowID(invitor->GetID()); - } } } diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index ee3aaf3a3..d7e633822 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -994,11 +994,28 @@ std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa continue; } + if ( + !RuleB(Bots, EnableBotTGB) && + IsGroupSpell(botSpellList[i].spellid) && + !IsTGBCompatibleSpell(botSpellList[i].spellid) && + !botCaster->IsInGroupOrRaid(tar, true) + ) { + continue; + } + if ( ( - !botCaster->IsCommandedSpell() || (botCaster->IsCommandedSpell() && (spellType != BotSpellTypes::Mez && spellType != BotSpellTypes::AEMez)) + !botCaster->IsCommandedSpell() || + ( + botCaster->IsCommandedSpell() && + (spellType != BotSpellTypes::Mez && spellType != BotSpellTypes::AEMez) + ) + ) + && + ( + !IsPBAESpell(botSpellList[i].spellid) && + !botCaster->CastChecks(botSpellList[i].spellid, tar, spellType, false, IsAEBotSpellType(spellType)) ) - && (!IsPBAESpell(botSpellList[i].spellid) && !botCaster->CastChecks(botSpellList[i].spellid, tar, spellType, false, IsGroupBotSpellType(spellType))) // TODO bot rewrite - needed for ae spells? ) { continue; } @@ -1232,7 +1249,15 @@ BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* botCaster, Mob* tar, uint16 spell if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_CurrentHP); - const std::vector v = botCaster->GatherSpellTargets(); + std::vector v; + + if (RuleB(Bots, CrossRaidBuffingAndHealing)) { + v = botCaster->GatherSpellTargets(true); + } + else { + v = botCaster->GatherGroupSpellTargets(); + } + int targetCount = 0; for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { @@ -1273,7 +1298,15 @@ BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* botCaster, Mob* tar, uint if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_HealOverTime); - const std::vector v = botCaster->GatherSpellTargets(); + std::vector v; + + if (RuleB(Bots, CrossRaidBuffingAndHealing)) { + v = botCaster->GatherSpellTargets(true); + } + else { + v = botCaster->GatherGroupSpellTargets(); + } + int targetCount = 0; for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { @@ -1314,7 +1347,15 @@ BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* botCaster, Mob* tar, uint if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, spellType, SE_CompleteHeal); - const std::vector v = botCaster->GatherSpellTargets(); + std::vector v; + + if (RuleB(Bots, CrossRaidBuffingAndHealing)) { + v = botCaster->GatherSpellTargets(true); + } + else { + v = botCaster->GatherGroupSpellTargets(); + } + int targetCount = 0; for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { @@ -1629,7 +1670,7 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* botCaster, SpellTargetType continue; } - if (!IsPBAESpell(botSpellListItr->SpellId) && !botCaster->CastChecks(botSpellListItr->SpellId, tar, spellType, false, IsGroupBotSpellType(spellType))) { + if (!IsPBAESpell(botSpellListItr->SpellId) && !botCaster->CastChecks(botSpellListItr->SpellId, tar, spellType, false, IsAEBotSpellType(spellType))) { continue; } @@ -1679,7 +1720,7 @@ BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType continue; } - if (!IsPBAESpell(botSpellListItr->SpellId) && !botCaster->CastChecks(botSpellListItr->SpellId, tar, spellType, false, IsGroupBotSpellType(spellType))) { + if (!IsPBAESpell(botSpellListItr->SpellId) && !botCaster->CastChecks(botSpellListItr->SpellId, tar, spellType, false, IsAEBotSpellType(spellType))) { continue; } @@ -2583,19 +2624,27 @@ bool Bot::HasBotSpellEntry(uint16 spellid) { return false; } -bool Bot::IsValidSpellRange(uint16 spell_id, Mob const* tar) { +bool Bot::IsValidSpellRange(uint16 spell_id, Mob* tar) { if (!IsValidSpell(spell_id)) { return false; } if (tar) { - int spellrange = (GetActSpellRange(spell_id, spells[spell_id].range) * GetActSpellRange(spell_id, spells[spell_id].range)); - if (spellrange >= DistanceSquared(m_Position, tar->GetPosition())) { + float range = spells[spell_id].range; + + if (tar != this) { + range += GetRangeDistTargetSizeMod(tar); + } + + range = GetActSpellRange(spell_id, range); + + if (range >= Distance(m_Position, tar->GetPosition())) { return true; } - spellrange = (GetActSpellRange(spell_id, spells[spell_id].aoe_range) * GetActSpellRange(spell_id, spells[spell_id].aoe_range)); - if (spellrange >= DistanceSquared(m_Position, tar->GetPosition())) { + range = GetActSpellRange(spell_id, spells[spell_id].aoe_range); + + if (range >= Distance(m_Position, tar->GetPosition())) { return true; } } diff --git a/zone/groups.cpp b/zone/groups.cpp index ad6972e2a..e157b0063 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -1017,6 +1017,27 @@ void Group::GetBotList(std::list& bot_list, bool clear_list) } } +void Group::GetRawBotList(std::list& bot_list, bool clear_list) +{ + if (clear_list) { + bot_list.clear(); + } + + const auto& l = GroupIdRepository::GetWhere( + database, + fmt::format( + "`group_id` = {}", + GetID() + ) + ); + + for (const auto& e : l) { + if (e.bot_id) { + bot_list.push_back(e.bot_id); + } + } +} + bool Group::Process() { if(disbandcheck && !GroupCount()) return false; @@ -1234,30 +1255,49 @@ void Group::GroupMessageString(Mob* sender, uint32 type, uint32 string_id, const void Client::LeaveGroup() { Group *g = GetGroup(); - if(g) - { + if (g) { int32 MemberCount = g->GroupCount(); // Account for both client and merc leaving the group - if (GetMerc() && g == GetMerc()->GetGroup()) - { + if (GetMerc() && g == GetMerc()->GetGroup()) { MemberCount -= 1; } - if(MemberCount < 3) - { + if (RuleB(Bots, Enabled)) { + std::list sbl; + g->GetRawBotList(sbl); + + for (auto botID : sbl) { + auto b = entity_list.GetBotByBotID(botID); + + if (b) { + if (b->GetBotOwnerCharacterID() == CharacterID()) { + MemberCount -= 1; + } + } + else { + if (database.botdb.GetOwnerID(botID) == CharacterID()) { + MemberCount -= 1; + } + } + } + } + + if (MemberCount < 3) { g->DisbandGroup(); } - else - { + else { g->DelMember(this); - if (GetMerc() != nullptr && g == GetMerc()->GetGroup() ) - { + + if (GetMerc() != nullptr && g == GetMerc()->GetGroup()) { GetMerc()->RemoveMercFromGroup(GetMerc(), GetMerc()->GetGroup()); } + + if (RuleB(Bots, Enabled)) { + g->RemoveClientsBots(this); + } } } - else - { + else { //force things a little Group::RemoveFromGroup(this); @@ -2576,3 +2616,77 @@ void Group::AddToGroup(AddToGroupRequest r) } ); } + +void Group::RemoveClientsBots(Client* c) { + std::list sbl; + GetRawBotList(sbl); + + for (auto botID : sbl) { + auto b = entity_list.GetBotByBotID(botID); + + if (b) { + if (b->GetBotOwnerCharacterID() == c->CharacterID()) { + b->RemoveBotFromGroup(b, this); + } + } + else { + if (database.botdb.GetOwnerID(botID) == c->CharacterID()) { + auto botName = database.botdb.GetBotNameByID(botID); + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (membername[i] == botName) { + members[i] = nullptr; + membername[i][0] = '\0'; + memset(membername[i], 0, 64); + MemberRoles[i] = 0; + break; + } + } + + auto pack = new ServerPacket(ServerOP_GroupLeave, sizeof(ServerGroupLeave_Struct)); + ServerGroupLeave_Struct* gl = (ServerGroupLeave_Struct*)pack->pBuffer; + gl->gid = GetID(); + gl->zoneid = zone->GetZoneID(); + gl->instance_id = zone->GetInstanceID(); + strcpy(gl->member_name, botName.c_str()); + worldserver.SendPacket(pack); + safe_delete(pack); + + auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct)); + GroupJoin_Struct* gu = (GroupJoin_Struct*)outapp->pBuffer; + gu->action = groupActLeave; + strcpy(gu->membername, botName.c_str()); + strcpy(gu->yourname, botName.c_str()); + + gu->leader_aas = LeaderAbilities; + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (members[i] == nullptr) { + //if (DEBUG>=5) LogFile->write(EQEMuLog::Debug, "Group::DelMember() null member at slot %i", i); + continue; + } + + if (membername[i] != botName.c_str()) { + strcpy(gu->yourname, members[i]->GetCleanName()); + + if (members[i]->IsClient()) { + members[i]->CastToClient()->QueuePacket(outapp); + } + } + } + + safe_delete(outapp); + + DelMemberOOZ(botName.c_str()); + + GroupIdRepository::DeleteWhere( + database, + fmt::format( + "`bot_id` = {}", + botID + ) + ); + } + } + } +} diff --git a/zone/groups.h b/zone/groups.h index 28954ee75..405478665 100644 --- a/zone/groups.h +++ b/zone/groups.h @@ -70,6 +70,7 @@ public: void GetMemberList(std::list& member_list, bool clear_list = true); void GetClientList(std::list& client_list, bool clear_list = true); void GetBotList(std::list& bot_list, bool clear_list = true); + void GetRawBotList(std::list& bot_list, bool clear_list = true); bool IsGroupMember(Mob* c); bool IsGroupMember(const char* name); bool Process(); @@ -155,6 +156,7 @@ public: void AddToGroup(AddToGroupRequest r); void AddToGroup(Mob* m); static void RemoveFromGroup(Mob* m); + void RemoveClientsBots(Client* c); void SetGroupMentor(int percent, char *name); void ClearGroupMentor(); diff --git a/zone/mob.cpp b/zone/mob.cpp index 7182bbfb4..fc255188d 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -6119,18 +6119,17 @@ int32 Mob::GetVulnerability(Mob *caster, uint32 spell_id, uint32 ticsremaining, bool Mob::IsTargetedFocusEffect(int focus_type) { switch (focus_type) { - case focusSpellVulnerability: - case focusFcSpellDamagePctIncomingPC: - case focusFcDamageAmtIncoming: - case focusFcSpellDamageAmtIncomingPC: - case focusFcCastSpellOnLand: - case focusFcHealAmtIncoming: - case focusFcHealPctCritIncoming: - case focusFcHealPctIncoming: - return true; - default: - return false; - + case focusSpellVulnerability: + case focusFcSpellDamagePctIncomingPC: + case focusFcDamageAmtIncoming: + case focusFcSpellDamageAmtIncomingPC: + case focusFcCastSpellOnLand: + case focusFcHealAmtIncoming: + case focusFcHealPctCritIncoming: + case focusFcHealPctIncoming: + return true; + default: + return false; } } @@ -7261,56 +7260,56 @@ void Mob::SlowMitigation(Mob* caster) EQ::skills::SkillType Mob::GetSkillByItemType(int ItemType) { switch (ItemType) { - case EQ::item::ItemType1HSlash: - return EQ::skills::Skill1HSlashing; - case EQ::item::ItemType2HSlash: - return EQ::skills::Skill2HSlashing; - case EQ::item::ItemType1HPiercing: - return EQ::skills::Skill1HPiercing; - case EQ::item::ItemType1HBlunt: - return EQ::skills::Skill1HBlunt; - case EQ::item::ItemType2HBlunt: - return EQ::skills::Skill2HBlunt; - case EQ::item::ItemType2HPiercing: - if (IsClient() && CastToClient()->ClientVersion() < EQ::versions::ClientVersion::RoF2) + case EQ::item::ItemType1HSlash: + return EQ::skills::Skill1HSlashing; + case EQ::item::ItemType2HSlash: + return EQ::skills::Skill2HSlashing; + case EQ::item::ItemType1HPiercing: return EQ::skills::Skill1HPiercing; - else - return EQ::skills::Skill2HPiercing; - case EQ::item::ItemTypeBow: - return EQ::skills::SkillArchery; - case EQ::item::ItemTypeLargeThrowing: - case EQ::item::ItemTypeSmallThrowing: - return EQ::skills::SkillThrowing; - case EQ::item::ItemTypeMartial: - return EQ::skills::SkillHandtoHand; - default: - return EQ::skills::SkillHandtoHand; + case EQ::item::ItemType1HBlunt: + return EQ::skills::Skill1HBlunt; + case EQ::item::ItemType2HBlunt: + return EQ::skills::Skill2HBlunt; + case EQ::item::ItemType2HPiercing: + if (IsClient() && CastToClient()->ClientVersion() < EQ::versions::ClientVersion::RoF2) + return EQ::skills::Skill1HPiercing; + else + return EQ::skills::Skill2HPiercing; + case EQ::item::ItemTypeBow: + return EQ::skills::SkillArchery; + case EQ::item::ItemTypeLargeThrowing: + case EQ::item::ItemTypeSmallThrowing: + return EQ::skills::SkillThrowing; + case EQ::item::ItemTypeMartial: + return EQ::skills::SkillHandtoHand; + default: + return EQ::skills::SkillHandtoHand; } } uint8 Mob::GetItemTypeBySkill(EQ::skills::SkillType skill) { switch (skill) { - case EQ::skills::SkillThrowing: - return EQ::item::ItemTypeSmallThrowing; - case EQ::skills::SkillArchery: - return EQ::item::ItemTypeArrow; - case EQ::skills::Skill1HSlashing: - return EQ::item::ItemType1HSlash; - case EQ::skills::Skill2HSlashing: - return EQ::item::ItemType2HSlash; - case EQ::skills::Skill1HPiercing: - return EQ::item::ItemType1HPiercing; - case EQ::skills::Skill2HPiercing: // watch for undesired client behavior - return EQ::item::ItemType2HPiercing; - case EQ::skills::Skill1HBlunt: - return EQ::item::ItemType1HBlunt; - case EQ::skills::Skill2HBlunt: - return EQ::item::ItemType2HBlunt; - case EQ::skills::SkillHandtoHand: - return EQ::item::ItemTypeMartial; - default: - return EQ::item::ItemTypeMartial; + case EQ::skills::SkillThrowing: + return EQ::item::ItemTypeSmallThrowing; + case EQ::skills::SkillArchery: + return EQ::item::ItemTypeArrow; + case EQ::skills::Skill1HSlashing: + return EQ::item::ItemType1HSlash; + case EQ::skills::Skill2HSlashing: + return EQ::item::ItemType2HSlash; + case EQ::skills::Skill1HPiercing: + return EQ::item::ItemType1HPiercing; + case EQ::skills::Skill2HPiercing: // watch for undesired client behavior + return EQ::item::ItemType2HPiercing; + case EQ::skills::Skill1HBlunt: + return EQ::item::ItemType1HBlunt; + case EQ::skills::Skill2HBlunt: + return EQ::item::ItemType2HBlunt; + case EQ::skills::SkillHandtoHand: + return EQ::item::ItemTypeMartial; + default: + return EQ::item::ItemTypeMartial; } } @@ -7318,7 +7317,6 @@ uint16 Mob::GetWeaponSpeedbyHand(uint16 hand) { uint16 weapon_speed = 0; switch (hand) { - case 13: weapon_speed = attack_timer.GetDuration(); break; @@ -9433,14 +9431,52 @@ void Mob::SetBaseSetting(uint16 baseSetting, int settingValue) { } bool Mob::TargetValidation(Mob* other) { - if (!other || GetAppearance() == eaDead) { - return false; - } + if (!other || GetAppearance() == eaDead) { + return false; + } - return true; + return true; } -std::unordered_map &Mob::GetCloseMobList(float distance) +std::unordered_map& Mob::GetCloseMobList(float distance) { return entity_list.GetCloseMobList(this, distance); } + +bool Mob::IsInGroupOrRaid(Mob *other, bool sameRaidGroup) { + if (!other || !IsOfClientBotMerc() || !other->IsOfClientBotMerc()) { + return false; + } + + auto* r = GetRaid(); + auto* rO = other->GetRaid(); + + if (r) { + if (!rO || r != rO) { + return false; + } + + auto rG = r->GetGroup(GetCleanName()); + auto rOG = rO->GetGroup(other->GetCleanName()); + + if (rG == RAID_GROUPLESS || rOG == RAID_GROUPLESS || (sameRaidGroup && rG != rOG)) { + return false; + } + + return true; + } + else { + auto* g = GetGroup(); + auto* gO = other->GetGroup(); + + if (g) { + if (!gO || g != gO) { + return false; + } + + return true; + } + } + + return false; +} diff --git a/zone/mob.h b/zone/mob.h index 65b37d3c1..6da29429f 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -774,6 +774,7 @@ public: virtual bool HasGroup() = 0; virtual Raid* GetRaid() = 0; virtual Group* GetGroup() = 0; + bool IsInGroupOrRaid(Mob* other, bool sameRaidGroup = false); //Faction virtual inline int32 GetPrimaryFaction() const { return 0; } diff --git a/zone/spells.cpp b/zone/spells.cpp index 375ab1571..df9bf3942 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -2196,14 +2196,42 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce case ST_Group: case ST_GroupNoPets: { - if(IsClient() && CastToClient()->TGB() && IsTGBCompatibleSpell(spell_id) && (slot != CastingSlot::Item || RuleB(Spells, AllowItemTGB))) { - if( (!target) || - (target->IsNPC() && !(target->GetOwner() && target->GetOwner()->IsClient())) || - (target->IsCorpse()) ) + if ( + IsClient() && CastToClient()->TGB() && + IsTGBCompatibleSpell(spell_id) && + (slot != CastingSlot::Item || RuleB(Spells, AllowItemTGB)) + ) { + if ( + !target || + target->IsCorpse() || + ( + target->IsNPC() && + !(target->GetOwner() && target->GetOwner()->IsClient()) + ) + ) { spell_target = this; - else + } + else { spell_target = target; - } else { + } + } + else if ( + IsBot() && RuleB(Bots, EnableBotTGB) && + IsTGBCompatibleSpell(spell_id) && + (slot != CastingSlot::Item || RuleB(Spells, AllowItemTGB)) + ) { + if ( + !spell_target || + spell_target->IsCorpse() || + ( + spell_target->IsNPC() && + !(spell_target->GetOwner() && spell_target->GetOwner()->IsOfClientBot()) + ) + ) { + spell_target = this; + } + } + else { spell_target = this; } @@ -2530,8 +2558,14 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in //range check our target, if we have one and it is not us float range = spells[spell_id].range + GetRangeDistTargetSizeMod(spell_target); - if(IsClient() && CastToClient()->TGB() && IsTGBCompatibleSpell(spell_id) && IsGroupSpell(spell_id)) + if ( + ( + (IsClient() && CastToClient()->TGB()) || (IsBot() && RuleB(Bots, EnableBotTGB) + ) && + IsTGBCompatibleSpell(spell_id) && IsGroupSpell(spell_id)) + ) { range = spells[spell_id].aoe_range; + } range = GetActSpellRange(spell_id, range); if(IsClient() && IsIllusionSpell(spell_id) && (HasProjectIllusion())){ From 8b5112a616249700e13bd998b98efed3b32e4b95 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 31 Oct 2024 23:21:42 -0500 Subject: [PATCH 03/97] oopsies --- zone/bot.cpp | 16 ++++++---------- zone/bot.h | 2 +- zone/spells.cpp | 4 ++-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 16f4b9db3..abc0865fa 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -87,7 +87,7 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm SetGuardFlag(false); SetHoldFlag(false); SetAttackFlag(false); - SetCombatRoundForAlerts(true); + SetCombatRoundForAlerts(false); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); @@ -211,7 +211,7 @@ Bot::Bot( SetGuardFlag(false); SetHoldFlag(false); SetAttackFlag(false); - SetCombatRoundForAlerts(true); + SetCombatRoundForAlerts(false); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); @@ -2106,7 +2106,6 @@ void Bot::AI_Process() // ATTACKING FLAG (HATE VALIDATION) if (GetAttackingFlag() && tar->CheckAggro(this)) { - SetCombatRoundForAlerts(true); SetAttackingFlag(false); } @@ -2277,7 +2276,7 @@ void Bot::AI_Process() } else { // Out-of-combat behavior SetAttackFlag(false); - SetCombatRoundForAlerts(true); + SetCombatRoundForAlerts(false); SetAttackingFlag(false); if (!bot_owner->GetBotPulling()) { @@ -2422,7 +2421,6 @@ bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) { AddToHateList(hater, 1); SetTarget(hater); SetAttackingFlag(); - SetCombatRoundForAlerts(); if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { GetPet()->AddToHateList(hater, 1); @@ -2884,7 +2882,7 @@ bool Bot::IsValidTarget( SetTarget(nullptr); SetAttackFlag(false); - SetCombatRoundForAlerts(true); + SetCombatRoundForAlerts(false); SetAttackingFlag(false); if (PULLING_BOT) { @@ -2918,7 +2916,6 @@ Mob* Bot::GetBotTarget(Client* bot_owner) WipeHateList(); SetAttackFlag(false); - SetCombatRoundForAlerts(true); SetAttackingFlag(false); if (PULLING_BOT) { @@ -3137,7 +3134,6 @@ void Bot::SetOwnerTarget(Client* bot_owner) { } SetAttackFlag(false); - SetCombatRoundForAlerts(true); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); @@ -3152,7 +3148,6 @@ void Bot::SetOwnerTarget(Client* bot_owner) { WipeHateList(); AddToHateList(attack_target, 1); SetTarget(attack_target); - SetCombatRoundForAlerts(); SetAttackingFlag(); if (GetPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { GetPet()->WipeHateList(); @@ -3165,7 +3160,7 @@ void Bot::SetOwnerTarget(Client* bot_owner) { void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) { SetAttackFlag(false); - SetCombatRoundForAlerts(true); + SetCombatRoundForAlerts(false); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); @@ -4915,6 +4910,7 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) { if (!botpiercer || (botpiercer->ItemType != EQ::item::ItemType1HPiercing)) { if (!GetCombatRoundForAlerts()) { + SetCombatRoundForAlerts(); BotGroupSay(this, "I can't backstab with this weapon!"); } diff --git a/zone/bot.h b/zone/bot.h index 5836af133..0ee67c5ce 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -1105,7 +1105,7 @@ private: int32 GenerateBaseManaPoints(); void GenerateSpecialAttacks(); void SetBotID(uint32 botID); - void SetCombatRoundForAlerts(bool flag = true) { m_combat_round_alert_flag; } + void SetCombatRoundForAlerts(bool flag = true) { m_combat_round_alert_flag = flag; } void SetAttackingFlag(bool flag = true) { m_attacking_flag = flag; } void SetPullingFlag(bool flag = true) { m_pulling_flag = flag; } void SetReturningFlag(bool flag = true) { m_returning_flag = flag; } diff --git a/zone/spells.cpp b/zone/spells.cpp index df9bf3942..ce2041302 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -2560,9 +2560,9 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in float range = spells[spell_id].range + GetRangeDistTargetSizeMod(spell_target); if ( ( - (IsClient() && CastToClient()->TGB()) || (IsBot() && RuleB(Bots, EnableBotTGB) + (IsClient() && CastToClient()->TGB()) || (IsBot() && RuleB(Bots, EnableBotTGB)) ) && - IsTGBCompatibleSpell(spell_id) && IsGroupSpell(spell_id)) + IsTGBCompatibleSpell(spell_id) && IsGroupSpell(spell_id) ) { range = spells[spell_id].aoe_range; } From a4e6bc5c1902cd4df43d2796b0cc751dfb14975d Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Fri, 1 Nov 2024 14:26:51 -0500 Subject: [PATCH 04/97] remove commented lines for ^follow --- zone/bot_commands/follow.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/zone/bot_commands/follow.cpp b/zone/bot_commands/follow.cpp index 9e7c33eaf..57e97a607 100644 --- a/zone/bot_commands/follow.cpp +++ b/zone/bot_commands/follow.cpp @@ -118,7 +118,6 @@ void bot_command_follow(Client* c, const Seperator* sep) } std::list sbl; - //if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg] : nullptr, class_race_check ? atoi(sep->arg[ab_arg]) : 0) == ActionableBots::ABT_None) { if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } From f9762e041d37f3a725b721b9f1b956d3869f3989 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Fri, 1 Nov 2024 14:56:49 -0500 Subject: [PATCH 05/97] command cleanup --- zone/bot_commands/actionable.cpp | 2 +- zone/bot_commands/behind_mob.cpp | 2 +- zone/bot_commands/spell_engaged_priority.cpp | 4 ++-- zone/bot_commands/spell_idle_priority.cpp | 4 ++-- zone/bot_commands/spell_max_thresholds.cpp | 3 +-- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/zone/bot_commands/actionable.cpp b/zone/bot_commands/actionable.cpp index 73ec9ef6c..f66a5b60a 100644 --- a/zone/bot_commands/actionable.cpp +++ b/zone/bot_commands/actionable.cpp @@ -29,7 +29,7 @@ void bot_command_actionable(Client* c, const Seperator* sep) "[spawned] - selects all spawned bots.", "[all] - selects all spawned bots.", "
", - "You may only select your bots as actionable" + "You may only select your own bots." }; std::vector example_format = { }; diff --git a/zone/bot_commands/behind_mob.cpp b/zone/bot_commands/behind_mob.cpp index 5761c4c31..8e6915860 100644 --- a/zone/bot_commands/behind_mob.cpp +++ b/zone/bot_commands/behind_mob.cpp @@ -9,7 +9,7 @@ void bot_command_behind_mob(Client* c, const Seperator* sep) if (helper_is_help_or_usage(sep->arg[1])) { std::vector description = { - "Toggles whether or not bots will stay behind the mob during combat." + "-Toggles whether or not bots will stay behind the mob during combat." }; std::vector notes = { }; diff --git a/zone/bot_commands/spell_engaged_priority.cpp b/zone/bot_commands/spell_engaged_priority.cpp index 03c151c3a..89d76f2b5 100644 --- a/zone/bot_commands/spell_engaged_priority.cpp +++ b/zone/bot_commands/spell_engaged_priority.cpp @@ -14,8 +14,8 @@ void bot_command_spell_engaged_priority(Client* c, const Seperator* sep) std::vector notes = { - "-Setting a spell type to 0 will prevent that type from being cast.", - "-If 2 or more are set to the same priority they will sort by spell type ID." + "- Setting a spell type to 0 will prevent that type from being cast.", + "- If 2 or more are set to the same priority they will sort by spell type ID." }; std::vector example_format = diff --git a/zone/bot_commands/spell_idle_priority.cpp b/zone/bot_commands/spell_idle_priority.cpp index 9bb95cf46..566a3f46a 100644 --- a/zone/bot_commands/spell_idle_priority.cpp +++ b/zone/bot_commands/spell_idle_priority.cpp @@ -14,8 +14,8 @@ void bot_command_spell_idle_priority(Client* c, const Seperator* sep) std::vector notes = { - "-Setting a spell type to 0 will prevent that type from being cast.", - "-If 2 or more are set to the same priority they will sort by spell type ID." + "- Setting a spell type to 0 will prevent that type from being cast.", + "- If 2 or more are set to the same priority they will sort by spell type ID." }; std::vector example_format = diff --git a/zone/bot_commands/spell_max_thresholds.cpp b/zone/bot_commands/spell_max_thresholds.cpp index 8ded61c1f..3b4e0e85f 100644 --- a/zone/bot_commands/spell_max_thresholds.cpp +++ b/zone/bot_commands/spell_max_thresholds.cpp @@ -16,8 +16,7 @@ void bot_command_spell_max_thresholds(Client* c, const Seperator* sep) { "- All pet types are based off the pet's owner's setting", "- Any remaining types use the owner's setting when a pet is the target", - "- All Heals, Cures, Buffs (DS and resists included)", - "are based off the target's setting, not the caster", + "- All Heals, Cures, Buffs (DS and resists included) are based off the target's setting, not the caster", "- e.g., BotA is healing BotB using BotB's settings", }; From cd4ebb5a73f3a56b64768ecdd7264e136b72cb2a Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Fri, 1 Nov 2024 14:58:37 -0500 Subject: [PATCH 06/97] Rewrite ^followd command and remove squared values from command --- .../database_update_manifest_bots.cpp | 3 +- common/ruletypes.h | 2 + zone/bot.cpp | 8 +- zone/bot.h | 3 - zone/bot_commands/bot.cpp | 207 +++++++++++++++--- 5 files changed, 185 insertions(+), 38 deletions(-) diff --git a/common/database/database_update_manifest_bots.cpp b/common/database/database_update_manifest_bots.cpp index afaa9f23e..44083475b 100644 --- a/common/database/database_update_manifest_bots.cpp +++ b/common/database/database_update_manifest_bots.cpp @@ -189,7 +189,7 @@ WHERE rv.rule_name LIKE 'Bots:BotExpansionSettings' AND bd.expansion_bitmask != rv.rule_value; INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, 1, 0, `show_helm`, 'BaseSetting', 'ShowHelm' FROM bot_data WHERE `show_helm` != 1; -INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, 2, 0, `follow_distance`, 'BaseSetting', 'FollowDistance' FROM bot_data WHERE `follow_distance` != 184; +INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, 2, 0, sqrt(`follow_distance`), 'BaseSetting', 'FollowDistance' FROM bot_data WHERE `follow_distance` != 184; INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, 3, 0, `stop_melee_level`, 'BaseSetting', 'StopMeleeLevel' @@ -238,6 +238,7 @@ ALTER TABLE `bot_data` UPDATE `bot_command_settings` SET `aliases`= 'bh' WHERE `bot_command`='behindmob'; UPDATE `bot_command_settings` SET `aliases`= 'bs|settings' WHERE `bot_command`='botsettings'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|followdistance') ELSE 'followd||followdistance' END WHERE `bot_command`='botfollowdistance' AND `aliases` NOT LIKE '%followdistance%'; UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|ranged|toggleranged|btr') ELSE 'ranged|toggleranged|btr' END WHERE `bot_command`='bottoggleranged' AND `aliases` NOT LIKE '%ranged|toggleranged|btr%'; UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|cr') ELSE 'cr' END WHERE `bot_command`='casterrange' AND `aliases` NOT LIKE '%cr%'; UPDATE `bot_command_settings` SET `aliases`= 'copy' WHERE `bot_command`='copysettings'; diff --git a/common/ruletypes.h b/common/ruletypes.h index 576d5e474..fcbe55874 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -867,6 +867,8 @@ RULE_INT(Bots, StatusCreateLimit, 120, "Minimum status to bypass spawn limit. De RULE_BOOL(Bots, BardsAnnounceCasts, false, "This determines whether or not Bard bots will announce that they're casting songs (Buffs, Heals, Nukes, Slows, etc.) they will always announce Mez.") RULE_BOOL(Bots, EnableBotTGB, true, "If enabled bots will cast group buffs as TGB.") RULE_BOOL(Bots, DoResponseAnimations, true, "If enabled bots will do animations to certain responses or commands.") +RULE_INT(Bots, DefaultFollowDistance, 20, "Default 20. Distance a bot will follow behind.") +RULE_INT(Bots, MaxFollowDistance, 300, "Default 300. Max distance a bot can be set to follow behind.") RULE_CATEGORY_END() RULE_CATEGORY(Chat) diff --git a/zone/bot.cpp b/zone/bot.cpp index abc0865fa..4eb44d55d 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9904,7 +9904,7 @@ void Bot::SetBotBaseSetting(uint16 botSetting, int settingValue) { SetShowHelm(settingValue); break; case BotBaseSettings::FollowDistance: - SetFollowDistance(EQ::Clamp(static_cast(settingValue), static_cast(1), BOT_FOLLOW_DISTANCE_DEFAULT_MAX)); + SetFollowDistance(EQ::Clamp(static_cast(settingValue * settingValue), static_cast(1), static_cast((RuleI(Bots, MaxFollowDistance) * RuleI(Bots, MaxFollowDistance))))); break; case BotBaseSettings::StopMeleeLevel: SetStopMeleeLevel(settingValue); @@ -9953,8 +9953,8 @@ int Bot::GetBotBaseSetting(uint16 botSetting) { //LogBotSettingsDetail("Returning current GetShowHelm of [{}] for [{}]", GetShowHelm(), GetCleanName()); //deleteme return GetShowHelm(); case BotBaseSettings::FollowDistance: - //LogBotSettingsDetail("Returning current GetFollowDistance of [{}] for [{}]", GetFollowDistance(), GetCleanName()); //deleteme - return GetFollowDistance(); + //LogBotSettingsDetail("Returning current GetFollowDistance of [{}] for [{}]", sqrt(GetFollowDistance()), GetCleanName()); //deleteme + return sqrt(GetFollowDistance()); case BotBaseSettings::StopMeleeLevel: //LogBotSettingsDetail("Returning current GetStopMeleeLevel of [{}] for [{}]", GetStopMeleeLevel(), GetCleanName()); //deleteme return GetStopMeleeLevel(); @@ -10002,7 +10002,7 @@ int Bot::GetDefaultBotBaseSetting(uint16 botSetting) { case BotBaseSettings::ShowHelm: return true; case BotBaseSettings::FollowDistance: - return BOT_FOLLOW_DISTANCE_DEFAULT; + return (RuleI(Bots, DefaultFollowDistance) * RuleI(Bots, DefaultFollowDistance)); case BotBaseSettings::StopMeleeLevel: if (IsCasterClass(GetClass())) { return RuleI(Bots, CasterStopMeleeLevel); diff --git a/zone/bot.h b/zone/bot.h index 0ee67c5ce..4e40f3939 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -37,9 +37,6 @@ #include -constexpr uint32 BOT_FOLLOW_DISTANCE_DEFAULT = 184; // as DSq value (~13.565 units) -constexpr uint32 BOT_FOLLOW_DISTANCE_DEFAULT_MAX = 2500; // as DSq value (50 units) - constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MIN = 1500; // 1.5 seconds diff --git a/zone/bot_commands/bot.cpp b/zone/bot_commands/bot.cpp index ccf1e51ff..e360d49ed 100644 --- a/zone/bot_commands/bot.cpp +++ b/zone/bot_commands/bot.cpp @@ -466,58 +466,205 @@ void bot_command_delete(Client *c, const Seperator *sep) void bot_command_follow_distance(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_command_follow_distance", sep->arg[0], "botfollowdistance")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [set [1 to %i]] [distance] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0], BOT_FOLLOW_DISTANCE_DEFAULT_MAX); - c->Message(Chat::White, "usage: %s [clear] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + if (helper_command_alias_fail(c, "bot_command_follow_distance", sep->arg[0], "botfollowdistance")) { return; } - const int ab_mask = ActionableBots::ABM_NoFilter; - uint32 bfd = BOT_FOLLOW_DISTANCE_DEFAULT; + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Sets or resets the follow distance of the selected bots." + }; + + std::vector notes = + { + fmt::format( + "[Default]: {}", + RuleI(Bots, MaxFollowDistance) + ), + + fmt::format( + "- You must use a value between 1 and {}.", + RuleI(Bots, MaxFollowDistance) + ) + }; + + std::vector example_format = + { + fmt::format( + "{} [reset]/[set [value]] [actionable]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set all bots to follow at a distance of 25:", + fmt::format( + "{} set 25 spawned", + sep->arg[0] + ) + }; + std::vector examples_two = + { + "To check the curret following distance of all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + std::vector examples_three = + { + "To reset the following distance of all Wizards:", + fmt::format( + "{} reset byclass {}", + sep->arg[0], + Class::Wizard + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type2; + + uint32 bfd = RuleI(Bots, DefaultFollowDistance); bool set_flag = false; + bool currentCheck = false; int ab_arg = 2; - if (!strcasecmp(sep->arg[1], "set")) { + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("set")) { if (!sep->IsNumber(2)) { - c->Message(Chat::White, "A numeric [distance] is required to use this command. [1 to %i]", BOT_FOLLOW_DISTANCE_DEFAULT_MAX); + c->Message(Chat::Yellow, "You must enter a value between 1 and %i.", RuleI(Bots, MaxFollowDistance)); + return; } bfd = Strings::ToInt(sep->arg[2]); - if (bfd < 1) - bfd = 1; - if (bfd > BOT_FOLLOW_DISTANCE_DEFAULT_MAX) - bfd = BOT_FOLLOW_DISTANCE_DEFAULT_MAX; + + if (bfd < 1) { + c->Message(Chat::Yellow, "You must enter a value between 1 and %i.", RuleI(Bots, MaxFollowDistance)); + + return; + } + + if (bfd > RuleI(Bots, MaxFollowDistance)) { + c->Message(Chat::Yellow, "You must enter a value between 1 and %i.", RuleI(Bots, MaxFollowDistance)); + + return; + } + set_flag = true; - ab_arg = 3; + ++ab_arg; } - else if (strcasecmp(sep->arg[1], "clear")) { - c->Message(Chat::White, "This command requires a [set | clear] argument"); + else if (!arg1.compare("current")) { + currentCheck = true; + } + else if (arg1.compare("reset")) { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + return; } + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + std::list sbl; - auto ab_type = ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]); - if (ab_type == ActionableBots::ABT_None) + + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; - - int bot_count = 0; - for (auto bot_iter : sbl) { - if (!bot_iter) - continue; - - bot_iter->SetFollowDistance(bfd); - - ++bot_count; } - if (ab_type == ActionableBots::ABT_All) { - c->Message(Chat::White, "%s all of your bot follow distances to %i", set_flag ? "Set" : "Cleared", bfd); + sbl.remove(nullptr); + + int botCount = 0; + for (auto bot_iter : sbl) { + if (!bot_iter) { + continue; + } + + if (currentCheck) { + Mob* follow_mob = entity_list.GetMob(bot_iter->GetFollowID()); + + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I am currently following {} at a distance of {}.'", + bot_iter->GetCleanName(), + follow_mob ? follow_mob->GetCleanName() : "no one", + sqrt(bot_iter->GetFollowDistance()) + ).c_str() + ); + } + else { + bot_iter->SetFollowDistance(bfd * bfd); + ++botCount; + } + } + + if (currentCheck) { + return; + } + + if (botCount == 1) { + Mob* follow_mob = entity_list.GetMob(sbl.front()->GetFollowID()); + + c->Message( + Chat::Green, + fmt::format( + "{} says, 'Following {} at a distance of {}.'", + sbl.front()->GetCleanName(), + follow_mob ? follow_mob->GetCleanName() : "you", + bfd + ).c_str() + ); } else { - c->Message(Chat::White, "%s %i of your spawned bot follow distances to %i", (set_flag ? "Set" : "Cleared"), bot_count, bfd); + c->Message( + Chat::Green, + fmt::format( + "{} of your bots are now following at a distance of {}.", + botCount, + bfd + ).c_str() + ); } } From c16e1e59546e00eede99432eb1249d939c40465f Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sat, 2 Nov 2024 22:05:55 -0500 Subject: [PATCH 07/97] misc cleanup --- zone/bot_commands/caster_range.cpp | 2 +- zone/bot_commands/follow.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/zone/bot_commands/caster_range.cpp b/zone/bot_commands/caster_range.cpp index 447c739db..87676c7d4 100644 --- a/zone/bot_commands/caster_range.cpp +++ b/zone/bot_commands/caster_range.cpp @@ -70,7 +70,7 @@ void bot_command_caster_range(Client* c, const Seperator* sep) c->Message( Chat::White, fmt::format( - "{} says, 'My current Caster Range is {}.'", + "{} says, 'My current caster range is {}.'", my_bot->GetCleanName(), my_bot->GetBotCasterRange() ).c_str() diff --git a/zone/bot_commands/follow.cpp b/zone/bot_commands/follow.cpp index 57e97a607..c5b7f8f78 100644 --- a/zone/bot_commands/follow.cpp +++ b/zone/bot_commands/follow.cpp @@ -132,6 +132,7 @@ void bot_command_follow(Client* c, const Seperator* sep) for (auto bot_iter : sbl) { if (currentCheck) { follow_mob = entity_list.GetMob(bot_iter->GetFollowID()); + c->Message( Chat::Green, fmt::format( From 7d8f4d9849121f62696ff9685c591f0b97603c75 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 6 Nov 2024 00:20:41 -0600 Subject: [PATCH 08/97] fix formatting in cazictouch --- zone/spells.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zone/spells.cpp b/zone/spells.cpp index ce2041302..1f80e3021 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -2471,8 +2471,9 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in } } - if ((RuleB(Bots, CazicTouchBotsOwner) && spell_target && spell_target->IsBot()) && spell_id == (SPELL_CAZIC_TOUCH || spell_id == SPELL_TOUCH_OF_VINITRAS)) { + if ((RuleB(Bots, CazicTouchBotsOwner) && spell_target && spell_target->IsBot()) && (spell_id == SPELL_CAZIC_TOUCH || spell_id == SPELL_TOUCH_OF_VINITRAS)) { auto bot_owner = spell_target->GetOwner(); + if (bot_owner) { spell_target = bot_owner; } From 32a10f4219dfd3bfd20394f67c328254bd04a07b Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 6 Nov 2024 00:30:40 -0600 Subject: [PATCH 09/97] Implement and rewrite stances --- .../database_update_manifest_bots.cpp | 15 +- .../base/base_bot_settings_repository.h | 52 ++-- zone/bot.cpp | 151 ++++++----- zone/bot.h | 26 +- zone/bot_commands/bot.cpp | 254 +++++++++++++++--- zone/bot_commands/default_settings.cpp | 146 +++++----- zone/bot_database.cpp | 62 +++-- zone/client.cpp | 32 +-- zone/mob.cpp | 208 +++++++++++--- zone/mob.h | 8 +- 10 files changed, 659 insertions(+), 295 deletions(-) diff --git a/common/database/database_update_manifest_bots.cpp b/common/database/database_update_manifest_bots.cpp index 44083475b..639d173b0 100644 --- a/common/database/database_update_manifest_bots.cpp +++ b/common/database/database_update_manifest_bots.cpp @@ -174,6 +174,7 @@ CREATE TABLE `bot_settings` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `char_id` INT UNSIGNED NOT NULL, `bot_id` INT UNSIGNED NOT NULL, + `stance` INT UNSIGNED NOT NULL, `setting_id` INT UNSIGNED NOT NULL, `setting_type` INT UNSIGNED NOT NULL, `value` INT UNSIGNED NOT NULL, @@ -183,16 +184,16 @@ CREATE TABLE `bot_settings` ( ) COLLATE='utf8mb4_general_ci'; -INSERT INTO bot_settings SELECT NULL, 0, bd.`bot_id`, 0, 0, bd.`expansion_bitmask`, 'BaseSetting', 'ExpansionBitmask' FROM bot_data bd +INSERT INTO bot_settings SELECT NULL, 0, bd.`bot_id`, (SELECT bs.`stance_id` FROM bot_stances bs WHERE bs.`bot_id` = bd.`bot_id`) AS stance_id, 0, 0, bd.`expansion_bitmask`, 'BaseSetting', 'ExpansionBitmask' FROM bot_data bd JOIN rule_values rv WHERE rv.rule_name LIKE 'Bots:BotExpansionSettings' AND bd.expansion_bitmask != rv.rule_value; -INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, 1, 0, `show_helm`, 'BaseSetting', 'ShowHelm' FROM bot_data WHERE `show_helm` != 1; -INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, 2, 0, sqrt(`follow_distance`), 'BaseSetting', 'FollowDistance' FROM bot_data WHERE `follow_distance` != 184; +INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, (SELECT bs.`stance_id` FROM bot_stances bs WHERE bs.`bot_id` = bd.`bot_id`) AS stance_id, 1, 0, `show_helm`, 'BaseSetting', 'ShowHelm' FROM bot_data WHERE `show_helm` != 1; +INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, (SELECT bs.`stance_id` FROM bot_stances bs WHERE bs.`bot_id` = bd.`bot_id`) AS stance_id, 2, 0, sqrt(`follow_distance`), 'BaseSetting', 'FollowDistance' FROM bot_data WHERE `follow_distance` != 184; INSERT INTO bot_settings -SELECT NULL, 0, `bot_id`, 3, 0, `stop_melee_level`, 'BaseSetting', 'StopMeleeLevel' +SELECT NULL, 0, `bot_id`, (SELECT bs.`stance_id` FROM bot_stances bs WHERE bs.`bot_id` = bd.`bot_id`) AS stance_id, 3, 0, `stop_melee_level`, 'BaseSetting', 'StopMeleeLevel' FROM ( SELECT `bot_id`, (CASE @@ -204,11 +205,11 @@ FROM ( ) AS `subquery` WHERE `sml` != `stop_melee_level`; -INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, 4, 0, `enforce_spell_settings`, 'BaseSetting', 'EnforceSpellSettings' FROM bot_data WHERE `enforce_spell_settings` != 0; -INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, 5, 0, `archery_setting`, 'BaseSetting', 'RangedSetting' FROM bot_data WHERE `archery_setting` != 0; +INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, (SELECT bs.`stance_id` FROM bot_stances bs WHERE bs.`bot_id` = bd.`bot_id`) AS stance_id, 4, 0, `enforce_spell_settings`, 'BaseSetting', 'EnforceSpellSettings' FROM bot_data WHERE `enforce_spell_settings` != 0; +INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, (SELECT bs.`stance_id` FROM bot_stances bs WHERE bs.`bot_id` = bd.`bot_id`) AS stance_id, 5, 0, `archery_setting`, 'BaseSetting', 'RangedSetting' FROM bot_data WHERE `archery_setting` != 0; INSERT INTO bot_settings -SELECT NULL, 0, `bot_id`, 8, 0, `caster_range`, 'BaseSetting', 'CasterRange' +SELECT NULL, 0, `bot_id`, (SELECT bs.`stance_id` FROM bot_stances bs WHERE bs.`bot_id` = bd.`bot_id`) AS stance_id, 8, 0, `caster_range`, 'BaseSetting', 'CasterRange' FROM ( SELECT `bot_id`, (CASE diff --git a/common/repositories/base/base_bot_settings_repository.h b/common/repositories/base/base_bot_settings_repository.h index 2018177dc..92d4aba15 100644 --- a/common/repositories/base/base_bot_settings_repository.h +++ b/common/repositories/base/base_bot_settings_repository.h @@ -22,6 +22,7 @@ public: uint32_t id; uint32_t char_id; uint32_t bot_id; + uint8_t stance; uint16_t setting_id; uint8_t setting_type; int32_t value; @@ -40,6 +41,7 @@ public: "id", "char_id", "bot_id", + "stance", "setting_id", "setting_type", "value", @@ -54,6 +56,7 @@ public: "id", "char_id", "bot_id", + "stance", "setting_id", "setting_type", "value", @@ -102,6 +105,7 @@ public: e.id = 0; e.char_id = 0; e.bot_id = 0; + e.stance = 0; e.setting_id = 0; e.setting_type = 0; e.value = 0; @@ -146,11 +150,12 @@ public: e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; e.bot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.setting_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.setting_type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.value = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.category_name = row[6] ? row[6] : ""; - e.setting_name = row[7] ? row[7] : ""; + e.stance = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.setting_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.setting_type = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.value = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.category_name = row[7] ? row[7] : ""; + e.setting_name = row[8] ? row[8] : ""; return e; } @@ -187,11 +192,12 @@ public: v.push_back(columns[0] + " = " + std::to_string(e.id)); v.push_back(columns[1] + " = " + std::to_string(e.char_id)); v.push_back(columns[2] + " = " + std::to_string(e.bot_id)); - v.push_back(columns[3] + " = " + std::to_string(e.setting_id)); - v.push_back(columns[4] + " = " + std::to_string(e.setting_type)); - v.push_back(columns[5] + " = " + std::to_string(e.value)); - v.push_back(columns[6] + " = '" + Strings::Escape(e.category_name) + "'"); - v.push_back(columns[7] + " = '" + Strings::Escape(e.setting_name) + "'"); + v.push_back(columns[3] + " = " + std::to_string(e.stance)); + v.push_back(columns[4] + " = " + std::to_string(e.setting_id)); + v.push_back(columns[5] + " = " + std::to_string(e.setting_type)); + v.push_back(columns[6] + " = " + std::to_string(e.value)); + v.push_back(columns[7] + " = '" + Strings::Escape(e.category_name) + "'"); + v.push_back(columns[8] + " = '" + Strings::Escape(e.setting_name) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -216,6 +222,7 @@ public: v.push_back(std::to_string(e.id)); v.push_back(std::to_string(e.char_id)); v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.stance)); v.push_back(std::to_string(e.setting_id)); v.push_back(std::to_string(e.setting_type)); v.push_back(std::to_string(e.value)); @@ -253,6 +260,7 @@ public: v.push_back(std::to_string(e.id)); v.push_back(std::to_string(e.char_id)); v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.stance)); v.push_back(std::to_string(e.setting_id)); v.push_back(std::to_string(e.setting_type)); v.push_back(std::to_string(e.value)); @@ -294,11 +302,12 @@ public: e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; e.bot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.setting_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.setting_type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.value = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.category_name = row[6] ? row[6] : ""; - e.setting_name = row[7] ? row[7] : ""; + e.stance = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.setting_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.setting_type = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.value = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.category_name = row[7] ? row[7] : ""; + e.setting_name = row[8] ? row[8] : ""; all_entries.push_back(e); } @@ -326,11 +335,12 @@ public: e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; e.bot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.setting_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.setting_type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.value = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.category_name = row[6] ? row[6] : ""; - e.setting_name = row[7] ? row[7] : ""; + e.stance = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.setting_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.setting_type = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.value = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.category_name = row[7] ? row[7] : ""; + e.setting_name = row[8] ? row[8] : ""; all_entries.push_back(e); } @@ -408,6 +418,7 @@ public: v.push_back(std::to_string(e.id)); v.push_back(std::to_string(e.char_id)); v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.stance)); v.push_back(std::to_string(e.setting_id)); v.push_back(std::to_string(e.setting_type)); v.push_back(std::to_string(e.value)); @@ -438,6 +449,7 @@ public: v.push_back(std::to_string(e.id)); v.push_back(std::to_string(e.char_id)); v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.stance)); v.push_back(std::to_string(e.setting_id)); v.push_back(std::to_string(e.setting_type)); v.push_back(std::to_string(e.value)); diff --git a/zone/bot.cpp b/zone/bot.cpp index 4eb44d55d..e7d4737d7 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9995,14 +9995,14 @@ int Bot::GetBotBaseSetting(uint16 botSetting) { return true; } -int Bot::GetDefaultBotBaseSetting(uint16 botSetting) { +int Bot::GetDefaultBotBaseSetting(uint16 botSetting, uint8 stance) { switch (botSetting) { case BotBaseSettings::ExpansionBitmask: return RuleI(Bots, BotExpansionSettings); case BotBaseSettings::ShowHelm: return true; case BotBaseSettings::FollowDistance: - return (RuleI(Bots, DefaultFollowDistance) * RuleI(Bots, DefaultFollowDistance)); + return RuleI(Bots, DefaultFollowDistance); case BotBaseSettings::StopMeleeLevel: if (IsCasterClass(GetClass())) { return RuleI(Bots, CasterStopMeleeLevel); @@ -10013,7 +10013,7 @@ int Bot::GetDefaultBotBaseSetting(uint16 botSetting) { case BotBaseSettings::PetSetTypeSetting: return 0; case BotBaseSettings::BehindMob: - if (GetClass() == Class::Rogue) { + if (GetClass() == Class::Rogue || (IsPureMeleeClass() && GetClass() != Class::Warrior)) { return true; } else { @@ -10053,10 +10053,14 @@ int Bot::GetDefaultBotBaseSetting(uint16 botSetting) { void Bot::LoadDefaultBotSettings() { + _spellSettings.clear(); + + uint8 botStance = GetBotStance(); + for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { - SetBotBaseSetting(i, GetDefaultSetting(BotSettingCategories::BaseSetting, i)); - LogBotSettingsDetail("{} says, 'Setting default {} [{}] to [{}]'", GetCleanName(), GetBotSettingCategoryName(i), i, GetDefaultBotBaseSetting(i)); //deleteme - } + SetBotBaseSetting(i, GetDefaultSetting(BotSettingCategories::BaseSetting, i, botStance)); + LogBotSettingsDetail("{} says, 'Setting default {} [{}] to [{}]'", GetCleanName(), GetBotSettingCategoryName(i), i, GetDefaultBotBaseSetting(i, botStance)); //deleteme + } for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { BotSpellSettings_Struct t; @@ -10064,28 +10068,28 @@ void Bot::LoadDefaultBotSettings() { t.spellType = i; t.shortName = GetSpellTypeShortNameByID(i); t.name = GetSpellTypeNameByID(i); - t.hold = GetDefaultSpellHold(i); - t.delay = GetDefaultSpellDelay(i); - t.minThreshold = GetDefaultSpellMinThreshold(i); - t.maxThreshold = GetDefaultSpellMaxThreshold(i); - t.resistLimit = GetDefaultSpellTypeResistLimit(i); - t.aggroCheck = GetDefaultSpellTypeAggroCheck(i); - t.minManaPct = GetDefaultSpellTypeMinManaLimit(i); - t.maxManaPct = GetDefaultSpellTypeMaxManaLimit(i); - t.minHPPct = GetDefaultSpellTypeMinHPLimit(i); - t.maxHPPct = GetDefaultSpellTypeMaxHPLimit(i); - t.idlePriority = GetDefaultSpellTypePriority(i, BotPriorityCategories::Idle, GetClass()); - t.engagedPriority = GetDefaultSpellTypePriority(i, BotPriorityCategories::Engaged, GetClass()); - t.pursuePriority = GetDefaultSpellTypePriority(i, BotPriorityCategories::Pursue, GetClass()); - t.AEOrGroupTargetCount = GetDefaultSpellTypeAEOrGroupTargetCount(i); + t.hold = GetDefaultSpellHold(i, botStance); + t.delay = GetDefaultSpellDelay(i, botStance); + t.minThreshold = GetDefaultSpellMinThreshold(i, botStance); + t.maxThreshold = GetDefaultSpellMaxThreshold(i, botStance); + t.resistLimit = GetDefaultSpellTypeResistLimit(i, botStance); + t.aggroCheck = GetDefaultSpellTypeAggroCheck(i, botStance); + t.minManaPct = GetDefaultSpellTypeMinManaLimit(i, botStance); + t.maxManaPct = GetDefaultSpellTypeMaxManaLimit(i, botStance); + t.minHPPct = GetDefaultSpellTypeMinHPLimit(i, botStance); + t.maxHPPct = GetDefaultSpellTypeMaxHPLimit(i, botStance); + t.idlePriority = GetDefaultSpellTypePriority(i, BotPriorityCategories::Idle, GetClass(), botStance); + t.engagedPriority = GetDefaultSpellTypePriority(i, BotPriorityCategories::Engaged, GetClass(), botStance); + t.pursuePriority = GetDefaultSpellTypePriority(i, BotPriorityCategories::Pursue, GetClass(), botStance); + t.AEOrGroupTargetCount = GetDefaultSpellTypeAEOrGroupTargetCount(i, botStance); t.recastTimer.Start(); _spellSettings.push_back(t); - LogBotSettingsDetail("{} says, 'Setting defaults for {} ({}) [#{}]'", GetCleanName(), t.name, t.shortName, t.spellType); //deleteme - LogBotSettingsDetail("{} says, 'Hold = [{}] | Delay = [{}ms] | MinThreshold = [{}\%] | MaxThreshold = [{}\%]'", GetCleanName(), GetDefaultSpellHold(i), GetDefaultSpellDelay(i), GetDefaultSpellMinThreshold(i), GetDefaultSpellMaxThreshold(i)); //deleteme - LogBotSettingsDetail("{} says, 'AggroCheck = [{}] | MinManaPCT = [{}\%] | MaxManaPCT = [{}\%] | MinHPPCT = [{}\% | MaxHPPCT = [{}\%]'", GetCleanName(), GetDefaultSpellTypeAggroCheck(i), GetDefaultSpellTypeMinManaLimit(i), GetDefaultSpellTypeMaxManaLimit(i), GetDefaultSpellTypeMinHPLimit(i), GetDefaultSpellTypeMaxHPLimit(i)); //deleteme - LogBotSettingsDetail("{} says, 'IdlePriority = [{}] | EngagedPriority = [{}] | PursuePriority = [{}] | AEOrGroupTargetCount = [{}] | recastTimer = [{}]'", GetCleanName(), GetDefaultSpellTypeIdlePriority(i, GetClass()), GetDefaultSpellTypeEngagedPriority(i, GetClass()), GetDefaultSpellTypePursuePriority(i, GetClass()), GetDefaultSpellTypeAEOrGroupTargetCount(i), t.recastTimer.GetRemainingTime()); //deleteme + LogBotSettingsDetail("{} says, 'Setting defaults for {} ({}) [#{}] - [{} [#{}] stance]'", GetCleanName(), t.name, t.shortName, t.spellType, Stance::GetName(botStance), botStance); //deleteme + LogBotSettingsDetail("{} says, 'Hold = [{}] | Delay = [{}ms] | MinThreshold = [{}\%] | MaxThreshold = [{}\%]'", GetCleanName(), GetDefaultSpellHold(i, botStance), GetDefaultSpellDelay(i, botStance), GetDefaultSpellMinThreshold(i, botStance), GetDefaultSpellMaxThreshold(i, botStance)); //deleteme + LogBotSettingsDetail("{} says, 'AggroCheck = [{}] | MinManaPCT = [{}\%] | MaxManaPCT = [{}\%] | MinHPPCT = [{}\% | MaxHPPCT = [{}\%]'", GetCleanName(), GetDefaultSpellTypeAggroCheck(i, botStance), GetDefaultSpellTypeMinManaLimit(i, botStance), GetDefaultSpellTypeMaxManaLimit(i, botStance), GetDefaultSpellTypeMinHPLimit(i, botStance), GetDefaultSpellTypeMaxHPLimit(i, botStance)); //deleteme + LogBotSettingsDetail("{} says, 'IdlePriority = [{}] | EngagedPriority = [{}] | PursuePriority = [{}] | AEOrGroupTargetCount = [{}]'", GetCleanName(), GetDefaultSpellTypeIdlePriority(i, GetClass(), botStance), GetDefaultSpellTypeEngagedPriority(i, GetClass(), botStance), GetDefaultSpellTypePursuePriority(i, GetClass(), botStance), GetDefaultSpellTypeAEOrGroupTargetCount(i, botStance)); //deleteme } } @@ -10159,36 +10163,36 @@ uint16 Bot::GetSpellTypePriority(uint16 spellType, uint8 priorityType) { } } -int Bot::GetDefaultSetting(uint16 settingCategory, uint16 settingType) { +int Bot::GetDefaultSetting(uint16 settingCategory, uint16 settingType, uint8 stance) { switch (settingCategory) { case BotSettingCategories::BaseSetting: - return GetDefaultBotBaseSetting(settingType); + return GetDefaultBotBaseSetting(settingType, stance); case BotSettingCategories::SpellHold: - return GetDefaultSpellHold(settingType); + return GetDefaultSpellHold(settingType, stance); case BotSettingCategories::SpellDelay: - return GetDefaultSpellDelay(settingType); + return GetDefaultSpellDelay(settingType, stance); case BotSettingCategories::SpellMinThreshold: - return GetDefaultSpellMinThreshold(settingType); + return GetDefaultSpellMinThreshold(settingType, stance); case BotSettingCategories::SpellMaxThreshold: - return GetDefaultSpellMinThreshold(settingType); + return GetDefaultSpellMaxThreshold(settingType, stance); case BotSettingCategories::SpellTypeAggroCheck: - return GetDefaultSpellTypeAggroCheck(settingType); + return GetDefaultSpellTypeAggroCheck(settingType, stance); case BotSettingCategories::SpellTypeMinManaPct: - return GetDefaultSpellTypeMinManaLimit(settingType); + return GetDefaultSpellTypeMinManaLimit(settingType, stance); case BotSettingCategories::SpellTypeMaxManaPct: - return GetDefaultSpellTypeMaxManaLimit(settingType); + return GetDefaultSpellTypeMaxManaLimit(settingType, stance); case BotSettingCategories::SpellTypeMinHPPct: - return GetDefaultSpellTypeMinHPLimit(settingType); + return GetDefaultSpellTypeMinHPLimit(settingType, stance); case BotSettingCategories::SpellTypeMaxHPPct: - return GetDefaultSpellTypeMaxHPLimit(settingType); + return GetDefaultSpellTypeMaxHPLimit(settingType, stance); case BotSettingCategories::SpellTypeIdlePriority: - return GetDefaultSpellTypePriority(settingType, BotPriorityCategories::Idle, GetClass()); + return GetDefaultSpellTypePriority(settingType, BotPriorityCategories::Idle, GetClass(), stance); case BotSettingCategories::SpellTypeEngagedPriority: - return GetDefaultSpellTypePriority(settingType, BotPriorityCategories::Engaged, GetClass()); + return GetDefaultSpellTypePriority(settingType, BotPriorityCategories::Engaged, GetClass(), stance); case BotSettingCategories::SpellTypePursuePriority: - return GetDefaultSpellTypePriority(settingType, BotPriorityCategories::Pursue, GetClass()); + return GetDefaultSpellTypePriority(settingType, BotPriorityCategories::Pursue, GetClass(), stance); case BotSettingCategories::SpellTypeAEOrGroupTargetCount: - return GetDefaultSpellTypeAEOrGroupTargetCount(settingType); + return GetDefaultSpellTypeAEOrGroupTargetCount(settingType, stance); default: break; } @@ -10205,7 +10209,7 @@ int Bot::GetSetting(uint16 settingCategory, uint16 settingType) { case BotSettingCategories::SpellMinThreshold: return GetSpellMinThreshold(settingType); case BotSettingCategories::SpellMaxThreshold: - return GetSpellMinThreshold(settingType); + return GetSpellMaxThreshold(settingType); case BotSettingCategories::SpellTypeAggroCheck: return GetSpellTypeAggroCheck(settingType); case BotSettingCategories::SpellTypeMinManaPct: @@ -10229,20 +10233,20 @@ int Bot::GetSetting(uint16 settingCategory, uint16 settingType) { } } -uint16 Bot::GetDefaultSpellTypePriority(uint16 spellType, uint8 priorityType, uint8 botClass) { +uint16 Bot::GetDefaultSpellTypePriority(uint16 spellType, uint8 priorityType, uint8 botClass, uint8 stance) { switch (priorityType) { case BotPriorityCategories::Idle: - return GetDefaultSpellTypeIdlePriority(spellType, botClass); + return GetDefaultSpellTypeIdlePriority(spellType, botClass, stance); case BotPriorityCategories::Engaged: - return GetDefaultSpellTypeEngagedPriority(spellType, botClass); + return GetDefaultSpellTypeEngagedPriority(spellType, botClass, stance); case BotPriorityCategories::Pursue: - return GetDefaultSpellTypePursuePriority(spellType, botClass); + return GetDefaultSpellTypePursuePriority(spellType, botClass, stance); default: return 0; } } -uint16 Bot::GetDefaultSpellTypeIdlePriority(uint16 spellType, uint8 botClass) { +uint16 Bot::GetDefaultSpellTypeIdlePriority(uint16 spellType, uint8 botClass, uint8 stance) { if (!BOT_SPELL_TYPES_BENEFICIAL(spellType, botClass)) { return 0; } @@ -10351,7 +10355,7 @@ uint16 Bot::GetDefaultSpellTypeIdlePriority(uint16 spellType, uint8 botClass) { return priority; } -uint16 Bot::GetDefaultSpellTypeEngagedPriority(uint16 spellType, uint8 botClass) { +uint16 Bot::GetDefaultSpellTypeEngagedPriority(uint16 spellType, uint8 botClass, uint8 stance) { switch (spellType) { case BotSpellTypes::Escape: return 1; @@ -10439,14 +10443,12 @@ uint16 Bot::GetDefaultSpellTypeEngagedPriority(uint16 spellType, uint8 botClass) return 42; case BotSpellTypes::InCombatBuffSong: return 43; - case BotSpellTypes::Pet: - return 44; default: return 0; } } -uint16 Bot::GetDefaultSpellTypePursuePriority(uint16 spellType, uint8 botClass) { +uint16 Bot::GetDefaultSpellTypePursuePriority(uint16 spellType, uint8 botClass, uint8 stance) { switch (spellType) { case BotSpellTypes::Escape: return 1; @@ -10497,7 +10499,7 @@ uint16 Bot::GetDefaultSpellTypePursuePriority(uint16 spellType, uint8 botClass) } } -uint16 Bot::GetDefaultSpellTypeResistLimit(uint16 spellType) { +uint16 Bot::GetDefaultSpellTypeResistLimit(uint16 spellType, uint8 stance) { if (!BOT_SPELL_TYPES_BENEFICIAL(spellType, GetClass())) { return RuleI(Bots, SpellResistLimit); @@ -10507,35 +10509,46 @@ uint16 Bot::GetDefaultSpellTypeResistLimit(uint16 spellType) { } } -bool Bot::GetDefaultSpellTypeAggroCheck(uint16 spellType) { +bool Bot::GetDefaultSpellTypeAggroCheck(uint16 spellType, uint8 stance) { + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + return false; + default: + break; + } + switch (spellType) { + case BotSpellTypes::Nuke: + case BotSpellTypes::Root: + case BotSpellTypes::Snare: + case BotSpellTypes::DOT: + case BotSpellTypes::Slow: + case BotSpellTypes::Debuff: + case BotSpellTypes::Fear: + case BotSpellTypes::Stun: case BotSpellTypes::AENukes: case BotSpellTypes::AERains: - case BotSpellTypes::PBAENuke: - case BotSpellTypes::Nuke: - case BotSpellTypes::AESlow: - case BotSpellTypes::Slow: - case BotSpellTypes::AESnare: - case BotSpellTypes::Snare: - case BotSpellTypes::AEDispel: - case BotSpellTypes::Dispel: - case BotSpellTypes::AEDebuff: - case BotSpellTypes::Debuff: - case BotSpellTypes::AEDoT: - case BotSpellTypes::DOT: case BotSpellTypes::AEStun: - case BotSpellTypes::Stun: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::AESlow: + case BotSpellTypes::AESnare: + case BotSpellTypes::AEFear: + case BotSpellTypes::AEDispel: + case BotSpellTypes::AERoot: + case BotSpellTypes::AEDoT: + case BotSpellTypes::PBAENuke: return true; default: return false; } } -uint8 Bot::GetDefaultSpellTypeMinManaLimit(uint16 spellType) { +uint8 Bot::GetDefaultSpellTypeMinManaLimit(uint16 spellType, uint8 stance) { return 0; } -uint8 Bot::GetDefaultSpellTypeMaxManaLimit(uint16 spellType) { +uint8 Bot::GetDefaultSpellTypeMaxManaLimit(uint16 spellType, uint8 stance) { switch (spellType) { case BotSpellTypes::InCombatBuff: if (GetClass() == Class::Shaman) { @@ -10550,7 +10563,7 @@ uint8 Bot::GetDefaultSpellTypeMaxManaLimit(uint16 spellType) { return 100; } -uint8 Bot::GetDefaultSpellTypeMinHPLimit(uint16 spellType) { +uint8 Bot::GetDefaultSpellTypeMinHPLimit(uint16 spellType, uint8 stance) { switch (spellType) { case BotSpellTypes::InCombatBuff: if (GetClass() == Class::Shaman) { @@ -10565,11 +10578,11 @@ uint8 Bot::GetDefaultSpellTypeMinHPLimit(uint16 spellType) { return 0; } -uint8 Bot::GetDefaultSpellTypeMaxHPLimit(uint16 spellType) { +uint8 Bot::GetDefaultSpellTypeMaxHPLimit(uint16 spellType, uint8 stance) { return 100; } -uint16 Bot::GetDefaultSpellTypeAEOrGroupTargetCount(uint16 spellType) { +uint16 Bot::GetDefaultSpellTypeAEOrGroupTargetCount(uint16 spellType, uint8 stance) { if (IsAEBotSpellType(spellType)) { return RuleI(Bots, MinTargetsForAESpell); } diff --git a/zone/bot.h b/zone/bot.h index 4e40f3939..553148ee5 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -458,7 +458,7 @@ public: void CopyBotSpellSettings(Bot* to); void ResetBotSpellSettings(); int GetBotBaseSetting(uint16 botSetting); - int GetDefaultBotBaseSetting(uint16 botSetting); + int GetDefaultBotBaseSetting(uint16 botSetting, uint8 stance = Stance::Balanced); void SetBotBaseSetting(uint16 botSetting, int settingValue); void LoadDefaultBotSettings(); void SetBotSpellRecastTimer(uint16 spellType, Mob* spelltar, bool preCast = false); @@ -467,18 +467,18 @@ public: std::string GetBotSpellCategoryName(uint8 setting_type); std::string GetBotSettingCategoryName(uint8 setting_type); - int GetDefaultSetting(uint16 settingCategory, uint16 settingType); - uint16 GetDefaultSpellTypePriority(uint16 spellType, uint8 priorityType, uint8 botClass); - uint16 GetDefaultSpellTypeIdlePriority(uint16 spellType, uint8 botClass); - uint16 GetDefaultSpellTypeEngagedPriority(uint16 spellType, uint8 botClass); - uint16 GetDefaultSpellTypePursuePriority(uint16 spellType, uint8 botClass); - uint16 GetDefaultSpellTypeResistLimit(uint16 spellType); - bool GetDefaultSpellTypeAggroCheck(uint16 spellType); - uint8 GetDefaultSpellTypeMinManaLimit(uint16 spellType); - uint8 GetDefaultSpellTypeMaxManaLimit(uint16 spellType); - uint8 GetDefaultSpellTypeMinHPLimit(uint16 spellType); - uint8 GetDefaultSpellTypeMaxHPLimit(uint16 spellType); - uint16 GetDefaultSpellTypeAEOrGroupTargetCount(uint16 spellType); + int GetDefaultSetting(uint16 settingCategory, uint16 settingType, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellTypePriority(uint16 spellType, uint8 priorityType, uint8 botClass, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellTypeIdlePriority(uint16 spellType, uint8 botClass, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellTypeEngagedPriority(uint16 spellType, uint8 botClass, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellTypePursuePriority(uint16 spellType, uint8 botClass, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellTypeResistLimit(uint16 spellType, uint8 stance = Stance::Balanced); + bool GetDefaultSpellTypeAggroCheck(uint16 spellType, uint8 stance = Stance::Balanced); + uint8 GetDefaultSpellTypeMinManaLimit(uint16 spellType, uint8 stance = Stance::Balanced); + uint8 GetDefaultSpellTypeMaxManaLimit(uint16 spellType, uint8 stance = Stance::Balanced); + uint8 GetDefaultSpellTypeMinHPLimit(uint16 spellType, uint8 stance = Stance::Balanced); + uint8 GetDefaultSpellTypeMaxHPLimit(uint16 spellType, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellTypeAEOrGroupTargetCount(uint16 spellType, uint8 stance = Stance::Balanced); int GetSetting(uint16 settingCategory, uint16 settingType); uint16 GetSpellTypePriority(uint16 spellType, uint8 priorityType); diff --git a/zone/bot_commands/bot.cpp b/zone/bot_commands/bot.cpp index e360d49ed..d867b4e67 100644 --- a/zone/bot_commands/bot.cpp +++ b/zone/bot_commands/bot.cpp @@ -1180,43 +1180,188 @@ void bot_command_stance(Client *c, const Seperator *sep) } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [current | value: 1-9] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); - c->Message( - Chat::White, + std::vector description = + { + "Change a bot's stance to control the way it behaves." + }; + + std::vector notes = + { + "- Changing a stance will reset all settings to match that stance type.", + "- Any changes made will only save to that stance for future use.", fmt::format( - "Value: {} ({}), {} ({}), {} ({})", - Stance::Passive, + "- {} (#{}) will tell Non-Warrior classes to taunt automatically.", + Stance::GetName(Stance::Aggressive), + Stance::Aggressive + ), + "
", + "Available stances:", + fmt::format( + "{} (#{}), {} (#{}), {} (#{}), {} (#{}), {} (#{}), {} (#{}), {} (#{})", Stance::GetName(Stance::Passive), - Stance::Balanced, + Stance::Passive, Stance::GetName(Stance::Balanced), + Stance::Balanced, + Stance::GetName(Stance::Efficient), + Stance::Efficient, + Stance::GetName(Stance::Aggressive), Stance::Aggressive, - Stance::GetName(Stance::Aggressive) - ).c_str() + Stance::GetName(Stance::Assist), + Stance::Assist, + Stance::GetName(Stance::Burn), + Stance::Burn, + Stance::GetName(Stance::AEBurn), + Stance::AEBurn + ), + "
", + fmt::format( + "- {} (#{}) [Default] - Overall balance and casts most spell types by default.", + Stance::GetName(Stance::Balanced), + Stance::Balanced + ), + fmt::format( + "- {} (#{}) - Idle. Does not cast or engage in combat.", + Stance::GetName(Stance::Passive), + Stance::Passive + ), + fmt::format( + "- {} (#{}) - More mana and aggro efficient (SKs will still cast hate line). Longer delays between detrimental spells, thresholds adjusted to cast less often.", + Stance::GetName(Stance::Efficient), + Stance::Efficient + ), + fmt::format( + "- {} (#{}) - Much more aggressive in their cast times and thresholds. More DPS, debuffs and slow but a higher risk of snagging aggro.", + Stance::GetName(Stance::Aggressive), + Stance::Aggressive + ), + fmt::format( + "- {} (#{}) - Support role. Most offensive spell types are disabled. Focused on heals, cures, CC, debuffs and slows.", + Stance::GetName(Stance::Assist), + Stance::Assist + ), + fmt::format( + "- {} (#{}) - Murder. Doesn't care about aggro, just wants to kill. DPS Machine.", + Stance::GetName(Stance::Burn), + Stance::Burn + ), + fmt::format( + "- {} (#{}) - Murder EVERYTHING. Doesn't care about aggro, casts AEs. Everything must die ASAP.", + Stance::GetName(Stance::AEBurn), + Stance::AEBurn + ) + }; + + std::vector example_format = + { + fmt::format( + "{} [current | value: {}-{}]", + sep->arg[0], + Stance::Passive, + Stance::AEBurn + ) + }; + std::vector examples_one = + { + "To set all bots to BurnAE:", + fmt::format( + "{} {} spawned {}", + sep->arg[0], + Stance::Aggressive, + Class::ShadowKnight + ) + }; + std::vector examples_two = + { + "To set all Shadowknights to Aggressive:", + fmt::format( + "{} {} byclass {}", + sep->arg[0], + Stance::Aggressive, + Class::ShadowKnight + ) + }; + std::vector examples_three = + { + "To check the current stances of all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + return; } const int ab_mask = ActionableBots::ABM_Type1; - std::string arg1 = sep->arg[1]; + bool currentCheck = false; int ab_arg = 1; - bool current_check = false; uint32 value = 0; + std::string arg1 = sep->arg[1]; + if (sep->IsNumber(1)) { ++ab_arg; value = atoi(sep->arg[1]); - if (value < 0 || value > 300) { - c->Message(Chat::White, "You must enter a value within the range of 0 - 300."); + if ( + value < Stance::Passive || + value > Stance::AEBurn || + value == Stance::Reactive || + value == Stance::Assist + ) { + c->Message( + Chat::Yellow, + fmt::format( + "You must choose a valid stance ID, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + return; } } else if (!arg1.compare("current")) { ++ab_arg; - current_check = true; + currentCheck = true; } else { - c->Message(Chat::White, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + return; } @@ -1232,26 +1377,56 @@ void bot_command_stance(Client *c, const Seperator *sep) return; } - if (!current_check && (value == Stance::Unknown || (value != Stance::Passive && value != Stance::Balanced && value != Stance::Aggressive))) { - c->Message(Chat::White, "A [current] argument or valid numeric [value] is required to use this command"); + Bot* first_found = nullptr; + int success_count = 0; + for (auto bot_iter : sbl) { + if (!first_found) { + first_found = bot_iter; + } + + if (currentCheck) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My current stance is {} ({}).'", + bot_iter->GetCleanName(), + Stance::GetName(bot_iter->GetBotStance()), + bot_iter->GetBotStance() + ).c_str() + ); + + continue; + } + + bot_iter->Save(); + bot_iter->SetBotStance(value); + bot_iter->LoadDefaultBotSettings(); + database.botdb.LoadBotSettings(bot_iter); + bot_iter->Save(); + ++success_count; + } + + if (currentCheck) { return; } - for (auto bot_iter : sbl) { - if (!bot_iter) - continue; - - if (!current_check) { - bot_iter->SetBotStance(value); - bot_iter->Save(); - } - - Bot::BotGroupSay( - bot_iter, + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, fmt::format( - "My current stance is {} ({}).", - Stance::GetName(bot_iter->GetBotStance()), - bot_iter->GetBotStance() + "{} says, 'I am now set to the stance [{}].'", + first_found->GetCleanName(), + Stance::GetName(value) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots are now set to the stance [{}].", + success_count, + Stance::GetName(value) ).c_str() ); } @@ -1278,7 +1453,7 @@ void bot_command_stop_melee_level(Client* c, const Seperator* sep) uint8 sml = RuleI(Bots, CasterStopMeleeLevel); bool sync_sml = false; bool reset_sml = false; - bool current_check = false; + bool currentCheck = false; if (sep->IsNumber(1)) { ab_arg = 2; @@ -1294,14 +1469,23 @@ void bot_command_stop_melee_level(Client* c, const Seperator* sep) } else if (!arg1.compare("current")) { ab_arg = 2; - current_check = true; + currentCheck = true; } else if (!strcasecmp(sep->arg[1], "reset")) { ab_arg = 2; reset_sml = true; } else { - c->Message(Chat::White, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + return; } @@ -1347,7 +1531,7 @@ void bot_command_stop_melee_level(Client* c, const Seperator* sep) sml = my_bot->GetDefaultBotBaseSetting(BotBaseSettings::StopMeleeLevel); } - if (current_check) { + if (currentCheck) { c->Message( Chat::White, fmt::format( @@ -1364,7 +1548,7 @@ void bot_command_stop_melee_level(Client* c, const Seperator* sep) } } - if (!current_check) { + if (!currentCheck) { if (success_count == 1 && first_found) { c->Message( Chat::White, diff --git a/zone/bot_commands/default_settings.cpp b/zone/bot_commands/default_settings.cpp index 1f7c854ca..87bb31b0f 100644 --- a/zone/bot_commands/default_settings.cpp +++ b/zone/bot_commands/default_settings.cpp @@ -199,24 +199,28 @@ void bot_command_default_settings(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; std::string output = ""; - for (auto my_bot : sbl) { + uint8 botStance = 2; + + for (auto myBot : sbl) { if (!first_found) { - first_found = my_bot; + first_found = myBot; } + botStance = myBot->GetBotStance(); + if (!strcasecmp(sep->arg[1], "misc")) { for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { - my_bot->SetBotBaseSetting(i, my_bot->GetDefaultBotBaseSetting(i)); + myBot->SetBotBaseSetting(i, myBot->GetDefaultBotBaseSetting(i, botStance)); output = "miscellanous settings"; } } else if (!strcasecmp(sep->arg[1], "holds")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellHold(spellType, my_bot->GetDefaultSpellHold(spellType)); + myBot->SetSpellHold(spellType, myBot->GetDefaultSpellHold(spellType, botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellHold(i, my_bot->GetDefaultSpellHold(i)); + myBot->SetSpellHold(i, myBot->GetDefaultSpellHold(i, botStance)); } } @@ -224,11 +228,11 @@ void bot_command_default_settings(Client* c, const Seperator* sep) } else if (!strcasecmp(sep->arg[1], "delays")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellDelay(spellType, my_bot->GetDefaultSpellDelay(spellType)); + myBot->SetSpellDelay(spellType, myBot->GetDefaultSpellDelay(spellType, botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellDelay(i, my_bot->GetDefaultSpellDelay(i)); + myBot->SetSpellDelay(i, myBot->GetDefaultSpellDelay(i, botStance)); } } @@ -236,11 +240,11 @@ void bot_command_default_settings(Client* c, const Seperator* sep) } else if (!strcasecmp(sep->arg[1], "minthresholds")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellMinThreshold(spellType, my_bot->GetDefaultSpellMinThreshold(spellType)); + myBot->SetSpellMinThreshold(spellType, myBot->GetDefaultSpellMinThreshold(spellType, botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellMinThreshold(i, my_bot->GetDefaultSpellMinThreshold(i)); + myBot->SetSpellMinThreshold(i, myBot->GetDefaultSpellMinThreshold(i, botStance)); } } @@ -248,11 +252,11 @@ void bot_command_default_settings(Client* c, const Seperator* sep) } else if (!strcasecmp(sep->arg[1], "maxthresholds")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellMaxThreshold(spellType, my_bot->GetDefaultSpellMaxThreshold(spellType)); + myBot->SetSpellMaxThreshold(spellType, myBot->GetDefaultSpellMaxThreshold(spellType, botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellMaxThreshold(i, my_bot->GetDefaultSpellMaxThreshold(i)); + myBot->SetSpellMaxThreshold(i, myBot->GetDefaultSpellMaxThreshold(i, botStance)); } } @@ -260,11 +264,11 @@ void bot_command_default_settings(Client* c, const Seperator* sep) } else if (!strcasecmp(sep->arg[1], "aggrochecks")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellTypeAggroCheck(spellType, my_bot->GetDefaultSpellTypeAggroCheck(spellType)); + myBot->SetSpellTypeAggroCheck(spellType, myBot->GetDefaultSpellTypeAggroCheck(spellType, botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellTypeAggroCheck(i, my_bot->GetDefaultSpellTypeAggroCheck(i)); + myBot->SetSpellTypeAggroCheck(i, myBot->GetDefaultSpellTypeAggroCheck(i, botStance)); } } @@ -272,11 +276,11 @@ void bot_command_default_settings(Client* c, const Seperator* sep) } else if (!strcasecmp(sep->arg[1], "minmanapct")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellTypeMinManaLimit(spellType, my_bot->GetDefaultSpellTypeMinManaLimit(spellType)); + myBot->SetSpellTypeMinManaLimit(spellType, myBot->GetDefaultSpellTypeMinManaLimit(spellType, botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellTypeMinManaLimit(i, my_bot->GetDefaultSpellTypeMinManaLimit(i)); + myBot->SetSpellTypeMinManaLimit(i, myBot->GetDefaultSpellTypeMinManaLimit(i, botStance)); } } @@ -284,11 +288,11 @@ void bot_command_default_settings(Client* c, const Seperator* sep) } else if (!strcasecmp(sep->arg[1], "maxmanapct")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellTypeMaxManaLimit(spellType, my_bot->GetDefaultSpellTypeMaxManaLimit(spellType)); + myBot->SetSpellTypeMaxManaLimit(spellType, myBot->GetDefaultSpellTypeMaxManaLimit(spellType, botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellTypeMaxManaLimit(i, my_bot->GetDefaultSpellTypeMaxManaLimit(i)); + myBot->SetSpellTypeMaxManaLimit(i, myBot->GetDefaultSpellTypeMaxManaLimit(i, botStance)); } } @@ -296,11 +300,11 @@ void bot_command_default_settings(Client* c, const Seperator* sep) } else if (!strcasecmp(sep->arg[1], "minhppct")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellTypeMinHPLimit(spellType, my_bot->GetDefaultSpellTypeMinHPLimit(spellType)); + myBot->SetSpellTypeMinHPLimit(spellType, myBot->GetDefaultSpellTypeMinHPLimit(spellType, botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellTypeMinHPLimit(i, my_bot->GetDefaultSpellTypeMinHPLimit(i)); + myBot->SetSpellTypeMinHPLimit(i, myBot->GetDefaultSpellTypeMinHPLimit(i, botStance)); } } @@ -308,11 +312,11 @@ void bot_command_default_settings(Client* c, const Seperator* sep) } else if (!strcasecmp(sep->arg[1], "maxhppct")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellTypeMaxHPLimit(spellType, my_bot->GetDefaultSpellTypeMaxHPLimit(spellType)); + myBot->SetSpellTypeMaxHPLimit(spellType, myBot->GetDefaultSpellTypeMaxHPLimit(spellType, botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellTypeMaxHPLimit(i, my_bot->GetDefaultSpellTypeMaxHPLimit(i)); + myBot->SetSpellTypeMaxHPLimit(i, myBot->GetDefaultSpellTypeMaxHPLimit(i, botStance)); } } @@ -320,11 +324,11 @@ void bot_command_default_settings(Client* c, const Seperator* sep) } else if (!strcasecmp(sep->arg[1], "idlepriority")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Idle, my_bot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Idle, my_bot->GetClass())); + myBot->SetSpellTypePriority(spellType, BotPriorityCategories::Idle, myBot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Idle, myBot->GetClass(), botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellTypePriority(i, BotPriorityCategories::Idle, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Idle, my_bot->GetClass())); + myBot->SetSpellTypePriority(i, BotPriorityCategories::Idle, myBot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Idle, myBot->GetClass(), botStance)); } } @@ -332,11 +336,11 @@ void bot_command_default_settings(Client* c, const Seperator* sep) } else if (!strcasecmp(sep->arg[1], "engagedpriority")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Engaged, my_bot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Engaged, my_bot->GetClass())); + myBot->SetSpellTypePriority(spellType, BotPriorityCategories::Engaged, myBot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Engaged, myBot->GetClass(), botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellTypePriority(i, BotPriorityCategories::Engaged, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Engaged, my_bot->GetClass())); + myBot->SetSpellTypePriority(i, BotPriorityCategories::Engaged, myBot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Engaged, myBot->GetClass(), botStance)); } } @@ -344,11 +348,11 @@ void bot_command_default_settings(Client* c, const Seperator* sep) } else if (!strcasecmp(sep->arg[1], "pursuepriority")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Pursue, my_bot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Pursue, my_bot->GetClass())); + myBot->SetSpellTypePriority(spellType, BotPriorityCategories::Pursue, myBot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Pursue, myBot->GetClass(), botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellTypePriority(i, BotPriorityCategories::Pursue, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Pursue, my_bot->GetClass())); + myBot->SetSpellTypePriority(i, BotPriorityCategories::Pursue, myBot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Pursue, myBot->GetClass(), botStance)); } } @@ -356,51 +360,51 @@ void bot_command_default_settings(Client* c, const Seperator* sep) } else if (!strcasecmp(sep->arg[1], "targetcounts")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellDelay(spellType, my_bot->GetDefaultSpellDelay(spellType)); + myBot->SetSpellDelay(spellType, myBot->GetDefaultSpellDelay(spellType, botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellTypeAEOrGroupTargetCount(i, my_bot->GetDefaultSpellTypeAEOrGroupTargetCount(i)); + myBot->SetSpellTypeAEOrGroupTargetCount(i, myBot->GetDefaultSpellTypeAEOrGroupTargetCount(i, botStance)); } } output = "ae/group count settings"; } else if (!strcasecmp(sep->arg[1], "spellsettings")) { - my_bot->ResetBotSpellSettings(); + myBot->ResetBotSpellSettings(); output = "^spellsettings"; } else if (!strcasecmp(sep->arg[1], "spelltypesettings")) { if (spellType != UINT16_MAX) { - my_bot->SetSpellHold(spellType, my_bot->GetDefaultSpellHold(spellType)); - my_bot->SetSpellDelay(spellType, my_bot->GetDefaultSpellDelay(spellType)); - my_bot->SetSpellMinThreshold(spellType, my_bot->GetDefaultSpellMinThreshold(spellType)); - my_bot->SetSpellMaxThreshold(spellType, my_bot->GetDefaultSpellMaxThreshold(spellType)); - my_bot->SetSpellTypeAggroCheck(spellType, my_bot->GetDefaultSpellTypeAggroCheck(spellType)); - my_bot->SetSpellTypeMinManaLimit(spellType, my_bot->GetDefaultSpellTypeMinManaLimit(spellType)); - my_bot->SetSpellTypeMaxManaLimit(spellType, my_bot->GetDefaultSpellTypeMaxManaLimit(spellType)); - my_bot->SetSpellTypeMinHPLimit(spellType, my_bot->GetDefaultSpellTypeMinHPLimit(spellType)); - my_bot->SetSpellTypeMaxHPLimit(spellType, my_bot->GetDefaultSpellTypeMaxHPLimit(spellType)); - my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Idle, my_bot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Idle, my_bot->GetClass())); - my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Engaged, my_bot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Engaged, my_bot->GetClass())); - my_bot->SetSpellTypePriority(spellType, BotPriorityCategories::Pursue, my_bot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Pursue, my_bot->GetClass())); - my_bot->SetSpellTypeAEOrGroupTargetCount(spellType, my_bot->GetDefaultSpellTypeAEOrGroupTargetCount(spellType)); + myBot->SetSpellHold(spellType, myBot->GetDefaultSpellHold(spellType, botStance)); + myBot->SetSpellDelay(spellType, myBot->GetDefaultSpellDelay(spellType, botStance)); + myBot->SetSpellMinThreshold(spellType, myBot->GetDefaultSpellMinThreshold(spellType, botStance)); + myBot->SetSpellMaxThreshold(spellType, myBot->GetDefaultSpellMaxThreshold(spellType, botStance)); + myBot->SetSpellTypeAggroCheck(spellType, myBot->GetDefaultSpellTypeAggroCheck(spellType, botStance)); + myBot->SetSpellTypeMinManaLimit(spellType, myBot->GetDefaultSpellTypeMinManaLimit(spellType, botStance)); + myBot->SetSpellTypeMaxManaLimit(spellType, myBot->GetDefaultSpellTypeMaxManaLimit(spellType, botStance)); + myBot->SetSpellTypeMinHPLimit(spellType, myBot->GetDefaultSpellTypeMinHPLimit(spellType, botStance)); + myBot->SetSpellTypeMaxHPLimit(spellType, myBot->GetDefaultSpellTypeMaxHPLimit(spellType, botStance)); + myBot->SetSpellTypePriority(spellType, BotPriorityCategories::Idle, myBot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Idle, myBot->GetClass(), botStance)); + myBot->SetSpellTypePriority(spellType, BotPriorityCategories::Engaged, myBot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Engaged, myBot->GetClass(), botStance)); + myBot->SetSpellTypePriority(spellType, BotPriorityCategories::Pursue, myBot->GetDefaultSpellTypePriority(spellType, BotPriorityCategories::Pursue, myBot->GetClass(), botStance)); + myBot->SetSpellTypeAEOrGroupTargetCount(spellType, myBot->GetDefaultSpellTypeAEOrGroupTargetCount(spellType, botStance)); } else { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellHold(i, my_bot->GetDefaultSpellHold(i)); - my_bot->SetSpellDelay(i, my_bot->GetDefaultSpellDelay(i)); - my_bot->SetSpellMinThreshold(i, my_bot->GetDefaultSpellMinThreshold(i)); - my_bot->SetSpellMaxThreshold(i, my_bot->GetDefaultSpellMaxThreshold(i)); - my_bot->SetSpellTypeAggroCheck(i, my_bot->GetDefaultSpellTypeAggroCheck(i)); - my_bot->SetSpellTypeMinManaLimit(i, my_bot->GetDefaultSpellTypeMinManaLimit(i)); - my_bot->SetSpellTypeMaxManaLimit(i, my_bot->GetDefaultSpellTypeMaxManaLimit(i)); - my_bot->SetSpellTypeMinHPLimit(i, my_bot->GetDefaultSpellTypeMinHPLimit(i)); - my_bot->SetSpellTypeMaxHPLimit(i, my_bot->GetDefaultSpellTypeMaxHPLimit(i)); - my_bot->SetSpellTypePriority(i, BotPriorityCategories::Idle, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Idle, my_bot->GetClass())); - my_bot->SetSpellTypePriority(i, BotPriorityCategories::Engaged, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Engaged, my_bot->GetClass())); - my_bot->SetSpellTypePriority(i, BotPriorityCategories::Pursue, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Pursue, my_bot->GetClass())); - my_bot->SetSpellTypeAEOrGroupTargetCount(i, my_bot->GetDefaultSpellTypeAEOrGroupTargetCount(i)); + myBot->SetSpellHold(i, myBot->GetDefaultSpellHold(i, botStance)); + myBot->SetSpellDelay(i, myBot->GetDefaultSpellDelay(i, botStance)); + myBot->SetSpellMinThreshold(i, myBot->GetDefaultSpellMinThreshold(i, botStance)); + myBot->SetSpellMaxThreshold(i, myBot->GetDefaultSpellMaxThreshold(i, botStance)); + myBot->SetSpellTypeAggroCheck(i, myBot->GetDefaultSpellTypeAggroCheck(i, botStance)); + myBot->SetSpellTypeMinManaLimit(i, myBot->GetDefaultSpellTypeMinManaLimit(i, botStance)); + myBot->SetSpellTypeMaxManaLimit(i, myBot->GetDefaultSpellTypeMaxManaLimit(i, botStance)); + myBot->SetSpellTypeMinHPLimit(i, myBot->GetDefaultSpellTypeMinHPLimit(i, botStance)); + myBot->SetSpellTypeMaxHPLimit(i, myBot->GetDefaultSpellTypeMaxHPLimit(i, botStance)); + myBot->SetSpellTypePriority(i, BotPriorityCategories::Idle, myBot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Idle, myBot->GetClass(), botStance)); + myBot->SetSpellTypePriority(i, BotPriorityCategories::Engaged, myBot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Engaged, myBot->GetClass(), botStance)); + myBot->SetSpellTypePriority(i, BotPriorityCategories::Pursue, myBot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Pursue, myBot->GetClass(), botStance)); + myBot->SetSpellTypeAEOrGroupTargetCount(i, myBot->GetDefaultSpellTypeAEOrGroupTargetCount(i, botStance)); } } @@ -408,26 +412,26 @@ void bot_command_default_settings(Client* c, const Seperator* sep) } else if (!strcasecmp(sep->arg[1], "all")) { for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { - my_bot->SetBotBaseSetting(i, my_bot->GetDefaultBotBaseSetting(i)); + myBot->SetBotBaseSetting(i, myBot->GetDefaultBotBaseSetting(i, botStance)); } for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - my_bot->SetSpellHold(i, my_bot->GetDefaultSpellHold(i)); - my_bot->SetSpellDelay(i, my_bot->GetDefaultSpellDelay(i)); - my_bot->SetSpellMinThreshold(i, my_bot->GetDefaultSpellMinThreshold(i)); - my_bot->SetSpellMaxThreshold(i, my_bot->GetDefaultSpellMaxThreshold(i)); - my_bot->SetSpellTypeAggroCheck(i, my_bot->GetDefaultSpellTypeAggroCheck(i)); - my_bot->SetSpellTypeMinManaLimit(i, my_bot->GetDefaultSpellTypeMinManaLimit(i)); - my_bot->SetSpellTypeMaxManaLimit(i, my_bot->GetDefaultSpellTypeMaxManaLimit(i)); - my_bot->SetSpellTypeMinHPLimit(i, my_bot->GetDefaultSpellTypeMinHPLimit(i)); - my_bot->SetSpellTypeMaxHPLimit(i, my_bot->GetDefaultSpellTypeMaxHPLimit(i)); - my_bot->SetSpellTypePriority(i, BotPriorityCategories::Idle, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Idle, my_bot->GetClass())); - my_bot->SetSpellTypePriority(i, BotPriorityCategories::Engaged, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Engaged, my_bot->GetClass())); - my_bot->SetSpellTypePriority(i, BotPriorityCategories::Pursue, my_bot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Pursue, my_bot->GetClass())); - my_bot->SetSpellTypeAEOrGroupTargetCount(i, my_bot->GetDefaultSpellTypeAEOrGroupTargetCount(i)); + myBot->SetSpellHold(i, myBot->GetDefaultSpellHold(i, botStance)); + myBot->SetSpellDelay(i, myBot->GetDefaultSpellDelay(i, botStance)); + myBot->SetSpellMinThreshold(i, myBot->GetDefaultSpellMinThreshold(i, botStance)); + myBot->SetSpellMaxThreshold(i, myBot->GetDefaultSpellMaxThreshold(i, botStance)); + myBot->SetSpellTypeAggroCheck(i, myBot->GetDefaultSpellTypeAggroCheck(i, botStance)); + myBot->SetSpellTypeMinManaLimit(i, myBot->GetDefaultSpellTypeMinManaLimit(i, botStance)); + myBot->SetSpellTypeMaxManaLimit(i, myBot->GetDefaultSpellTypeMaxManaLimit(i, botStance)); + myBot->SetSpellTypeMinHPLimit(i, myBot->GetDefaultSpellTypeMinHPLimit(i, botStance)); + myBot->SetSpellTypeMaxHPLimit(i, myBot->GetDefaultSpellTypeMaxHPLimit(i, botStance)); + myBot->SetSpellTypePriority(i, BotPriorityCategories::Idle, myBot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Idle, myBot->GetClass(), botStance)); + myBot->SetSpellTypePriority(i, BotPriorityCategories::Engaged, myBot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Engaged, myBot->GetClass(), botStance)); + myBot->SetSpellTypePriority(i, BotPriorityCategories::Pursue, myBot->GetDefaultSpellTypePriority(i, BotPriorityCategories::Pursue, myBot->GetClass(), botStance)); + myBot->SetSpellTypeAEOrGroupTargetCount(i, myBot->GetDefaultSpellTypeAEOrGroupTargetCount(i, botStance)); }; - my_bot->ResetBotSpellSettings(); + myBot->ResetBotSpellSettings(); output = "settings"; diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 20715d0d7..e60a17961 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -2211,14 +2211,15 @@ bool BotDatabase::LoadBotSettings(Mob* m) } uint32 mobID = (m->IsClient() ? m->CastToClient()->CharacterID() : m->CastToBot()->GetBotID()); + uint8 stanceID = (m->IsBot() ? m->CastToBot()->GetBotStance() : 0); std::string query = ""; if (m->IsClient()) { - query = fmt::format("`char_id` = {}", mobID); + query = fmt::format("`char_id` = {} AND `stance` = {}", mobID, stanceID); } else { - query = fmt::format("`bot_id` = {}", mobID); + query = fmt::format("`bot_id` = {} AND `stance` = {}", mobID, stanceID); } const auto& l = BotSettingsRepository::GetWhere(database, query); @@ -2228,12 +2229,24 @@ bool BotDatabase::LoadBotSettings(Mob* m) } for (const auto& e : l) { - LogBotSettings("[{}] says, 'Loading {} [{}] - setting to [{}]." - , m->GetCleanName() - , (e.setting_type == BotSettingCategories::BaseSetting ? m->CastToBot()->GetBotSettingCategoryName(e.setting_id) : m->CastToBot()->GetBotSpellCategoryName(e.setting_id)) - , e.setting_id - , e.value - ); //deleteme + if (e.setting_type == BotSettingCategories::BaseSetting) { + LogBotSettings("[{}] says, 'Loading {} [{}] - setting to [{}]." + , m->GetCleanName() + , m->CastToBot()->GetBotSettingCategoryName(e.setting_type) + , e.setting_type + , e.value + ); //deleteme + } + else { + LogBotSettings("[{}] says, 'Loading {} [{}], {} [{}] - setting to [{}]." + , m->GetCleanName() + , m->CastToBot()->GetBotSpellCategoryName(e.setting_type) + , e.setting_type + , m->GetSpellTypeNameByID(e.setting_id) + , e.setting_id + , e.value + ); //deleteme + } m->SetBotSetting(e.setting_type, e.setting_id, e.value); } @@ -2251,16 +2264,17 @@ bool BotDatabase::SaveBotSettings(Mob* m) return false; } - uint32 botID = (m->IsClient() ? 0 : m->CastToBot()->GetBotID()); + uint32 botID = (m->IsBot() ? m->CastToBot()->GetBotID() : 0); uint32 charID = (m->IsClient() ? m->CastToClient()->CharacterID() : 0); + uint8 stanceID = (m->IsBot() ? m->CastToBot()->GetBotStance() : 0); std::string query = ""; if (m->IsClient()) { - query = fmt::format("`char_id` = {}", charID); + query = fmt::format("`char_id` = {} AND `stance` = {}", charID, stanceID); } else { - query = fmt::format("`bot_id` = {}", botID); + query = fmt::format("`bot_id` = {} AND `stance` = {}", botID, stanceID); } BotSettingsRepository::DeleteWhere(database, query); @@ -2268,16 +2282,19 @@ bool BotDatabase::SaveBotSettings(Mob* m) std::vector v; if (m->IsBot()) { + uint8 botStance = m->CastToBot()->GetBotStance(); + for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { - if (m->CastToBot()->GetBotBaseSetting(i) != m->CastToBot()->GetDefaultBotBaseSetting(i)) { + if (m->CastToBot()->GetBotBaseSetting(i) != m->CastToBot()->GetDefaultBotBaseSetting(i, botStance)) { auto e = BotSettingsRepository::BotSettings{ - .char_id = charID, - .bot_id = botID, - .setting_id = static_cast(i), - .setting_type = static_cast(BotSettingCategories::BaseSetting), - .value = static_cast(m->CastToBot()->GetBotBaseSetting(i)), - .category_name = m->CastToBot()->GetBotSpellCategoryName(BotSettingCategories::BaseSetting), - .setting_name = m->CastToBot()->GetBotSettingCategoryName(i) + .char_id = charID, + .bot_id = botID, + .stance = stanceID, + .setting_id = static_cast(i), + .setting_type = static_cast(BotSettingCategories::BaseSetting), + .value = static_cast(m->CastToBot()->GetBotBaseSetting(i)), + .category_name = m->CastToBot()->GetBotSpellCategoryName(BotSettingCategories::BaseSetting), + .setting_name = m->CastToBot()->GetBotSettingCategoryName(i) }; v.emplace_back(e); @@ -2288,10 +2305,11 @@ bool BotDatabase::SaveBotSettings(Mob* m) for (uint16 i = BotSettingCategories::START_NO_BASE; i <= BotSettingCategories::END; ++i) { for (uint16 x = BotSpellTypes::START; x <= BotSpellTypes::END; ++x) { - if (m->CastToBot()->GetSetting(i, x) != m->CastToBot()->GetDefaultSetting(i, x)) { + if (m->CastToBot()->GetSetting(i, x) != m->CastToBot()->GetDefaultSetting(i, x, botStance)) { auto e = BotSettingsRepository::BotSettings{ .char_id = charID, .bot_id = botID, + .stance = stanceID, .setting_id = static_cast(x), .setting_type = static_cast(i), .value = m->CastToBot()->GetSetting(i, x), @@ -2301,7 +2319,7 @@ bool BotDatabase::SaveBotSettings(Mob* m) v.emplace_back(e); - LogBotSettings("{} says, 'Saving {} {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), m->CastToBot()->GetBotSpellCategoryName(i), m->GetSpellTypeNameByID(x), x, e.value, m->CastToBot()->GetDefaultSetting(i, x)); //deleteme + LogBotSettings("{} says, 'Saving {} {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), m->CastToBot()->GetBotSpellCategoryName(i), m->GetSpellTypeNameByID(x), x, e.value, m->CastToBot()->GetDefaultSetting(i, x, botStance)); //deleteme } } } @@ -2312,6 +2330,7 @@ bool BotDatabase::SaveBotSettings(Mob* m) auto e = BotSettingsRepository::BotSettings{ .char_id = charID, .bot_id = botID, + .stance = stanceID, .setting_id = static_cast(BotBaseSettings::IllusionBlock), .setting_type = static_cast(BotSettingCategories::BaseSetting), .value = m->GetIllusionBlock(), @@ -2331,6 +2350,7 @@ bool BotDatabase::SaveBotSettings(Mob* m) auto e = BotSettingsRepository::BotSettings{ .char_id = charID, .bot_id = botID, + .stance = stanceID, .setting_id = static_cast(x), .setting_type = static_cast(i), .value = m->CastToClient()->GetBotSetting(i, x), diff --git a/zone/client.cpp b/zone/client.cpp index a5bc2f387..a03e6f931 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -13080,6 +13080,8 @@ void Client::ClientToNpcAggroProcess() } void Client::LoadDefaultBotSettings() { + _spellSettings.clear(); + // Only illusion block supported currently SetBotSetting(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock, GetDefaultBotSettings(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock)); LogBotSettingsDetail("{} says, 'Setting default {} [{}] to [{}]'", GetCleanName(), CastToBot()->GetBotSettingCategoryName(BotBaseSettings::IllusionBlock), BotBaseSettings::IllusionBlock, GetDefaultBotSettings(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock)); //deleteme @@ -13132,21 +13134,21 @@ int Client::GetBotSetting(uint8 settingType, uint16 botSetting) { void Client::SetBotSetting(uint8 settingType, uint16 botSetting, uint32 settingValue) { switch (settingType) { - case BotSettingCategories::BaseSetting: - SetBaseSetting(botSetting, settingValue); - break; - case BotSettingCategories::SpellHold: - SetSpellHold(botSetting, settingValue); - break; - case BotSettingCategories::SpellDelay: - SetSpellDelay(botSetting, settingValue); - break; - case BotSettingCategories::SpellMinThreshold: - SetSpellMinThreshold(botSetting, settingValue); - break; - case BotSettingCategories::SpellMaxThreshold: - SetSpellMaxThreshold(botSetting, settingValue); - break; + case BotSettingCategories::BaseSetting: + SetBaseSetting(botSetting, settingValue); + break; + case BotSettingCategories::SpellHold: + SetSpellHold(botSetting, settingValue); + break; + case BotSettingCategories::SpellDelay: + SetSpellDelay(botSetting, settingValue); + break; + case BotSettingCategories::SpellMinThreshold: + SetSpellMinThreshold(botSetting, settingValue); + break; + case BotSettingCategories::SpellMaxThreshold: + SetSpellMaxThreshold(botSetting, settingValue); + break; } } diff --git a/zone/mob.cpp b/zone/mob.cpp index fc255188d..d472eb7f7 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -9063,18 +9063,30 @@ std::string Mob::GetSpellTypeShortNameByID(uint16 spellType) { return spellTypeName; } -bool Mob::GetDefaultSpellHold(uint16 spellType) { +bool Mob::GetDefaultSpellHold(uint16 spellType, uint8 stance) { switch (spellType) { + case BotSpellTypes::Nuke: + case BotSpellTypes::DOT: + case BotSpellTypes::Stun: + switch (stance) { + case Stance::Assist: + return true; + default: + return false; + } case BotSpellTypes::AENukes: case BotSpellTypes::AERains: - case BotSpellTypes::AEMez: case BotSpellTypes::AEStun: - case BotSpellTypes::AEDebuff: - case BotSpellTypes::AESlow: - case BotSpellTypes::AESnare: case BotSpellTypes::AEDoT: case BotSpellTypes::AELifetap: case BotSpellTypes::PBAENuke: + switch (stance) { + case Stance::AEBurn: + return false; + default: + return true; + } + case BotSpellTypes::AESnare: case BotSpellTypes::AERoot: case BotSpellTypes::Root: case BotSpellTypes::AEDispel: @@ -9082,28 +9094,46 @@ bool Mob::GetDefaultSpellHold(uint16 spellType) { case BotSpellTypes::AEFear: case BotSpellTypes::Fear: return true; - case BotSpellTypes::Snare: - if (GetClass() == Class::Wizard) { - return true; + case BotSpellTypes::AEMez: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::AESlow: + case BotSpellTypes::HateRedux: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + return true; + default: + return false; } - else { - return false; + case BotSpellTypes::Snare: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Assist: + return true; + default: + if (GetClass() == Class::Wizard) { + return true; + } + else { + return false; + } } case BotSpellTypes::InCombatBuffSong: case BotSpellTypes::OutOfCombatBuffSong: case BotSpellTypes::PreCombatBuffSong: if (GetClass() == Class::Bard) { - return true; + return false; } else { - return false; + return true; } default: return false; } } -uint16 Mob::GetDefaultSpellDelay(uint16 spellType) { +uint16 Mob::GetDefaultSpellDelay(uint16 spellType, uint8 stance) { switch (spellType) { case BotSpellTypes::VeryFastHeals: case BotSpellTypes::PetVeryFastHeals: @@ -9111,12 +9141,31 @@ uint16 Mob::GetDefaultSpellDelay(uint16 spellType) { case BotSpellTypes::FastHeals: case BotSpellTypes::PetFastHeals: return 2500; - case BotSpellTypes::AEDoT: - case BotSpellTypes::DOT: case BotSpellTypes::GroupHeals: case BotSpellTypes::RegularHeal: case BotSpellTypes::PetRegularHeals: return 4000; + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::PetCompleteHeals: + return 8000; + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetHoTHeals: + return 22000; + case BotSpellTypes::AEDoT: + case BotSpellTypes::DOT: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + return 1; + case Stance::Aggressive: + return 2000; + case Stance::Efficient: + return 8000; + default: + return 4000; + } case BotSpellTypes::AENukes: case BotSpellTypes::AERains: case BotSpellTypes::PBAENuke: @@ -9129,43 +9178,68 @@ uint16 Mob::GetDefaultSpellDelay(uint16 spellType) { case BotSpellTypes::Slow: case BotSpellTypes::AEStun: case BotSpellTypes::Stun: - return 6000; + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + return 1; + case Stance::Aggressive: + return 3000; + case Stance::Efficient: + return 10000; + default: + return 6000; + } case BotSpellTypes::AERoot: case BotSpellTypes::Root: - case BotSpellTypes::CompleteHeal: - case BotSpellTypes::GroupCompleteHeals: - case BotSpellTypes::PetCompleteHeals: - return 8000; + return 8000; case BotSpellTypes::Fear: case BotSpellTypes::AEFear: return 15000; - case BotSpellTypes::GroupHoTHeals: - case BotSpellTypes::HoTHeals: - case BotSpellTypes::PetHoTHeals: - return 22000; default: return 1; } } -uint8 Mob::GetDefaultSpellMinThreshold(uint16 spellType) { +uint8 Mob::GetDefaultSpellMinThreshold(uint16 spellType, uint8 stance) { switch (spellType) { - case BotSpellTypes::AENukes: - case BotSpellTypes::AERains: - case BotSpellTypes::PBAENuke: - case BotSpellTypes::Nuke: case BotSpellTypes::AEDebuff: case BotSpellTypes::Debuff: case BotSpellTypes::AEDispel: case BotSpellTypes::Dispel: case BotSpellTypes::AESlow: case BotSpellTypes::Slow: - return 20; + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 0; + default: + return 20; + } + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Nuke: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 0; + default: + return 5; + } case BotSpellTypes::AEDoT: case BotSpellTypes::DOT: - return 35; - case BotSpellTypes::Charm: - return 50; + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 0; + case Stance::Efficient: + return 40; + default: + return 25; + } case BotSpellTypes::Mez: case BotSpellTypes::AEMez: return 85; @@ -9174,25 +9248,60 @@ uint8 Mob::GetDefaultSpellMinThreshold(uint16 spellType) { } } -uint8 Mob::GetDefaultSpellMaxThreshold(uint16 spellType) { +uint8 Mob::GetDefaultSpellMaxThreshold(uint16 spellType, uint8 stance) { switch (spellType) { case BotSpellTypes::Escape: case BotSpellTypes::VeryFastHeals: case BotSpellTypes::PetVeryFastHeals: - return 25; + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 40; + case Stance::Efficient: + default: + return 25; + } case BotSpellTypes::AELifetap: case BotSpellTypes::Lifetap: case BotSpellTypes::FastHeals: case BotSpellTypes::PetFastHeals: - return 40; + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 55; + case Stance::Efficient: + return 35; + default: + return 40; + } case BotSpellTypes::GroupHeals: case BotSpellTypes::RegularHeal: case BotSpellTypes::PetRegularHeals: - return 60; + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 70; + case Stance::Efficient: + return 50; + default: + return 60; + } case BotSpellTypes::CompleteHeal: case BotSpellTypes::GroupCompleteHeals: case BotSpellTypes::PetCompleteHeals: - return 80; + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 90; + case Stance::Efficient: + return 65; + default: + return 80; + } case BotSpellTypes::AENukes: case BotSpellTypes::AERains: case BotSpellTypes::PBAENuke: @@ -9212,7 +9321,17 @@ uint8 Mob::GetDefaultSpellMaxThreshold(uint16 spellType) { case BotSpellTypes::AEDebuff: case BotSpellTypes::Debuff: case BotSpellTypes::Stun: - return 99; + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + return 100; + case Stance::Aggressive: + return 100; + case Stance::Efficient: + return 90; + default: + return 99; + } case BotSpellTypes::Buff: case BotSpellTypes::Charm: case BotSpellTypes::Cure: @@ -9237,7 +9356,16 @@ uint8 Mob::GetDefaultSpellMaxThreshold(uint16 spellType) { return 60; } else { - return 90; + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 95; + case Stance::Efficient: + return 80; + default: + return 90; + } } default: return 100; diff --git a/zone/mob.h b/zone/mob.h index 6da29429f..662798af5 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -437,10 +437,10 @@ public: std::string GetSpellTypeNameByID(uint16 spellType); std::string GetSpellTypeShortNameByID(uint16 spellType); - bool GetDefaultSpellHold(uint16 spellType); - uint16 GetDefaultSpellDelay(uint16 spellType); - uint8 GetDefaultSpellMinThreshold(uint16 spellType); - uint8 GetDefaultSpellMaxThreshold(uint16 spellType); + bool GetDefaultSpellHold(uint16 spellType, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellDelay(uint16 spellType, uint8 stance = Stance::Balanced); + uint8 GetDefaultSpellMinThreshold(uint16 spellType, uint8 stance = Stance::Balanced); + uint8 GetDefaultSpellMaxThreshold(uint16 spellType, uint8 stance = Stance::Balanced); inline bool GetSpellHold(uint16 spellType) const { return _spellSettings[spellType].hold; } void SetSpellHold(uint16 spellType, bool holdStatus); From 2dfa1681c46f878130951de5b99522edc810c963 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 6 Nov 2024 07:30:24 -0600 Subject: [PATCH 10/97] Make rogue/monk evade logic more accurate to players --- zone/bot.cpp | 68 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index e7d4737d7..79b7a02f0 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2607,8 +2607,16 @@ bool Bot::TryFacingTarget(Mob* tar) { bool Bot::TryEvade(Mob* tar) { - if (HasTargetReflection() && !tar->IsFeared() && !tar->IsStunned()) { - if (GetClass() == Class::Rogue && !GetSpellHold(BotSpellTypes::Escape)) { + if (GetSpellHold(BotSpellTypes::Escape)) { + return false; + } + + switch (GetClass()) { + case Class::Rogue: { + if (GetSkill(EQ::skills::SkillHide) == 0) { + return false; + } + if (m_rogue_evade_timer.Check(false)) { int timer_duration = (HideReuseTime - GetSkillReuseTime(EQ::skills::SkillHide)) * 1000; @@ -2617,18 +2625,47 @@ bool Bot::TryEvade(Mob* tar) { } m_rogue_evade_timer.Start(timer_duration); - BotGroupSay(this, "Attempting to evade %s", tar->GetCleanName()); if (zone->random.Int(0, 260) < (int)GetSkill(EQ::skills::SkillHide)) { + + } + + float hidechance = ((GetSkill(EQ::skills::SkillHide) / 250.0f) + .25) * 100; + float random = zone->random.Real(0, 100); + + if (random < hidechance) { //SendAppearancePacket(AT_Invis, Invisibility::Invisible); + + if (spellbonuses.ShroudofStealth || aabonuses.ShroudofStealth || itembonuses.ShroudofStealth) { + improved_hidden = true; + hidden = true; + } + else { + hidden = true; + } + } + + if (zone->random.Int(0, 260) < (int)GetSkill(EQ::skills::SkillHide)) { + BotGroupSay(this, "I have momentarily ducked away from the main combat."); RogueEvade(tar); } + else { + BotGroupSay(this, "My attempts at ducking clear of combat fail."); + } //SendAppearancePacket(AT_Invis, Invisibility::Visible); + hidden = false; + return true; } + + break; } - else if (GetClass() == Class::Monk && GetLevel() >= 17 && !GetSpellHold(BotSpellTypes::Escape)) { + case Class::Monk: { + if (GetSkill(EQ::skills::SkillFeignDeath) == 0) { + return false; + } + if (m_monk_evade_timer.Check(false)) { int timer_duration = (FeignDeathReuseTime - GetSkillReuseTime(EQ::skills::SkillFeignDeath)) * 1000; @@ -2637,13 +2674,26 @@ bool Bot::TryEvade(Mob* tar) { } m_monk_evade_timer.Start(timer_duration); - BotGroupSay(this, "Attempting to evade %s", tar->GetCleanName()); - if (zone->random.Int(0, 260) < (int)GetSkill(EQ::skills::SkillFeignDeath)) { + uint16 primfeign = GetSkill(EQ::skills::SkillFeignDeath); + uint16 secfeign = GetSkill(EQ::skills::SkillFeignDeath); + if (primfeign > 100) { + primfeign = 100; + secfeign = secfeign - 100; + secfeign = secfeign / 2; + } + else + secfeign = 0; + + uint16 totalfeign = primfeign + secfeign; + + if (zone->random.Real(0, 160) > totalfeign) { //SendAppearancePacket(AT_Anim, ANIM_DEATH); - entity_list.MessageCloseString(this, false, 200, 10, STRING_FEIGNFAILED, GetName()); + BotGroupSay(this, "I have fallen to the ground."); + SetFeigned(false); } else { + BotGroupSay(this, "I have successfully feigned my death."); SetFeigned(true); //SendAppearancePacket(AT_Anim, ANIM_DEATH); } @@ -7110,6 +7160,10 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl continue; } + if (caster == tar) { + continue; + } + uint8 chanceToCast = caster->IsEngaged() ? caster->GetChanceToCastBySpellType(spellType) : 100; if (!caster->PrecastChecks(tar, spellType)) { From 775511c935104cb3e73dc66b069432f7d232777e Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 6 Nov 2024 07:31:48 -0600 Subject: [PATCH 11/97] more target validation for bots to prevent pets from getting hit with AEs and pets trying to attack invalid targets --- zone/aggro.cpp | 21 +++++++++++++++++++-- zone/bot.cpp | 7 +++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 1a8c7b929..4f1d65831 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -745,10 +745,27 @@ bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack) // can't damage own pet (applies to everthing) Mob *target_owner = target->GetOwner(); Mob *our_owner = GetOwner(); - if(target_owner && target_owner == this) + Mob* target_ultimate_owner = target->GetUltimateOwner(); + Mob* our_ultimate_owner = GetUltimateOwner(); + + if (target_owner && target_owner == this) { return false; - else if(our_owner && our_owner == target) + } + else if ( + IsBot() && target_ultimate_owner && + ( + (target_ultimate_owner == our_ultimate_owner) || + (target_ultimate_owner->IsOfClientBot()) + ) + ) { return false; + } + else if (our_owner && our_owner == target) { + return false; + } + else if (IsBot() && our_ultimate_owner && our_ultimate_owner == target) { + return false; + } // invalidate for swarm pets for later on if their owner is a corpse if (IsNPC() && CastToNPC()->GetSwarmInfo() && our_owner && diff --git a/zone/bot.cpp b/zone/bot.cpp index 79b7a02f0..0737d0961 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2113,6 +2113,13 @@ void Bot::AI_Process() // TARGET VALIDATION if (!IsValidTarget(bot_owner, leash_owner, lo_distance, leash_distance, tar, tar_distance)) { + if (HasPet()) { + if (tar && GetPet()->GetTarget() && GetPet()->GetTarget() == tar) { + GetPet()->WipeHateList(); + GetPet()->SetTarget(nullptr); + } + } + return; } From c22d687d72a5ee6587ef40b64ca89ca733deb5da Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 7 Nov 2024 16:33:59 -0600 Subject: [PATCH 12/97] misc command and rule cleanup --- common/ruletypes.h | 8 ++++---- zone/bot_commands/follow.cpp | 2 +- zone/bot_commands/illusion_block.cpp | 7 ------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index fcbe55874..526373e9d 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -805,9 +805,9 @@ RULE_INT(Bots, PercentChanceToCastCure, 75, "The chance for a bot to attempt to RULE_INT(Bots, PercentChanceToCastHateRedux, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") RULE_INT(Bots, PercentChanceToCastFear, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") RULE_INT(Bots, PercentChanceToCastOtherType, 90, "The chance for a bot to attempt to cast the remaining spell types in combat. Default 0-%.") -RULE_INT(Bots, MinDelayBetweenInCombatCastAttempts, 250, "The minimum delay in milliseconds between cast attempts while in-combat. This is rolled between the min and max. Default 500ms.") +RULE_INT(Bots, MinDelayBetweenInCombatCastAttempts, 250, "The minimum delay in milliseconds between cast attempts while in-combat. This is rolled between the min and max. Default 250ms.") RULE_INT(Bots, MaxDelayBetweenInCombatCastAttempts, 2000, "The maximum delay in milliseconds between cast attempts while in-combat. This is rolled between the min and max. Default 2000ms.") -RULE_INT(Bots, MinDelayBetweenOutCombatCastAttempts, 125, "The minimum delay in milliseconds between cast attempts while out-of-combat. This is rolled between the min and max. Default 200ms.") +RULE_INT(Bots, MinDelayBetweenOutCombatCastAttempts, 125, "The minimum delay in milliseconds between cast attempts while out-of-combat. This is rolled between the min and max. Default 125ms.") RULE_INT(Bots, MaxDelayBetweenOutCombatCastAttempts, 500, "The maximum delay in milliseconds between cast attempts while out-of-combat. This is rolled between the min and max. Default 500ms.") RULE_INT(Bots, MezChance, 35, "35 Default. Chance for a bot to attempt to Mez a target after validating it is eligible.") RULE_INT(Bots, AEMezChance, 35, "35 Default. Chance for a bot to attempt to AE Mez targets after validating they are eligible.") @@ -852,13 +852,13 @@ RULE_BOOL(Bots, PreventBotSpawnOnFD, true, "True Default. If true, players will RULE_BOOL(Bots, PreventBotSpawnOnEngaged, true, "True Default. If true, players will not be able to spawn bots while you, your group or raid are engaged.") RULE_BOOL(Bots, PreventBotCampOnEngaged, true, "True Default. If true, players will not be able to camp bots while you, your group or raid are engaged.") RULE_BOOL(Bots, CopySettingsOwnBotsOnly, true, "Determines whether a bot you are copying settings from must be a bot you own or not, default true.") -RULE_BOOL(Bots, AllowCopySettingsAnon, true, "If player's are allowed to copy settings of bots owned by anonymous players.") +RULE_BOOL(Bots, AllowCopySettingsAnon, false, "If player's are allowed to copy settings of bots owned by anonymous players.") RULE_BOOL(Bots, AllowCharmedPetBuffs, true, "Whether or not bots are allowed to cast buff charmed pets, default true.") RULE_BOOL(Bots, AllowCharmedPetHeals, true, "Whether or not bots are allowed to cast heal charmed pets, default true.") RULE_BOOL(Bots, AllowCharmedPetCures, true, "Whether or not bots are allowed to cast cure charmed pets, default true.") RULE_BOOL(Bots, ShowResistMessagesToOwner, true, "Default True. If enabled, when a bot's spell is resisted it will send a spell failure to their owner.") RULE_BOOL(Bots, BotBuffLevelRestrictions, true, "Buffs will not land on low level bots like live players") -RULE_BOOL(Bots, BotsUseLiveBlockedMessage, false, "Setting whether detailed spell block messages should be used for bots as players do on the live servers") +RULE_BOOL(Bots, BotsUseLiveBlockedMessage, true, "Setting whether detailed spell block messages should be used for bots as players do on the live servers") RULE_BOOL(Bots, BotSoftDeletes, true, "When bots are deleted, they are only soft deleted") RULE_INT(Bots, MinStatusToBypassSpawnLimit, 100, "Minimum status to bypass the anti-spam system") RULE_INT(Bots, StatusSpawnLimit, 120, "Minimum status to bypass spawn limit. Default 120.") diff --git a/zone/bot_commands/follow.cpp b/zone/bot_commands/follow.cpp index c5b7f8f78..f970773d7 100644 --- a/zone/bot_commands/follow.cpp +++ b/zone/bot_commands/follow.cpp @@ -200,7 +200,7 @@ void bot_command_follow(Client* c, const Seperator* sep) nextTar = target_mob; } } - LogTestDebug("{} is now following {}.", bot_iter->GetCleanName(), nextTar->GetCleanName()); //deleteme + chainList.push_back(bot_iter); ++it; ++count; diff --git a/zone/bot_commands/illusion_block.cpp b/zone/bot_commands/illusion_block.cpp index 0a3625046..3699fa080 100644 --- a/zone/bot_commands/illusion_block.cpp +++ b/zone/bot_commands/illusion_block.cpp @@ -63,13 +63,6 @@ void bot_command_illusion_block(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); return; } From 31a4f053b5eea7ee91182c5e22d5624cd1bd582a Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 7 Nov 2024 16:34:20 -0600 Subject: [PATCH 13/97] bot movement cleanup and tweaks, move casterrange to distanceranged --- .../database_update_manifest_bots.cpp | 8 +- common/ruletypes.h | 5 +- zone/bot.cpp | 127 ++++++------------ zone/bot.h | 15 +-- zone/bot_command.cpp | 4 +- zone/bot_command.h | 2 +- zone/bot_commands/bot_settings.cpp | 2 +- zone/bot_commands/copy_settings.cpp | 2 +- zone/bot_commands/default_settings.cpp | 2 +- .../{caster_range.cpp => distance_ranged.cpp} | 39 +++--- 10 files changed, 76 insertions(+), 130 deletions(-) rename zone/bot_commands/{caster_range.cpp => distance_ranged.cpp} (68%) diff --git a/common/database/database_update_manifest_bots.cpp b/common/database/database_update_manifest_bots.cpp index 639d173b0..a57a6f075 100644 --- a/common/database/database_update_manifest_bots.cpp +++ b/common/database/database_update_manifest_bots.cpp @@ -209,18 +209,18 @@ INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, (SELECT bs.`stance_id` FROM b INSERT INTO bot_settings SELECT NULL, 0, `bot_id`, (SELECT bs.`stance_id` FROM bot_stances bs WHERE bs.`bot_id` = bd.`bot_id`) AS stance_id, 5, 0, `archery_setting`, 'BaseSetting', 'RangedSetting' FROM bot_data WHERE `archery_setting` != 0; INSERT INTO bot_settings -SELECT NULL, 0, `bot_id`, (SELECT bs.`stance_id` FROM bot_stances bs WHERE bs.`bot_id` = bd.`bot_id`) AS stance_id, 8, 0, `caster_range`, 'BaseSetting', 'CasterRange' +SELECT NULL, 0, `bot_id`, (SELECT bs.`stance_id` FROM bot_stances bs WHERE bs.`bot_id` = bd.`bot_id`) AS stance_id, 8, 0, `caster_range`, 'BaseSetting', 'DistanceRanged' FROM ( SELECT `bot_id`, (CASE WHEN (`class` IN (1, 7, 19, 16)) THEN 0 WHEN `class` = 8 THEN 0 ELSE 90 - END) AS `casterRange`, + END) AS `DistanceRanged`, `caster_range` FROM bot_data ) AS `subquery` -WHERE `casterRange` != `caster_range`; +WHERE `DistanceRanged` != `caster_range`; ALTER TABLE `bot_data` DROP COLUMN `show_helm`; @@ -241,7 +241,7 @@ UPDATE `bot_command_settings` SET `aliases`= 'bh' WHERE `bot_command`='behindmob UPDATE `bot_command_settings` SET `aliases`= 'bs|settings' WHERE `bot_command`='botsettings'; UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|followdistance') ELSE 'followd||followdistance' END WHERE `bot_command`='botfollowdistance' AND `aliases` NOT LIKE '%followdistance%'; UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|ranged|toggleranged|btr') ELSE 'ranged|toggleranged|btr' END WHERE `bot_command`='bottoggleranged' AND `aliases` NOT LIKE '%ranged|toggleranged|btr%'; -UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|cr') ELSE 'cr' END WHERE `bot_command`='casterrange' AND `aliases` NOT LIKE '%cr%'; +UPDATE `bot_command_settings` SET `aliases`= 'distranged|dr' WHERE `bot_command`='distanceranged'; UPDATE `bot_command_settings` SET `aliases`= 'copy' WHERE `bot_command`='copysettings'; UPDATE `bot_command_settings` SET `aliases`= 'default' WHERE `bot_command`='defaultsettings'; UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|enforce') ELSE 'enforce' END WHERE `bot_command`='enforcespellsettings' AND `aliases` NOT LIKE '%enforce%'; diff --git a/common/ruletypes.h b/common/ruletypes.h index 526373e9d..c1e46b5ae 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -833,15 +833,13 @@ RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing RULE_INT(Bots, StackSizeMin, 20, "20 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).") RULE_INT(Bots, HasOrMayGetAggroThreshold, 90, "90 Default. Percent threshold of total hate where bots will stop casting spells that generate hate if they are set to try to not pull aggro via spells.") RULE_BOOL(Bots, UseFlatNormalMeleeRange, false, "False Default. If true, bots melee distance will be a flat distance set by Bots:NormalMeleeRangeDistance.") -RULE_REAL(Bots, NormalMeleeRangeDistance, 0.75, "Multiplier of the max melee range at which a bot will stand in melee combat. 0.75 Recommended, max melee for all abilities to land.") +RULE_REAL(Bots, NormalMeleeRangeDistance, 0.75, "If UseFlatNormalMeleeRange is enabled, multiplier of the max melee range at which a bot will stand in melee combat. 0.75 Recommended, max melee for all abilities to land.") RULE_REAL(Bots, PercentMinMeleeDistance, 0.60, "Multiplier of the max melee range - Minimum distance from target a bot will stand while in melee combat before trying to adjust. 0.60 Recommended.") RULE_REAL(Bots, MaxDistanceForMelee, 20, "Maximum distance bots will stand for melee. Default 20 to allow all special attacks to land.") RULE_REAL(Bots, TauntNormalMeleeRangeDistance, 0.50, "Multiplier of the max melee range at which a taunting bot will stand in melee combat. 0.50 Recommended, closer than others .") RULE_REAL(Bots, PercentTauntMinMeleeDistance, 0.25, "Multiplier of max melee range - Minimum distance from target a taunting bot will stand while in melee combat before trying to adjust. 0.25 Recommended.") RULE_REAL(Bots, PercentMaxMeleeRangeDistance, 0.95, "Multiplier of the max melee range at which a bot will stand in melee combat. 0.95 Recommended, max melee while disabling special attacks/taunt.") RULE_REAL(Bots, PercentMinMaxMeleeRangeDistance, 0.75, "Multiplier of the closest max melee range at which a bot will stand in melee combat before trying to adjust. 0.75 Recommended, max melee while disabling special attacks/taunt.") -RULE_BOOL(Bots, CastersStayJustOutOfMeleeRange, true, "True Default. If true, caster bots will stay just out of melee range. Otherwise they use Bots:PercentMinCasterRangeDistance.") -RULE_REAL(Bots, PercentMinCasterRangeDistance, 0.60, "Multiplier of the closest caster range at which a bot will stand while casting before trying to adjust. 0.60 Recommended.") RULE_BOOL(Bots, TauntingBotsFollowTopHate, true, "True Default. If true, bots that are taunting will attempt to stick with whoever currently is top hate.") RULE_REAL(Bots, DistanceTauntingBotsStickMainHate, 25.00, "If TauntingBotsFollowTopHate is enabled, this is the distance bots will try to stick to whoever currently is Top Hate.") RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, false, "False Default. If true, when bots are at max melee distance, special abilities including taunt will be disabled.") @@ -869,6 +867,7 @@ RULE_BOOL(Bots, EnableBotTGB, true, "If enabled bots will cast group buffs as TG RULE_BOOL(Bots, DoResponseAnimations, true, "If enabled bots will do animations to certain responses or commands.") RULE_INT(Bots, DefaultFollowDistance, 20, "Default 20. Distance a bot will follow behind.") RULE_INT(Bots, MaxFollowDistance, 300, "Default 300. Max distance a bot can be set to follow behind.") +RULE_INT(Bots, MaxDistanceRanged, 300, "Default 300. Max distance a bot can be set to ranged.") RULE_CATEGORY_END() RULE_CATEGORY(Chat) diff --git a/zone/bot.cpp b/zone/bot.cpp index 0737d0961..bd62fdc62 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2148,8 +2148,9 @@ void Bot::AI_Process() float melee_distance_min = 0.0f; float melee_distance_max = 0.0f; float melee_distance = 0.0f; + tar_distance = sqrt(tar_distance); - CheckCombatRange(tar, sqrt(tar_distance), atCombatRange, behindMob, p_item, s_item, melee_distance_min, melee_distance_max, melee_distance, stopMeleeLevel); + CheckCombatRange(tar, tar_distance, atCombatRange, behindMob, p_item, s_item, melee_distance_min, melee_distance, melee_distance_max, stopMeleeLevel); // PULLING FLAG (ACTIONABLE RANGE) @@ -2227,7 +2228,7 @@ void Bot::AI_Process() return; } - if (IsBotRanged() && ranged_timer.Check(false)) { // Can shoot mezzed, stunned and dead!? + if (IsBotRanged() && ranged_timer.Check(false)) { TryRangedAttack(tar); if (!TargetValidation(tar)) { return; } @@ -2237,10 +2238,6 @@ void Bot::AI_Process() } } else if (!IsBotRanged() && GetLevel() < stopMeleeLevel) { - if (tar->IsEnraged() && !BehindMob(tar, GetX(), GetY())) { - return; - } - if (!GetMaxMeleeRange() || !RuleB(Bots, DisableSpecialAbilitiesAtMaxMelee)) { DoClassAttacks(tar); } @@ -2715,7 +2712,7 @@ bool Bot::TryEvade(Mob* tar) { return false; } -void Bot::CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, bool& behindMob, const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item, float& melee_distance_min, float& melee_distance_max, float& melee_distance, uint8 stopMeleeLevel) { +void Bot::CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, bool& behindMob, const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item, float& melee_distance_min, float& melee_distance, float& melee_distance_max, uint8 stopMeleeLevel) { atCombatRange= false; p_item = GetBotItem(EQ::invslot::slotPrimary); @@ -2731,16 +2728,16 @@ void Bot::CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, bo } // Calculate melee distances - CalcMeleeDistances(tar, p_item, s_item, behindMob, backstab_weapon, melee_distance_max, melee_distance, melee_distance_min, stopMeleeLevel); + CalcMeleeDistances(tar, p_item, s_item, behindMob, backstab_weapon, melee_distance_min, melee_distance, melee_distance_max, stopMeleeLevel); - //LogTestDebugDetail("{} is {} {}. They are currently {} away {} to be {} [{} - {}] away." + //LogTestDebugDetail("{} is {} {}. They are currently {} away {} to be between [{} - {}] away. MMR is {}." // , GetCleanName() - // , (tar_distance <= melee_distance ? "within range of" : "too far away from") + // , (tar_distance < melee_distance_min ? "too close to" : (tar_distance <= melee_distance ? "within range of" : "too far away from")) // , tar->GetCleanName() // , tar_distance // , (tar_distance <= melee_distance ? "but only needed" : "but need to be") - // , melee_distance // , melee_distance_min + // , melee_distance // , melee_distance_max //); //deleteme @@ -2749,16 +2746,14 @@ void Bot::CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, bo } } -void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_item, const EQ::ItemInstance* const& s_item, bool behindMob, bool backstab_weapon, float& melee_distance_max, float& melee_distance, float& melee_distance_min, uint8 stopMeleeLevel) { +void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_item, const EQ::ItemInstance* const& s_item, bool behindMob, bool backstab_weapon, float& melee_distance_min, float& melee_distance, float& melee_distance_max, uint8 stopMeleeLevel) { float size_mod = GetSize(); float other_size_mod = tar->GetSize(); - bool resquareDistance = false; // For races with a fixed size if (GetRace() == Race::LavaDragon || GetRace() == Race::Wurm || GetRace() == Race::GhostDragon) { // size_mod = 60.0f; } - else if (size_mod < 6.0f) { size_mod = 8.0f; } @@ -2767,7 +2762,6 @@ void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_it if (tar->GetRace() == Race::LavaDragon || tar->GetRace() == Race::Wurm || tar->GetRace() == Race::GhostDragon) { other_size_mod = 60.0f; } - else if (other_size_mod < 6.0f) { other_size_mod = 8.0f; } @@ -2779,11 +2773,9 @@ void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_it if (size_mod > 29.0f) { size_mod *= size_mod; } - else if (size_mod > 19.0f) { size_mod *= (size_mod * 2.0f); } - else { size_mod *= (size_mod * 4.0f); } @@ -2792,6 +2784,7 @@ void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_it { size_mod *= 1.75; } + if (tar->GetRace() == Race::DragonSkeleton) // Dracoliche in Fear. Skeletal Dragon { size_mod *= 2.25; @@ -2805,6 +2798,7 @@ void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_it } melee_distance_max = size_mod; + if (!RuleB(Bots, UseFlatNormalMeleeRange)) { switch (GetClass()) { case Class::Warrior: @@ -2872,31 +2866,26 @@ void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_it melee_distance = melee_distance_max * RuleR(Bots, TauntNormalMeleeRangeDistance); } - if (!taunting && !IsBotRanged() && GetMaxMeleeRange()) { + bool isStopMeleeLevel = GetLevel() >= stopMeleeLevel; + + if (!taunting && !IsBotRanged() && !isStopMeleeLevel && GetMaxMeleeRange()) { melee_distance = melee_distance_max * RuleR(Bots, PercentMaxMeleeRangeDistance); melee_distance_min = melee_distance_max * RuleR(Bots, PercentMinMaxMeleeRangeDistance); } - - /* Caster Range Checks */ - bool isStopMeleeLevel = GetLevel() >= stopMeleeLevel; - if (isStopMeleeLevel) { - melee_distance = GetBotCasterMaxRange(melee_distance_max); - if (RuleB(Bots, CastersStayJustOutOfMeleeRange)) { - melee_distance_min = melee_distance_max + 1; - } - else { - melee_distance_min = melee_distance_max * RuleR(Bots, PercentMinCasterRangeDistance); - } + if (isStopMeleeLevel && !IsBotRanged()) { + float desiredRange = GetBotDistanceRanged(); + melee_distance_min = std::min(melee_distance, (desiredRange / 2)); + melee_distance = std::max((melee_distance + 1), desiredRange); } /* Archer Checks*/ if (IsBotRanged()) { - float archeryRange = GetBotRangedValue(); - float casterRange = GetBotCasterRange(); - float minArcheryRange = RuleI(Combat, MinRangedAttackDist); - melee_distance = std::min(archeryRange, (casterRange * 2)); - melee_distance_min = std::max(std::max(minArcheryRange, (melee_distance_max + 1)), std::min(casterRange, archeryRange)); + float minDistance = RuleI(Combat, MinRangedAttackDist); + float maxDistance = GetBotRangedValue(); + float desiredRange = GetBotDistanceRanged(); + melee_distance_min = std::max(minDistance, (desiredRange / 2)); + melee_distance = std::min(maxDistance, desiredRange); } } @@ -8490,29 +8479,6 @@ void Bot::SendSpellAnim(uint16 target_id, uint16 spell_id) entity_list.QueueCloseClients(this, &app, false, RuleI(Range, SpellParticles)); } -float Bot::GetBotCasterMaxRange(float melee_distance_max) {// Calculate caster distances - float caster_distance_max = 0.0f; - float caster_distance_min = 0.0f; - float caster_distance = 0.0f; - - caster_distance_max = GetBotCasterRange(); - - if (!GetBotCasterRange() && GetLevel() >= GetStopMeleeLevel() && GetClass() >= Class::Warrior && GetClass() <= Class::Berserker) { - caster_distance_max = GetDefaultBotBaseSetting(BotBaseSettings::CasterRange); - } - - if (caster_distance_max) { - caster_distance_min = melee_distance_max; - - if (caster_distance_max <= caster_distance_min) { - caster_distance_max = caster_distance_min * 1.25f; - } - } - - return caster_distance_max; -} - - int32 Bot::CalcItemATKCap() { return RuleI(Character, ItemATKCap) + itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap; @@ -9982,8 +9948,8 @@ void Bot::SetBotBaseSetting(uint16 botSetting, int settingValue) { case BotBaseSettings::BehindMob: SetBehindMob(settingValue); break; - case BotBaseSettings::CasterRange: - SetBotCasterRange(settingValue); + case BotBaseSettings::DistanceRanged: + SetBotDistanceRanged(settingValue); break; case BotBaseSettings::IllusionBlock: SetIllusionBlock(settingValue); @@ -10031,9 +9997,9 @@ int Bot::GetBotBaseSetting(uint16 botSetting) { case BotBaseSettings::BehindMob: //LogBotSettingsDetail("Returning current GetBehindMob of [{}] for [{}]", GetBehindMob(), GetCleanName()); //deleteme return GetBehindMob(); - case BotBaseSettings::CasterRange: - //LogBotSettingsDetail("Returning current GetBotCasterRange of [{}] for [{}]", GetBotCasterRange(), GetCleanName()); //deleteme - return GetBotCasterRange(); + case BotBaseSettings::DistanceRanged: + //LogBotSettingsDetail("Returning current GetBotDistanceRanged of [{}] for [{}]", GetBotDistanceRanged(), GetCleanName()); //deleteme + return GetBotDistanceRanged(); case BotBaseSettings::IllusionBlock: //LogBotSettingsDetail("Returning current GetIllusionBlock of [{}] for [{}]", GetIllusionBlock(), GetCleanName()); //deleteme return GetIllusionBlock(); @@ -10080,7 +10046,7 @@ int Bot::GetDefaultBotBaseSetting(uint16 botSetting, uint8 stance) { else { return false; } - case BotBaseSettings::CasterRange: + case BotBaseSettings::DistanceRanged: switch (GetClass()) { case Class::Warrior: case Class::Monk: @@ -10717,8 +10683,8 @@ std::string Bot::GetBotSettingCategoryName(uint8 setting_type) { return "PetSetTypeSetting"; case BotBaseSettings::BehindMob: return "BehindMob"; - case BotBaseSettings::CasterRange: - return "CasterRange"; + case BotBaseSettings::DistanceRanged: + return "DistanceRanged"; case BotBaseSettings::IllusionBlock: return "IllusionBlock"; case BotBaseSettings::MaxMeleeRange: @@ -11036,7 +11002,7 @@ void Bot::SetCombatJitter() { void Bot::DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stopMeleeLevel, float tar_distance, float melee_distance_min, float melee_distance, float melee_distance_max, bool behindMob) { if (HasTargetReflection()) { - if (!tar->IsFeared() && !tar->IsStunned()) { + if (!taunting && !tar->IsFeared() && !tar->IsStunned()) { if (TryEvade(tar)) { return; } @@ -11044,14 +11010,14 @@ void Bot::DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stopMeleeLevel, flo if (tar->IsRooted() && !taunting) { // Move non-taunters out of range - Above already checks if bot is targeted, otherwise they would stay if (tar_distance <= melee_distance_max) { - if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 2), false, false, true)) { + if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 2), false, taunting)) { RunToGoalWithJitter(Goal); return; } } } - if (taunting && tar_distance < melee_distance_min) { // Back up any taunting bots that are too close + if (taunting && tar_distance < melee_distance_min) { // Back up any bots that are too close if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, taunting)) { RunToGoalWithJitter(Goal); return; @@ -11087,25 +11053,12 @@ void Bot::DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stopMeleeLevel, flo RunToGoalWithJitter(Goal); return; } - //else { - // if (stopMeleeLevel || IsBotArcher()) { - // if (IsBotArcher()) { - // float minArcheryRange = RuleI(Combat, MinRangedAttackDist) * RuleI(Combat, MinRangedAttackDist); - // if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, minArcheryRange, melee_distance, false, taunting)) { - // RunToGoalWithJitter(Goal); - // return; - // } - // } - // else { - // if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_max + 1, melee_distance, false, taunting)) { - // RunToGoalWithJitter(Goal); - // return; - // } - // } - // } - // DoFaceCheckWithJitter(tar); - // return; - //} + } + else if (tar->IsEnraged() && !taunting && !stopMeleeLevel && !behindMob) { // Move non-taunting melee bots behind target during enrage + if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) { + RunToGoalWithJitter(Goal); + return; + } } } } diff --git a/zone/bot.h b/zone/bot.h index 553148ee5..a2e0a8ffb 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -136,7 +136,7 @@ namespace BotBaseSettings { constexpr uint16 RangedSetting = 5; constexpr uint16 PetSetTypeSetting = 6; constexpr uint16 BehindMob = 7; - constexpr uint16 CasterRange = 8; + constexpr uint16 DistanceRanged = 8; constexpr uint16 IllusionBlock = 9; constexpr uint16 MaxMeleeRange = 10; constexpr uint16 MedInCombat = 11; @@ -506,8 +506,8 @@ public: void SetMaxMeleeRange(bool value) { _maxMeleeRangeStatus = value; } uint8 GetStopMeleeLevel() const { return _stopMeleeLevel; } void SetStopMeleeLevel(uint8 level) { _stopMeleeLevel = level; } - uint32 GetBotCasterRange() const { return _casterRange; } - void SetBotCasterRange(uint32 casterRange) { _casterRange = casterRange; } + uint32 GetBotDistanceRanged() const { return _distanceRanged; } + void SetBotDistanceRanged(uint32 distanceRanged) { _distanceRanged = distanceRanged; } bool GetMedInCombat() const { return _medInCombat; } void SetMedInCombat(bool value) { _medInCombat = value; } uint8 GetHPWhenToMed() const { return _HPWhenToMed; } @@ -640,7 +640,6 @@ public: bool GetRangerAutoWeaponSelect() { return _rangerAutoWeaponSelect; } uint8 GetBotStance() { return _botStance; } uint8 GetChanceToCastBySpellType(uint16 spellType); - float GetBotCasterMaxRange(float melee_distance_max); bool IsGroupHealer() const { return m_CastingRoles.GroupHealer; } bool IsGroupSlower() const { return m_CastingRoles.GroupSlower; } bool IsGroupNuker() const { return m_CastingRoles.GroupNuker; } @@ -929,9 +928,9 @@ public: const EQ::ItemInstance* const& s_item, bool behindMob, bool backstab_weapon, - float& melee_distance_max, - float& melee_distance, float& melee_distance_min, + float& melee_distance, + float& melee_distance_max, uint8 stopMeleeLevel ); @@ -947,8 +946,8 @@ public: const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item, float& melee_distance_min, - float& melee_distance_max, float& melee_distance, + float& melee_distance_max, uint8 stopMeleeLevel ); bool GetCombatJitterFlag() { return m_combat_jitter_flag; } @@ -1065,7 +1064,7 @@ private: bool _showHelm; bool _botRangedSetting; uint8 _stopMeleeLevel; - uint32 _casterRange; + uint32 _distanceRanged; bool _behindMobStatus; bool _maxMeleeRangeStatus; bool _medInCombat; diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 3f6dfef5b..80be595f8 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1285,7 +1285,7 @@ int bot_command_init(void) bot_command_add("botupdate", "Updates a bot to reflect any level changes that you have experienced", AccountStatus::Player, bot_command_update) || bot_command_add("botwoad", "Changes the Barbarian woad of a bot", AccountStatus::Player, bot_command_woad) || bot_command_add("cast", "Tells the first found specified bot to cast the given spell type", AccountStatus::Player, bot_command_cast) || - bot_command_add("casterrange", "Controls the range casters will try to stay away from a mob (if too far, they will skip spells that are out-of-range)", AccountStatus::Player, bot_command_caster_range) || + bot_command_add("distanceranged", "Controls the range casters and ranged will try to stay away from a mob", AccountStatus::Player, bot_command_distance_ranged) || bot_command_add("charm", "Attempts to have a bot charm your target", AccountStatus::Player, bot_command_charm) || bot_command_add("circle", "Orders a Druid bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_circle) || bot_command_add("classracelist", "Lists the classes and races and their appropriate IDs", AccountStatus::Player, bot_command_class_race_list) || @@ -2256,7 +2256,6 @@ void Bot::SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, st #include "bot_commands/bot.cpp" #include "bot_commands/bot_settings.cpp" #include "bot_commands/cast.cpp" -#include "bot_commands/caster_range.cpp" #include "bot_commands/charm.cpp" #include "bot_commands/class_race_list.cpp" #include "bot_commands/click_item.cpp" @@ -2265,6 +2264,7 @@ void Bot::SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, st #include "bot_commands/default_settings.cpp" #include "bot_commands/defensive.cpp" #include "bot_commands/depart.cpp" +#include "bot_commands/distance_ranged.cpp" #include "bot_commands/escape.cpp" #include "bot_commands/find_aliases.cpp" #include "bot_commands/follow.cpp" diff --git a/zone/bot_command.h b/zone/bot_command.h index 53cb2776b..a8951a7cd 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -1673,7 +1673,7 @@ void bot_command_bind_affinity(Client *c, const Seperator *sep); void bot_command_bot(Client *c, const Seperator *sep); void bot_command_bot_settings(Client* c, const Seperator* sep); void bot_command_cast(Client* c, const Seperator* sep); -void bot_command_caster_range(Client* c, const Seperator* sep); +void bot_command_distance_ranged(Client* c, const Seperator* sep); void bot_command_charm(Client *c, const Seperator *sep); void bot_command_class_race_list(Client* c, const Seperator* sep); void bot_command_click_item(Client* c, const Seperator* sep); diff --git a/zone/bot_commands/bot_settings.cpp b/zone/bot_commands/bot_settings.cpp index e94bea5f1..4dc9babdf 100644 --- a/zone/bot_commands/bot_settings.cpp +++ b/zone/bot_commands/bot_settings.cpp @@ -4,7 +4,7 @@ void bot_command_bot_settings(Client* c, const Seperator* sep) { std::list subcommand_list; subcommand_list.push_back("behindmob"); - subcommand_list.push_back("casterrange"); + subcommand_list.push_back("distanceranged"); subcommand_list.push_back("copysettings"); subcommand_list.push_back("defaultsettings"); subcommand_list.push_back("enforcespelllist"); diff --git a/zone/bot_commands/copy_settings.cpp b/zone/bot_commands/copy_settings.cpp index fafead14c..e2745442f 100644 --- a/zone/bot_commands/copy_settings.cpp +++ b/zone/bot_commands/copy_settings.cpp @@ -74,7 +74,7 @@ void bot_command_copy_settings(Client* c, const Seperator* sep) "[misc] copies all miscellaneous options such as:", "- ^showhelm, ^followd, ^stopmeleelevel", "- ^enforcespellsettings, ^bottoggleranged, ^petsettype", - "- ^behindmob, ^casterrange, ^illusionblock", + "- ^behindmob, ^distanceranged, ^illusionblock", "- ^sitincombat, ^sithppercent and ^sitmanapercent", }; diff --git a/zone/bot_commands/default_settings.cpp b/zone/bot_commands/default_settings.cpp index 87bb31b0f..ddf809a75 100644 --- a/zone/bot_commands/default_settings.cpp +++ b/zone/bot_commands/default_settings.cpp @@ -68,7 +68,7 @@ void bot_command_default_settings(Client* c, const Seperator* sep) "[misc] restores all miscellaneous options such as:", "- ^showhelm, ^followd, ^stopmeleelevel", "- ^enforcespellsettings, ^bottoggleranged, ^petsettype", - "- ^behindmob, ^casterrange, ^illusionblock", + "- ^behindmob, ^distanceranged, ^illusionblock", "- ^sitincombat, ^sithppercent and ^sitmanapercent", }; diff --git a/zone/bot_commands/caster_range.cpp b/zone/bot_commands/distance_ranged.cpp similarity index 68% rename from zone/bot_commands/caster_range.cpp rename to zone/bot_commands/distance_ranged.cpp index 87676c7d4..70d5e655b 100644 --- a/zone/bot_commands/caster_range.cpp +++ b/zone/bot_commands/distance_ranged.cpp @@ -1,14 +1,13 @@ #include "../bot_command.h" -void bot_command_caster_range(Client* c, const Seperator* sep) +void bot_command_distance_ranged(Client* c, const Seperator* sep) { - if (helper_command_alias_fail(c, "bot_command_caster_range", sep->arg[0], "casterrange")) { + if (helper_command_alias_fail(c, "bot_command_distance_ranged", sep->arg[0], "distanceranged")) { return; } if (helper_is_help_or_usage(sep->arg[1])) { c->Message(Chat::White, "usage: %s [current | value: 0 - 300] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); - c->Message(Chat::White, "note: Can only be used for Casters or Hybrids."); c->Message(Chat::White, "note: Use [current] to check the current setting."); c->Message(Chat::White, "note: Set the value to the minimum distance you want your bot to try to remain from its target."); c->Message(Chat::White, "note: If they are too far for a spell, it will be skipped."); @@ -21,13 +20,13 @@ void bot_command_caster_range(Client* c, const Seperator* sep) std::string arg1 = sep->arg[1]; int ab_arg = 1; bool current_check = false; - uint32 crange = 0; + uint32 value = 0; if (sep->IsNumber(1)) { ++ab_arg; - crange = atoi(sep->arg[1]); - if (crange < 0 || crange > 300) { - c->Message(Chat::White, "You must enter a value within the range of 0 - 300."); + value = atoi(sep->arg[1]); + if (value < 0 || value > RuleI(Bots, MaxDistanceRanged)) { + c->Message(Chat::Yellow, "You must enter a value within the range of 0 - 300."); return; } } @@ -36,7 +35,7 @@ void bot_command_caster_range(Client* c, const Seperator* sep) current_check = true; } else { - c->Message(Chat::White, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); + c->Message(Chat::Yellow, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); return; } @@ -58,47 +57,43 @@ void bot_command_caster_range(Client* c, const Seperator* sep) int success_count = 0; for (auto my_bot : sbl) { - if (!IsCasterClass(my_bot->GetClass()) && !IsHybridClass(my_bot->GetClass())) { - continue; - } - if (!first_found) { first_found = my_bot; } if (current_check) { c->Message( - Chat::White, + Chat::Green, fmt::format( - "{} says, 'My current caster range is {}.'", + "{} says, 'My current Distance Ranged is {}.'", my_bot->GetCleanName(), - my_bot->GetBotCasterRange() + my_bot->GetBotDistanceRanged() ).c_str() ); } else { - my_bot->SetBotCasterRange(crange); + my_bot->SetBotDistanceRanged(value); ++success_count; } } if (!current_check) { if (success_count == 1 && first_found) { c->Message( - Chat::White, + Chat::Green, fmt::format( - "{} says, 'My Caster Range was set to {}.'", + "{} says, 'My Distance Ranged was set to {}.'", first_found->GetCleanName(), - crange + value ).c_str() ); } else { c->Message( - Chat::White, + Chat::Green, fmt::format( - "{} of your bots set their Caster Range to {}.", + "{} of your bots set their Distance Ranged to {}.", success_count, - crange + value ).c_str() ); } From 2d1b34d0cbef2cac0470d6d33974728acd071a5d Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Fri, 8 Nov 2024 08:41:18 -0600 Subject: [PATCH 14/97] command cleanup --- zone/bot_commands/illusion_block.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zone/bot_commands/illusion_block.cpp b/zone/bot_commands/illusion_block.cpp index 3699fa080..e44ddf601 100644 --- a/zone/bot_commands/illusion_block.cpp +++ b/zone/bot_commands/illusion_block.cpp @@ -63,6 +63,14 @@ void bot_command_illusion_block(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + return; } From 57e9b62a48794b1e21a6815f9c43ee357391d42c Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:59:01 -0600 Subject: [PATCH 15/97] Add viral, fear, stun, knockback, gravityeffect support to bots --- zone/bot.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index bd62fdc62..0a9a20639 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -588,7 +588,7 @@ void Bot::ChangeBotRangedWeapons(bool isRanged) { BotAddEquipItem(EQ::invslot::slotAmmo, GetBotItemBySlot(EQ::invslot::slotAmmo)); BotAddEquipItem(EQ::invslot::slotSecondary, GetBotItemBySlot(EQ::invslot::slotRange)); SetAttackTimer(); - BotGroupSay(this, "My bow is true and ready"); //TODO bot rewrite - make this say throwing or bow + BotGroupSay(this, "My blades are sheathed"); } } @@ -1593,6 +1593,7 @@ bool Bot::Process() { if (IsStunned() && stunned_timer.Check()) { Mob::UnStun(); + spun_timer.Disable(); } if (!GetBotOwner()) { @@ -1600,7 +1601,6 @@ bool Bot::Process() } if (GetDepop()) { - _botOwner = nullptr; _botOwnerCharacterID = 0; @@ -1608,6 +1608,7 @@ bool Bot::Process() } ScanCloseMobProcess(); + CheckScanCloseMobsMovingTimer(); // TODO bot rewrite -- necessary for bots? SpellProcess(); if (tic_timer.Check()) { @@ -1620,7 +1621,7 @@ bool Bot::Process() BuffProcess(); CalcRestState(); - if (currently_fleeing) { + if (currently_fleeing || IsFeared()) { ProcessFlee(); } @@ -1655,6 +1656,16 @@ bool Bot::Process() } } + if (viral_timer.Check()) { // TODO bot rewrite -- necessary for bots? + VirusEffectProcess(); + } + + if (spellbonuses.GravityEffect == 1) { + if (gravity_timer.Check()) { + DoGravityEffect(); + } + } + if (GetAppearance() == eaDead && GetHP() > 0) { SetAppearance(eaStanding); } @@ -1663,7 +1674,6 @@ bool Bot::Process() ping_timer.Disable(); } else { - if (!ping_timer.Enabled()) { ping_timer.Start(BOT_KEEP_ALIVE_INTERVAL); } @@ -1684,7 +1694,19 @@ bool Bot::Process() auto_save_timer.Start(RuleI(Bots, AutosaveIntervalSeconds) * 1000); } - if (IsStunned() || IsMezzed()) { + if (ForcedMovement) { + ProcessForcedMovement(); + } + + if (IsMezzed()) { + return true; + } + + if (IsStunned()) { + if (spun_timer.Check()) { + Spin(); + } + return true; } @@ -2155,7 +2177,6 @@ void Bot::AI_Process() // PULLING FLAG (ACTIONABLE RANGE) if (GetPullingFlag()) { - //TODO bot rewrite - add ways to here to determine if throw stone is allowed, then check for ranged/spell/melee if (!IsBotNonSpellFighter() && !HOLDING && AI_HasSpells() && AI_EngagedCastCheck()) { return; } @@ -3130,6 +3151,25 @@ bool Bot::CheckIfIncapacitated() { return true; } + if (currently_fleeing) { + if (RuleB(Combat, EnableFearPathing) && AI_movement_timer->Check()) { + // Check if we have reached the last fear point + if (DistanceNoZ(glm::vec3(GetX(), GetY(), GetZ()), m_FearWalkTarget) <= 5.0f) { + // Calculate a new point to run to + StopNavigation(); + CalculateNewFearpoint(); + } + + RunTo( + m_FearWalkTarget.x, + m_FearWalkTarget.y, + m_FearWalkTarget.z + ); + } + + return true; + } + return false; } @@ -10891,7 +10931,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spellType, uint16 spellid) { return false; } - switch (spellType) { //TODO bot rewrite - fix Buff/ResistBuff + switch (spellType) { case BotSpellTypes::Buff: if (IsResistanceOnlySpell(spellid) || IsDamageShieldOnlySpell(spellid) || IsDamageShieldAndResistanceSpellOnly(spellid)) { return false; From 938c72619cb361c5a5afc14fab5f5030b64da0aa Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sat, 9 Nov 2024 21:59:52 -0600 Subject: [PATCH 16/97] only load client spell types for clients --- zone/client.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zone/client.cpp b/zone/client.cpp index a03e6f931..9d01f181b 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -13087,6 +13087,10 @@ void Client::LoadDefaultBotSettings() { LogBotSettingsDetail("{} says, 'Setting default {} [{}] to [{}]'", GetCleanName(), CastToBot()->GetBotSettingCategoryName(BotBaseSettings::IllusionBlock), BotBaseSettings::IllusionBlock, GetDefaultBotSettings(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock)); //deleteme for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + if (!IsClientBotSpellType(i)) { + continue; + } + BotSpellSettings_Struct t; t.spellType = i; From 0d970844d303d67299762485f32a081b855710fb Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sat, 9 Nov 2024 22:00:20 -0600 Subject: [PATCH 17/97] add passive stance checks to commands and loading/saving. shouldn't be ` --- zone/bot.cpp | 19 ++++++++++++++++++- zone/bot.h | 3 ++- zone/bot_commands/behind_mob.cpp | 5 +++++ zone/bot_commands/bot.cpp | 10 +--------- zone/bot_commands/cast.cpp | 2 +- zone/bot_commands/click_item.cpp | 6 ++++++ zone/bot_commands/distance_ranged.cpp | 4 ++++ zone/bot_commands/illusion_block.cpp | 5 +++++ zone/bot_commands/max_melee_range.cpp | 5 +++++ zone/bot_commands/sit_hp_percent.cpp | 5 +++++ zone/bot_commands/sit_in_combat.cpp | 5 +++++ zone/bot_commands/sit_mana_percent.cpp | 5 +++++ zone/bot_commands/spell_aggro_checks.cpp | 5 +++++ zone/bot_commands/spell_delays.cpp | 5 +++++ zone/bot_commands/spell_engaged_priority.cpp | 5 +++++ zone/bot_commands/spell_idle_priority.cpp | 5 +++++ zone/bot_commands/spell_max_hp_pct.cpp | 5 +++++ zone/bot_commands/spell_max_mana_pct.cpp | 5 +++++ zone/bot_commands/spell_max_thresholds.cpp | 5 +++++ zone/bot_commands/spell_min_hp_pct.cpp | 5 +++++ zone/bot_commands/spell_min_mana_pct.cpp | 5 +++++ zone/bot_commands/spell_min_thresholds.cpp | 5 +++++ zone/bot_commands/spell_pursue_priority.cpp | 5 +++++ zone/bot_commands/spell_target_count.cpp | 5 +++++ zone/bot_database.cpp | 10 ++++++++++ 25 files changed, 132 insertions(+), 12 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 0a9a20639..e99cbd3e8 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9844,7 +9844,6 @@ bool Bot::IsMobEngagedByAnyone(Mob* tar) { if (m->GetTarget() == tar) { if ( m->IsBot() && - !m->CastToBot()->GetHoldFlag() && m->IsEngaged() && ( !m->CastToBot()->IsBotNonSpellFighter() || @@ -11432,3 +11431,21 @@ void Bot::ResetBotSpellSettings() AI_AddBotSpells(GetBotSpellID()); SetBotEnforceSpellSetting(false); } + +bool Bot::BotPassiveCheck() { + if (GetBotStance() == Stance::Passive) { + GetOwner()->Message( + Chat::Yellow, + fmt::format( + "{} says, 'I am currently set to stance {} [#{}]. My settings cannot be modified.'", + GetCleanName(), + Stance::GetName(Stance::Passive), + Stance::Passive + ).c_str() + ); + + return true; + } + + return false; +} diff --git a/zone/bot.h b/zone/bot.h index a2e0a8ffb..e0d857880 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -497,7 +497,8 @@ public: void SetSpellTypeMaxHPLimit(uint16 spellType, uint8 hpLimit); inline uint16 GetSpellTypeAEOrGroupTargetCount(uint16 spellType) const { return _spellSettings[spellType].AEOrGroupTargetCount; } void SetSpellTypeAEOrGroupTargetCount(uint16 spellType, uint16 targetCount); - + bool BotPassiveCheck(); + bool GetShowHelm() const { return _showHelm; } void SetShowHelm(bool showHelm) { _showHelm = showHelm; } bool GetBehindMob() const { return _behindMobStatus; } diff --git a/zone/bot_commands/behind_mob.cpp b/zone/bot_commands/behind_mob.cpp index 8e6915860..a7710185f 100644 --- a/zone/bot_commands/behind_mob.cpp +++ b/zone/bot_commands/behind_mob.cpp @@ -130,9 +130,14 @@ void bot_command_behind_mob(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/bot.cpp b/zone/bot_commands/bot.cpp index d867b4e67..ba0ae10dc 100644 --- a/zone/bot_commands/bot.cpp +++ b/zone/bot_commands/bot.cpp @@ -1440,7 +1440,6 @@ void bot_command_stop_melee_level(Client* c, const Seperator* sep) if (helper_is_help_or_usage(sep->arg[1])) { c->Message(Chat::White, "usage: %s [current | reset | sync | value: 0-255] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); - c->Message(Chat::White, "note: Only caster or hybrid class bots may be modified"); c->Message(Chat::White, "note: Use [reset] to set stop melee level to server rule"); c->Message(Chat::White, "note: Use [sync] to set stop melee level to current bot level"); return; @@ -1508,14 +1507,7 @@ void bot_command_stop_melee_level(Client* c, const Seperator* sep) int success_count = 0; for (auto my_bot : sbl) { - if (!IsCasterClass(my_bot->GetClass()) && !IsHybridClass(my_bot->GetClass())) { - c->Message( - Chat::White, - fmt::format( - "{} says, 'This command only works on caster or hybrid classes.'", - my_bot->GetCleanName() - ).c_str() - ); + if (my_bot->BotPassiveCheck()) { continue; } diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index 70b6e659c..fd7dc5aa8 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -234,7 +234,7 @@ void bot_command_cast(Client* c, const Seperator* sep) NEED TO CHECK: precombat, AE Dispel, AE Lifetap DO I NEED A PBAE CHECK??? */ - if (bot_iter->GetHoldFlag() || bot_iter->GetAppearance() == eaDead || bot_iter->IsFeared() || bot_iter->IsStunned() || bot_iter->IsMezzed() || bot_iter->DivineAura() || bot_iter->GetHP() < 0) { + if (bot_iter->GetBotStance() == Stance::Passive || bot_iter->GetHoldFlag() || bot_iter->GetAppearance() == eaDead || bot_iter->IsFeared() || bot_iter->IsStunned() || bot_iter->IsMezzed() || bot_iter->DivineAura() || bot_iter->GetHP() < 0) { continue; } diff --git a/zone/bot_commands/click_item.cpp b/zone/bot_commands/click_item.cpp index 80375b52c..469750f59 100644 --- a/zone/bot_commands/click_item.cpp +++ b/zone/bot_commands/click_item.cpp @@ -40,12 +40,18 @@ void bot_command_click_item(Client* c, const Seperator* sep) } std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } + sbl.remove(nullptr); for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (RuleI(Bots, BotsClickItemsMinLvl) > my_bot->GetLevel()) { c->Message(Chat::White, "%s must be level %i to use clickable items.", my_bot->GetCleanName(), RuleI(Bots, BotsClickItemsMinLvl)); continue; diff --git a/zone/bot_commands/distance_ranged.cpp b/zone/bot_commands/distance_ranged.cpp index 70d5e655b..7c7dec82a 100644 --- a/zone/bot_commands/distance_ranged.cpp +++ b/zone/bot_commands/distance_ranged.cpp @@ -57,6 +57,10 @@ void bot_command_distance_ranged(Client* c, const Seperator* sep) int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } diff --git a/zone/bot_commands/illusion_block.cpp b/zone/bot_commands/illusion_block.cpp index e44ddf601..94faafc42 100644 --- a/zone/bot_commands/illusion_block.cpp +++ b/zone/bot_commands/illusion_block.cpp @@ -130,9 +130,14 @@ void bot_command_illusion_block(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/max_melee_range.cpp b/zone/bot_commands/max_melee_range.cpp index e876a9759..c9e32dec1 100644 --- a/zone/bot_commands/max_melee_range.cpp +++ b/zone/bot_commands/max_melee_range.cpp @@ -129,9 +129,14 @@ void bot_command_max_melee_range(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/sit_hp_percent.cpp b/zone/bot_commands/sit_hp_percent.cpp index ac94a13fd..fa4a183bf 100644 --- a/zone/bot_commands/sit_hp_percent.cpp +++ b/zone/bot_commands/sit_hp_percent.cpp @@ -129,9 +129,14 @@ void bot_command_sit_hp_percent(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/sit_in_combat.cpp b/zone/bot_commands/sit_in_combat.cpp index ffcf4d887..210372f56 100644 --- a/zone/bot_commands/sit_in_combat.cpp +++ b/zone/bot_commands/sit_in_combat.cpp @@ -129,9 +129,14 @@ void bot_command_sit_in_combat(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/sit_mana_percent.cpp b/zone/bot_commands/sit_mana_percent.cpp index 85afc8047..a5751f9f4 100644 --- a/zone/bot_commands/sit_mana_percent.cpp +++ b/zone/bot_commands/sit_mana_percent.cpp @@ -129,9 +129,14 @@ void bot_command_sit_mana_percent(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/spell_aggro_checks.cpp b/zone/bot_commands/spell_aggro_checks.cpp index e9ae709ee..2dfeef486 100644 --- a/zone/bot_commands/spell_aggro_checks.cpp +++ b/zone/bot_commands/spell_aggro_checks.cpp @@ -190,9 +190,14 @@ void bot_command_spell_aggro_checks(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/spell_delays.cpp b/zone/bot_commands/spell_delays.cpp index 3c3b153ca..2a5de1017 100644 --- a/zone/bot_commands/spell_delays.cpp +++ b/zone/bot_commands/spell_delays.cpp @@ -196,9 +196,14 @@ void bot_command_spell_delays(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/spell_engaged_priority.cpp b/zone/bot_commands/spell_engaged_priority.cpp index 89d76f2b5..70425ee11 100644 --- a/zone/bot_commands/spell_engaged_priority.cpp +++ b/zone/bot_commands/spell_engaged_priority.cpp @@ -194,9 +194,14 @@ void bot_command_spell_engaged_priority(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/spell_idle_priority.cpp b/zone/bot_commands/spell_idle_priority.cpp index 566a3f46a..d6f0a0c35 100644 --- a/zone/bot_commands/spell_idle_priority.cpp +++ b/zone/bot_commands/spell_idle_priority.cpp @@ -194,9 +194,14 @@ void bot_command_spell_idle_priority(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/spell_max_hp_pct.cpp b/zone/bot_commands/spell_max_hp_pct.cpp index 9d94fd722..7f8cd150b 100644 --- a/zone/bot_commands/spell_max_hp_pct.cpp +++ b/zone/bot_commands/spell_max_hp_pct.cpp @@ -190,9 +190,14 @@ void bot_command_spell_max_hp_pct(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/spell_max_mana_pct.cpp b/zone/bot_commands/spell_max_mana_pct.cpp index 9e22617b0..44cc7e8d5 100644 --- a/zone/bot_commands/spell_max_mana_pct.cpp +++ b/zone/bot_commands/spell_max_mana_pct.cpp @@ -190,9 +190,14 @@ void bot_command_spell_max_mana_pct(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/spell_max_thresholds.cpp b/zone/bot_commands/spell_max_thresholds.cpp index 3b4e0e85f..7ff651514 100644 --- a/zone/bot_commands/spell_max_thresholds.cpp +++ b/zone/bot_commands/spell_max_thresholds.cpp @@ -196,9 +196,14 @@ void bot_command_spell_max_thresholds(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/spell_min_hp_pct.cpp b/zone/bot_commands/spell_min_hp_pct.cpp index 739ab0d1c..371b44820 100644 --- a/zone/bot_commands/spell_min_hp_pct.cpp +++ b/zone/bot_commands/spell_min_hp_pct.cpp @@ -190,9 +190,14 @@ void bot_command_spell_min_hp_pct(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/spell_min_mana_pct.cpp b/zone/bot_commands/spell_min_mana_pct.cpp index e83362f76..b053c462c 100644 --- a/zone/bot_commands/spell_min_mana_pct.cpp +++ b/zone/bot_commands/spell_min_mana_pct.cpp @@ -190,9 +190,14 @@ void bot_command_spell_min_mana_pct(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/spell_min_thresholds.cpp b/zone/bot_commands/spell_min_thresholds.cpp index 6df8fc999..d690dede2 100644 --- a/zone/bot_commands/spell_min_thresholds.cpp +++ b/zone/bot_commands/spell_min_thresholds.cpp @@ -198,9 +198,14 @@ void bot_command_spell_min_thresholds(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/spell_pursue_priority.cpp b/zone/bot_commands/spell_pursue_priority.cpp index 593606437..872444f32 100644 --- a/zone/bot_commands/spell_pursue_priority.cpp +++ b/zone/bot_commands/spell_pursue_priority.cpp @@ -194,9 +194,14 @@ void bot_command_spell_pursue_priority(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_commands/spell_target_count.cpp b/zone/bot_commands/spell_target_count.cpp index f87e232ee..2f78a7468 100644 --- a/zone/bot_commands/spell_target_count.cpp +++ b/zone/bot_commands/spell_target_count.cpp @@ -190,9 +190,14 @@ void bot_command_spell_target_count(Client* c, const Seperator* sep) Bot* first_found = nullptr; int success_count = 0; for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + if (!first_found) { first_found = my_bot; } + if (current_check) { c->Message( Chat::Green, diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index e60a17961..3f42d995d 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -2221,6 +2221,11 @@ bool BotDatabase::LoadBotSettings(Mob* m) else { query = fmt::format("`bot_id` = {} AND `stance` = {}", mobID, stanceID); } + + if (stanceID == Stance::Passive) { + LogBotSettings("{} is currently set to {} [#{}]. No saving or loading required.", m->GetCleanName(), Stance::GetName(Stance::Passive), Stance::Passive); + return true; + } const auto& l = BotSettingsRepository::GetWhere(database, query); @@ -2268,6 +2273,11 @@ bool BotDatabase::SaveBotSettings(Mob* m) uint32 charID = (m->IsClient() ? m->CastToClient()->CharacterID() : 0); uint8 stanceID = (m->IsBot() ? m->CastToBot()->GetBotStance() : 0); + if (stanceID == Stance::Passive) { + LogBotSettings("{} is currently set to {} [#{}]. No saving or loading required.", m->GetCleanName(), Stance::GetName(Stance::Passive), Stance::Passive); + return true; + } + std::string query = ""; if (m->IsClient()) { From 9f9c25c653ac0adf7b4d33991fea017ea9d8a6df Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sat, 9 Nov 2024 22:07:35 -0600 Subject: [PATCH 18/97] passivecheck response --- zone/bot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index e99cbd3e8..e6af415b3 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -11437,7 +11437,7 @@ bool Bot::BotPassiveCheck() { GetOwner()->Message( Chat::Yellow, fmt::format( - "{} says, 'I am currently set to stance {} [#{}]. My settings cannot be modified.'", + "{} says, 'I am currently set to stance {} [#{}]. I cannot do commands and my settings cannot be modified.'", GetCleanName(), Stance::GetName(Stance::Passive), Stance::Passive From cf26a476fd98d8c9f5046915edba77c2cdc1e94d Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 10 Nov 2024 10:13:44 -0600 Subject: [PATCH 19/97] remove spelltype checks from clients causing bad data --- zone/bot_database.cpp | 2 +- zone/client.cpp | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 3f42d995d..60f389a9b 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -2356,7 +2356,7 @@ bool BotDatabase::SaveBotSettings(Mob* m) for (uint16 i = BotSettingCategories::START_CLIENT; i <= BotSettingCategories::END_CLIENT; ++i) { for (uint16 x = BotSpellTypes::START; x <= BotSpellTypes::END; ++x) { LogBotSettings("{} says, 'Checking {} {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), m->CastToBot()->GetBotSpellCategoryName(i), m->CastToBot()->GetSpellTypeNameByID(x), x, m->CastToClient()->GetBotSetting(i, x), m->CastToClient()->GetDefaultBotSettings(i, x)); //deleteme - if (IsClientBotSpellType(x) && m->CastToClient()->GetBotSetting(i, x) != m->CastToClient()->GetDefaultBotSettings(i, x)) { + if (m->CastToClient()->GetBotSetting(i, x) != m->CastToClient()->GetDefaultBotSettings(i, x)) { auto e = BotSettingsRepository::BotSettings{ .char_id = charID, .bot_id = botID, diff --git a/zone/client.cpp b/zone/client.cpp index 9d01f181b..a03e6f931 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -13087,10 +13087,6 @@ void Client::LoadDefaultBotSettings() { LogBotSettingsDetail("{} says, 'Setting default {} [{}] to [{}]'", GetCleanName(), CastToBot()->GetBotSettingCategoryName(BotBaseSettings::IllusionBlock), BotBaseSettings::IllusionBlock, GetDefaultBotSettings(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock)); //deleteme for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - if (!IsClientBotSpellType(i)) { - continue; - } - BotSpellSettings_Struct t; t.spellType = i; From d3d53c5a74cd4299c1f046f3c68bd59be01abe1b Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 10 Nov 2024 10:14:23 -0600 Subject: [PATCH 20/97] remove circle/teleport, tweak depart --- zone/bot_command.cpp | 3 - zone/bot_command.h | 2 - zone/bot_commands/depart.cpp | 82 +++++++++++++++++------ zone/bot_commands/teleport.cpp | 119 --------------------------------- 4 files changed, 63 insertions(+), 143 deletions(-) delete mode 100644 zone/bot_commands/teleport.cpp diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 80be595f8..b47edd5e3 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1287,7 +1287,6 @@ int bot_command_init(void) bot_command_add("cast", "Tells the first found specified bot to cast the given spell type", AccountStatus::Player, bot_command_cast) || bot_command_add("distanceranged", "Controls the range casters and ranged will try to stay away from a mob", AccountStatus::Player, bot_command_distance_ranged) || bot_command_add("charm", "Attempts to have a bot charm your target", AccountStatus::Player, bot_command_charm) || - bot_command_add("circle", "Orders a Druid bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_circle) || bot_command_add("classracelist", "Lists the classes and races and their appropriate IDs", AccountStatus::Player, bot_command_class_race_list) || bot_command_add("clickitem", "Orders your targeted bot to click the item in the provided inventory slot.", AccountStatus::Player, bot_command_click_item) || bot_command_add("copysettings", "Copies settings from one bot to another", AccountStatus::Player, bot_command_copy_settings) || @@ -1345,7 +1344,6 @@ int bot_command_init(void) bot_command_add("picklock", "Orders a capable bot to pick the lock of the closest door", AccountStatus::Player, bot_command_pick_lock) || bot_command_add("pickpocket", "Orders a capable bot to pickpocket a NPC", AccountStatus::Player, bot_command_pickpocket) || bot_command_add("precombat", "Sets flag used to determine pre-combat behavior", AccountStatus::Player, bot_command_precombat) || - bot_command_add("portal", "Orders a Wizard bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_portal) || bot_command_add("pull", "Orders a designated bot to 'pull' an enemy", AccountStatus::Player, bot_command_pull) || bot_command_add("release", "Releases a suspended bot's AI processing (with hate list wipe)", AccountStatus::Player, bot_command_release) || bot_command_add("resistance", "Orders a bot to cast a specified resistance buff", AccountStatus::Player, bot_command_resistance) || @@ -2316,7 +2314,6 @@ void Bot::SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, st #include "bot_commands/summon_corpse.cpp" #include "bot_commands/suspend.cpp" #include "bot_commands/taunt.cpp" -#include "bot_commands/teleport.cpp" #include "bot_commands/timer.cpp" #include "bot_commands/track.cpp" #include "bot_commands/view_combos.cpp" diff --git a/zone/bot_command.h b/zone/bot_command.h index a8951a7cd..373fbd1e5 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -1775,7 +1775,6 @@ void bot_command_toggle_ranged(Client* c, const Seperator* sep); void bot_command_update(Client *c, const Seperator *sep); void bot_command_woad(Client *c, const Seperator *sep); -void bot_command_circle(Client *c, const Seperator *sep); void bot_command_heal_rotation_adaptive_targeting(Client *c, const Seperator *sep); void bot_command_heal_rotation_add_member(Client *c, const Seperator *sep); void bot_command_heal_rotation_add_target(Client *c, const Seperator *sep); @@ -1803,7 +1802,6 @@ void bot_command_inventory_window(Client *c, const Seperator *sep); void bot_command_pet_get_lost(Client *c, const Seperator *sep); void bot_command_pet_remove(Client *c, const Seperator *sep); void bot_command_pet_set_type(Client *c, const Seperator *sep); -void bot_command_portal(Client *c, const Seperator *sep); // bot command helpers diff --git a/zone/bot_commands/depart.cpp b/zone/bot_commands/depart.cpp index b337ef404..d9b09d2d4 100644 --- a/zone/bot_commands/depart.cpp +++ b/zone/bot_commands/depart.cpp @@ -1,59 +1,103 @@ #include "../bot_command.h" -void bot_command_depart(Client *c, const Seperator *sep) +void bot_command_depart(Client* c, const Seperator* sep) { bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Depart]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_command_depart", sep->arg[0], "depart")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [list | destination] ([option: single])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Depart); + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_command_depart", sep->arg[0], "depart")) { return; } - bool single = false; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [list | destination] ([option: single])", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Depart); + + return; + } + + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; bool single = false; std::string single_arg = sep->arg[2]; - if (!single_arg.compare("single")) + + if (!single_arg.compare("single")) { single = true; + } std::string destination = sep->arg[1]; + if (!destination.compare("list")) { - Bot* my_druid_bot = ActionableBots::AsGroupMember_ByClass(c, c, Class::Druid); - Bot* my_wizard_bot = ActionableBots::AsGroupMember_ByClass(c, c, Class::Wizard); + Bot* my_druid_bot = ActionableBots::Select_ByMinLevelAndClass(c, BCEnum::TT_None, sbl, 1, Class::Druid); + Bot* my_wizard_bot = ActionableBots::Select_ByMinLevelAndClass(c, BCEnum::TT_None, sbl, 1, Class::Wizard); + + if ( + (!my_druid_bot && !my_wizard_bot) || + (my_druid_bot && !my_druid_bot->IsInGroupOrRaid(c)) || + (my_wizard_bot && !my_wizard_bot->IsInGroupOrRaid(c)) + ) { + c->Message(Chat::Yellow, "No compatible bots found for %s.", sep->arg[0]); + return; + } + helper_command_depart_list(c, my_druid_bot, my_wizard_bot, local_list, single); + return; } else if (destination.empty()) { c->Message(Chat::White, "A [destination] or [list] argument is required to use this command"); + return; } - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; + my_bot = nullptr; + sbl.clear(); MyBots::PopulateSBL_BySpawnedBots(c, sbl); - bool cast_success = false; + for (auto list_iter : *local_list) { auto local_entry = list_iter->SafeCastToDepart(); - if (helper_spell_check_fail(local_entry)) + + if (helper_spell_check_fail(local_entry)) { continue; - if (local_entry->single != single) + } + + if (local_entry->single != single) { continue; - if (destination.compare(spells[local_entry->spell_id].teleport_zone)) + } + + if (destination.compare(spells[local_entry->spell_id].teleport_zone)) { continue; + } auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) continue; my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) + + if (!my_bot) { continue; + } + + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!my_bot->IsInGroupOrRaid(c)) { + continue; + } + + if (local_entry->spell_id != 0 && my_bot->GetMana() < spells[local_entry->spell_id].mana) { + continue; + } cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; } - helper_no_available_bots(c, my_bot); + if (!cast_success) { + helper_no_available_bots(c, my_bot); + } } diff --git a/zone/bot_commands/teleport.cpp b/zone/bot_commands/teleport.cpp deleted file mode 100644 index 94e8ee2b0..000000000 --- a/zone/bot_commands/teleport.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "../bot_command.h" - -void bot_command_circle(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Depart]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_command_circle", sep->arg[0], "circle")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [list | destination] ([option: single])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Depart, Class::Druid); - return; - } - - bool single = false; - std::string single_arg = sep->arg[2]; - if (!single_arg.compare("single")) - single = true; - - std::string destination = sep->arg[1]; - if (!destination.compare("list")) { - auto my_druid_bot = ActionableBots::AsGroupMember_ByClass(c, c, Class::Druid); - helper_command_depart_list(c, my_druid_bot, nullptr, local_list, single); - return; - } - else if (destination.empty()) { - c->Message(Chat::White, "A [destination] or [list] argument is required to use this command"); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToDepart(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->caster_class != Class::Druid) - continue; - if (local_entry->single != single) - continue; - if (destination.compare(spells[local_entry->spell_id].teleport_zone)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} - -void bot_command_portal(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Depart]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_command_portal", sep->arg[0], "portal")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [list | destination] ([option: single])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Depart, Class::Wizard); - return; - } - - bool single = false; - std::string single_arg = sep->arg[2]; - if (!single_arg.compare("single")) - single = true; - - std::string destination = sep->arg[1]; - if (!destination.compare("list")) { - auto my_wizard_bot = ActionableBots::AsGroupMember_ByClass(c, c, Class::Wizard); - helper_command_depart_list(c, nullptr, my_wizard_bot, local_list, single); - return; - } - else if (destination.empty()) { - c->Message(Chat::White, "A [destination] or [list] argument is required to use this command"); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToDepart(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->caster_class != Class::Wizard) - continue; - if (local_entry->single != single) - continue; - if (destination.compare(spells[local_entry->spell_id].teleport_zone)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} From 18b6fc2667fcc04a4b55c9ebd217d8ba6796f107 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 10 Nov 2024 23:10:13 -0600 Subject: [PATCH 21/97] adjust spell hold checks to rely on caster and Implement pet resist buffs and pet damage shields --- common/spdat.cpp | 10 ++++++++-- common/spdat.h | 8 +++++--- zone/bot.cpp | 19 +++++++++++++++++-- zone/bot_commands/spell_holds.cpp | 7 ++----- zone/botspellsai.cpp | 4 ++++ zone/mob.cpp | 24 +++++++++++++++++++----- 6 files changed, 55 insertions(+), 17 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index 48b6351fb..8211ecfdc 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -2874,14 +2874,16 @@ bool BOT_SPELL_TYPES_BENEFICIAL(uint16 spellType, uint8 cls) { case BotSpellTypes::Buff: case BotSpellTypes::Cure: case BotSpellTypes::GroupCures: - case BotSpellTypes::DamageShields: + case BotSpellTypes::DamageShields: case BotSpellTypes::InCombatBuffSong: case BotSpellTypes::OutOfCombatBuffSong: case BotSpellTypes::Pet: case BotSpellTypes::PetBuffs: case BotSpellTypes::PreCombatBuff: case BotSpellTypes::PreCombatBuffSong: - case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::ResistBuffs: case BotSpellTypes::Resurrect: return true; case BotSpellTypes::InCombatBuff: @@ -2916,8 +2918,10 @@ bool BOT_SPELL_TYPES_OTHER_BENEFICIAL(uint16 spellType) { case BotSpellTypes::Cure: case BotSpellTypes::GroupCures: case BotSpellTypes::DamageShields: + case BotSpellTypes::PetDamageShields: case BotSpellTypes::PetBuffs: case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: return true; default: return false; @@ -3050,8 +3054,10 @@ bool IsClientBotSpellType(uint16 spellType) { case BotSpellTypes::Cure: case BotSpellTypes::GroupCures: case BotSpellTypes::DamageShields: + case BotSpellTypes::PetDamageShields: case BotSpellTypes::PetBuffs: case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: return true; default: return false; diff --git a/common/spdat.h b/common/spdat.h index b07bf7d96..60257a401 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -705,10 +705,12 @@ namespace BotSpellTypes constexpr uint16 PetVeryFastHeals = 49; constexpr uint16 PetHoTHeals = 50; constexpr uint16 DamageShields = 51; - constexpr uint16 ResistBuffs = 52; + constexpr uint16 ResistBuffs = 52; + constexpr uint16 PetDamageShields = 53; + constexpr uint16 PetResistBuffs = 54; - constexpr uint16 START = BotSpellTypes::Nuke; // Do not remove or change this - constexpr uint16 END = BotSpellTypes::ResistBuffs; // Do not remove this, increment as needed + constexpr uint16 START = BotSpellTypes::Nuke; // Do not remove or change this + constexpr uint16 END = BotSpellTypes::PetResistBuffs; // Do not remove this, increment as needed } const uint32 SPELL_TYPES_DETRIMENTAL = (SpellType_Nuke | SpellType_Root | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Charm | SpellType_Debuff | SpellType_Slow); diff --git a/zone/bot.cpp b/zone/bot.cpp index e6af415b3..ad8bbacc5 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9516,7 +9516,9 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { case BotSpellTypes::PetBuffs: case BotSpellTypes::PreCombatBuff: case BotSpellTypes::DamageShields: - case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: if ( !( spells[spellid].target_type == ST_Target || @@ -10411,6 +10413,14 @@ uint16 Bot::GetDefaultSpellTypeIdlePriority(uint16 spellType, uint8 botClass, ui case BotSpellTypes::PreCombatBuffSong: priority = 23; + break; + case BotSpellTypes::PetResistBuffs: + priority = 24; + + break; + case BotSpellTypes::PetDamageShields: + priority = 25; + break; default: priority = 0; //unused @@ -10872,7 +10882,9 @@ uint16 Bot::GetSpellListSpellType(uint16 spellType) { case BotSpellTypes::Buff: case BotSpellTypes::PetBuffs: case BotSpellTypes::DamageShields: - case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: return BotSpellTypes::Buff; case BotSpellTypes::AEMez: case BotSpellTypes::Mez: @@ -10932,18 +10944,21 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spellType, uint16 spellid) { switch (spellType) { case BotSpellTypes::Buff: + case BotSpellTypes::PetBuffs: if (IsResistanceOnlySpell(spellid) || IsDamageShieldOnlySpell(spellid) || IsDamageShieldAndResistanceSpellOnly(spellid)) { return false; } return true; case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: if (IsResistanceOnlySpell(spellid)) { return true; } return false; case BotSpellTypes::DamageShields: + case BotSpellTypes::PetDamageShields: if (IsDamageShieldOnlySpell(spellid) || IsDamageShieldAndResistanceSpellOnly(spellid)) { return true; } diff --git a/zone/bot_commands/spell_holds.cpp b/zone/bot_commands/spell_holds.cpp index effd97a7a..fd17749d1 100644 --- a/zone/bot_commands/spell_holds.cpp +++ b/zone/bot_commands/spell_holds.cpp @@ -9,15 +9,12 @@ void bot_command_spell_holds(Client* c, const Seperator* sep) if (helper_is_help_or_usage(sep->arg[1])) { std::vector description = { - "Toggles whether or not bots can cast or receive certain spell types" + "Toggles whether or not bots can cast certain spell types" }; std::vector notes = { - "- All pet types are based off the pet's owner's setting", - "- Any remaining types use the owner's setting when a pet is the target", - "- All Heals, Cures, Buffs (DS and resists included) are based off the target's setting, not the caster", - "- e.g., BotA is healing BotB using BotB's settings", + "- All pet types are based off the pet owner's setting when a pet is the target" }; std::vector example_format = diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index d7e633822..b0ca5edf4 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -125,7 +125,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType) { case BotSpellTypes::PetBuffs: case BotSpellTypes::PreCombatBuff: case BotSpellTypes::DamageShields: + case BotSpellTypes::PetDamageShields: case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: if (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) { return false; } @@ -2039,7 +2041,9 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spellType) //TODO bot rewrite - adj case BotSpellTypes::Buff: case BotSpellTypes::PetBuffs: case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: case BotSpellTypes::DamageShields: + case BotSpellTypes::PetDamageShields: return RuleI(Bots, PercentChanceToCastBuff); case BotSpellTypes::Escape: return RuleI(Bots, PercentChanceToCastEscape); diff --git a/zone/mob.cpp b/zone/mob.cpp index d472eb7f7..ef6659bed 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -8886,6 +8886,12 @@ std::string Mob::GetSpellTypeNameByID(uint16 spellType) { case BotSpellTypes::ResistBuffs: spellTypeName = "Resist Buff"; break; + case BotSpellTypes::PetDamageShields: + spellTypeName = "Pet Damage Shield"; + break; + case BotSpellTypes::PetResistBuffs: + spellTypeName = "Pet Resist Buff"; + break; default: break; } @@ -9056,6 +9062,12 @@ std::string Mob::GetSpellTypeShortNameByID(uint16 spellType) { case BotSpellTypes::ResistBuffs: spellTypeName = "resistbuffs"; break; + case BotSpellTypes::PetDamageShields: + spellTypeName = "petdamageshields"; + break; + case BotSpellTypes::PetResistBuffs: + spellTypeName = "petresistbuffs"; + break; default: break; } @@ -9335,7 +9347,7 @@ uint8 Mob::GetDefaultSpellMaxThreshold(uint16 spellType, uint8 stance) { case BotSpellTypes::Buff: case BotSpellTypes::Charm: case BotSpellTypes::Cure: - case BotSpellTypes::DamageShields: + case BotSpellTypes::DamageShields: case BotSpellTypes::HateRedux: case BotSpellTypes::InCombatBuff: case BotSpellTypes::InCombatBuffSong: @@ -9346,6 +9358,8 @@ uint8 Mob::GetDefaultSpellMaxThreshold(uint16 spellType, uint8 stance) { case BotSpellTypes::PetBuffs: case BotSpellTypes::PreCombatBuff: case BotSpellTypes::PreCombatBuffSong: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::PetResistBuffs: case BotSpellTypes::ResistBuffs: case BotSpellTypes::Resurrect: return 100; @@ -9418,10 +9432,6 @@ bool Mob::GetUltimateSpellHold(uint16 spellType, Mob* tar) { return tar->GetOwner()->GetSpellHold(GetPetSpellType(spellType)); } - if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType) && tar->IsOfClientBot()) { - return tar->GetSpellHold(spellType); - } - return GetSpellHold(spellType); } @@ -9503,6 +9513,10 @@ uint16 Mob::GetPetSpellType(uint16 spellType) { return BotSpellTypes::PetHoTHeals; case BotSpellTypes::Buff: return BotSpellTypes::PetBuffs; + case BotSpellTypes::DamageShields: + return BotSpellTypes::PetDamageShields; + case BotSpellTypes::ResistBuffs: + return BotSpellTypes::PetResistBuffs; default: break; } From 351ada12a52463e7c8f438cde42d870798b35613 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 10 Nov 2024 23:13:04 -0600 Subject: [PATCH 22/97] correct GetBestBotMagicianPetSpell --- zone/bot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/bot.h b/zone/bot.h index e0d857880..f29d86235 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -595,7 +595,7 @@ public: static Mob* GetFirstIncomingMobToMez(Bot* botCaster, int16 spellid, uint16 spellType, bool AE = false); bool IsValidMezTarget(Mob* owner, Mob* npc, uint16 spellid); static BotSpell GetBestBotSpellForMez(Bot* botCaster, uint16 spellType = BotSpellTypes::Mez); - static BotSpell GetBestBotMagicianPetSpell(Bot* botCaster, uint16 spellType = BotSpellTypes::Mez); + static BotSpell GetBestBotMagicianPetSpell(Bot* botCaster, uint16 spellType = BotSpellTypes::Pet); static std::string GetBotMagicianPetType(Bot* botCaster); static BotSpell GetBestBotSpellForNukeByTargetType(Bot* botCaster, SpellTargetType targetType, uint16 spellType, bool AE = false, Mob* tar = nullptr); static BotSpell GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType targetType, uint16 spellType, bool AE = false, Mob* tar = nullptr); From 8fa429c02d5cfb8d8c7b67c94f4a9255dd2cbee3 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 10 Nov 2024 23:13:27 -0600 Subject: [PATCH 23/97] add sanity check to campCount on ^camp --- zone/bot_commands/bot.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zone/bot_commands/bot.cpp b/zone/bot_commands/bot.cpp index ba0ae10dc..da2352a19 100644 --- a/zone/bot_commands/bot.cpp +++ b/zone/bot_commands/bot.cpp @@ -63,7 +63,9 @@ void bot_command_camp(Client *c, const Seperator *sep) ++campCount; } - c->Message(Chat::White, "%i of your bots have been camped.", campCount); + if (campCount) { + c->Message(Chat::White, "%i of your bots have been camped.", campCount); + } } void bot_command_clone(Client *c, const Seperator *sep) From c50d72ccf0c9eaaaff5961e595eb71f61c6dc5df Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 10 Nov 2024 23:14:34 -0600 Subject: [PATCH 24/97] add PercentChanceToCast rules for AE and group spells --- common/ruletypes.h | 2 ++ zone/botspellsai.cpp | 32 ++++++++++++++++++++------------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index c1e46b5ae..068fd0044 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -788,7 +788,9 @@ RULE_INT(Bots, SpellResistLimit, 150, "150 Default. This is the resist cap where RULE_INT(Bots, StunCastChanceIfCasting, 50, "50 Default. Chance for non-Paladins to cast a stun spell if the target is casting.") RULE_INT(Bots, StunCastChanceNormal, 15, "15 Default. Chance for non-Paladins to cast a stun spell on the target.") RULE_INT(Bots, StunCastChancePaladins, 75, "75 Default. Chance for Paladins to cast a stun spell if the target is casting.") +RULE_INT(Bots, PercentChanceToCastAEs, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") RULE_INT(Bots, PercentChanceToCastNuke, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastGroupHeal, 90, "The chance for a bot to attempt to cast the given spell type in combat. Default 90%.") RULE_INT(Bots, PercentChanceToCastHeal, 90, "The chance for a bot to attempt to cast the given spell type in combat. Default 90%.") RULE_INT(Bots, PercentChanceToCastRoot, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") RULE_INT(Bots, PercentChanceToCastBuff, 90, "The chance for a bot to attempt to cast the given spell type in combat. Default 90%.") diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index b0ca5edf4..37decd779 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -2034,6 +2034,22 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spellType) //TODO bot rewrite - adj case BotSpellTypes::AENukes: case BotSpellTypes::AERains: case BotSpellTypes::AEStun: + case BotSpellTypes::AESnare: + case BotSpellTypes::AEMez: + case BotSpellTypes::AESlow: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::AEFear: + case BotSpellTypes::AEDispel: + case BotSpellTypes::AEDoT: + case BotSpellTypes::AELifetap: + case BotSpellTypes::AERoot: + case BotSpellTypes::PBAENuke: + return RuleI(Bots, PercentChanceToCastAEs); + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::GroupCures: + return RuleI(Bots, PercentChanceToCastGroupHeal); case BotSpellTypes::Nuke: return RuleI(Bots, PercentChanceToCastNuke); case BotSpellTypes::Root: @@ -2049,7 +2065,6 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spellType) //TODO bot rewrite - adj return RuleI(Bots, PercentChanceToCastEscape); case BotSpellTypes::Lifetap: return RuleI(Bots, PercentChanceToCastLifetap); - case BotSpellTypes::AESnare: case BotSpellTypes::Snare: return RuleI(Bots, PercentChanceToCastSnare); case BotSpellTypes::DOT: @@ -2057,30 +2072,23 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spellType) //TODO bot rewrite - adj case BotSpellTypes::Dispel: return RuleI(Bots, PercentChanceToCastDispel); case BotSpellTypes::InCombatBuff: - return RuleI(Bots, PercentChanceToCastInCombatBuff); - case BotSpellTypes::AEMez: + return RuleI(Bots, PercentChanceToCastInCombatBuff); case BotSpellTypes::Mez: - return RuleI(Bots, PercentChanceToCastMez); - case BotSpellTypes::AESlow: + return RuleI(Bots, PercentChanceToCastMez); case BotSpellTypes::Slow: - return RuleI(Bots, PercentChanceToCastSlow); - case BotSpellTypes::AEDebuff: + return RuleI(Bots, PercentChanceToCastSlow); case BotSpellTypes::Debuff: return RuleI(Bots, PercentChanceToCastDebuff); case BotSpellTypes::Cure: return RuleI(Bots, PercentChanceToCastCure); case BotSpellTypes::HateRedux: - return RuleI(Bots, PercentChanceToCastHateRedux); - case BotSpellTypes::AEFear: + return RuleI(Bots, PercentChanceToCastHateRedux); case BotSpellTypes::Fear: return RuleI(Bots, PercentChanceToCastFear); case BotSpellTypes::RegularHeal: case BotSpellTypes::CompleteHeal: case BotSpellTypes::FastHeals: case BotSpellTypes::VeryFastHeals: - case BotSpellTypes::GroupHeals: - case BotSpellTypes::GroupCompleteHeals: - case BotSpellTypes::GroupHoTHeals: case BotSpellTypes::HoTHeals: case BotSpellTypes::PetRegularHeals: case BotSpellTypes::PetCompleteHeals: From 9dd11a598fea242810cf31fd31941b1fd5421612 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 10 Nov 2024 23:15:08 -0600 Subject: [PATCH 25/97] Add AllowAIMez to allow bot auto mez to be toggled --- common/ruletypes.h | 1 + zone/botspellsai.cpp | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/common/ruletypes.h b/common/ruletypes.h index 068fd0044..5cbbbd6e9 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -870,6 +870,7 @@ RULE_BOOL(Bots, DoResponseAnimations, true, "If enabled bots will do animations RULE_INT(Bots, DefaultFollowDistance, 20, "Default 20. Distance a bot will follow behind.") RULE_INT(Bots, MaxFollowDistance, 300, "Default 300. Max distance a bot can be set to follow behind.") RULE_INT(Bots, MaxDistanceRanged, 300, "Default 300. Max distance a bot can be set to ranged.") +RULE_BOOL(Bots, AllowAIMez, true, "If enabled bots will automatically mez/AE mez eligible targets.") RULE_CATEGORY_END() RULE_CATEGORY(Chat) diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 37decd779..5adbdb3e9 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -639,6 +639,10 @@ bool Bot::AI_PursueCastCheck() { continue; } + if (RuleB(Bots, AllowAIMez) && (currentCast.spellType == BotSpellTypes::AEMez || currentCast.spellType == BotSpellTypes::Mez)) { + continue; + } + if (currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. continue; } @@ -698,6 +702,10 @@ bool Bot::AI_IdleCastCheck() { continue; } + if (RuleB(Bots, AllowAIMez) && (currentCast.spellType == BotSpellTypes::AEMez || currentCast.spellType == BotSpellTypes::Mez)) { + continue; + } + if (currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. continue; } @@ -744,6 +752,10 @@ bool Bot::AI_EngagedCastCheck() { continue; } + if (RuleB(Bots, AllowAIMez) && (currentCast.spellType == BotSpellTypes::AEMez || currentCast.spellType == BotSpellTypes::Mez)) { + continue; + } + if (currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. continue; } From 8989c6f21bce3645a8051cfcf6052f25d76230d0 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 10 Nov 2024 23:31:59 -0600 Subject: [PATCH 26/97] apply ranged setting on spawn to show correct weapons --- zone/bot.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zone/bot.cpp b/zone/bot.cpp index ad8bbacc5..2653ebabd 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -3395,6 +3395,10 @@ bool Bot::Spawn(Client* botCharacterOwner) { } } + if (IsBotRanged()) { + ChangeBotRangedWeapons(true); + } + if (auto raid = entity_list.GetRaidByBotName(GetName())) { // Safety Check to confirm we have a valid raid auto owner = GetBotOwner(); From 464c69190d8de89c9050fd12f7ee228583196274 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 10 Nov 2024 23:35:28 -0600 Subject: [PATCH 27/97] correct and tweak all combat positioning and combat range --- common/ruletypes.h | 8 +-- zone/bot.cpp | 146 +++++++++++++++++++++++--------------- zone/bot.h | 4 +- zone/bot_commands/bot.cpp | 1 + 4 files changed, 94 insertions(+), 65 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 5cbbbd6e9..65239a07c 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -836,15 +836,15 @@ RULE_INT(Bots, StackSizeMin, 20, "20 Default. -1 to disable and use default max RULE_INT(Bots, HasOrMayGetAggroThreshold, 90, "90 Default. Percent threshold of total hate where bots will stop casting spells that generate hate if they are set to try to not pull aggro via spells.") RULE_BOOL(Bots, UseFlatNormalMeleeRange, false, "False Default. If true, bots melee distance will be a flat distance set by Bots:NormalMeleeRangeDistance.") RULE_REAL(Bots, NormalMeleeRangeDistance, 0.75, "If UseFlatNormalMeleeRange is enabled, multiplier of the max melee range at which a bot will stand in melee combat. 0.75 Recommended, max melee for all abilities to land.") -RULE_REAL(Bots, PercentMinMeleeDistance, 0.60, "Multiplier of the max melee range - Minimum distance from target a bot will stand while in melee combat before trying to adjust. 0.60 Recommended.") +RULE_REAL(Bots, PercentMinMeleeDistance, 0.75, "Multiplier of the their melee range - Minimum distance from target a bot will stand while in melee combat before trying to adjust. 0.60 Recommended.") RULE_REAL(Bots, MaxDistanceForMelee, 20, "Maximum distance bots will stand for melee. Default 20 to allow all special attacks to land.") RULE_REAL(Bots, TauntNormalMeleeRangeDistance, 0.50, "Multiplier of the max melee range at which a taunting bot will stand in melee combat. 0.50 Recommended, closer than others .") -RULE_REAL(Bots, PercentTauntMinMeleeDistance, 0.25, "Multiplier of max melee range - Minimum distance from target a taunting bot will stand while in melee combat before trying to adjust. 0.25 Recommended.") +RULE_REAL(Bots, PercentTauntMinMeleeDistance, 0.40, "Multiplier of their melee range - Minimum distance from target a taunting bot will stand while in melee combat before trying to adjust. 0.25 Recommended.") RULE_REAL(Bots, PercentMaxMeleeRangeDistance, 0.95, "Multiplier of the max melee range at which a bot will stand in melee combat. 0.95 Recommended, max melee while disabling special attacks/taunt.") RULE_REAL(Bots, PercentMinMaxMeleeRangeDistance, 0.75, "Multiplier of the closest max melee range at which a bot will stand in melee combat before trying to adjust. 0.75 Recommended, max melee while disabling special attacks/taunt.") RULE_BOOL(Bots, TauntingBotsFollowTopHate, true, "True Default. If true, bots that are taunting will attempt to stick with whoever currently is top hate.") -RULE_REAL(Bots, DistanceTauntingBotsStickMainHate, 25.00, "If TauntingBotsFollowTopHate is enabled, this is the distance bots will try to stick to whoever currently is Top Hate.") -RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, false, "False Default. If true, when bots are at max melee distance, special abilities including taunt will be disabled.") +RULE_INT(Bots, DistanceTauntingBotsStickMainHate, 10, "If TauntingBotsFollowTopHate is enabled, this is the distance bots will try to stick to whoever currently is Top Hate.") +RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "True Default. If true, when bots are at max melee distance, special abilities including taunt will be disabled.") RULE_INT(Bots, MinJitterTimer, 500, "Minimum ms between bot movement jitter checks.") RULE_INT(Bots, MaxJitterTimer, 2500, "Maximum ms between bot movement jitter checks. Set to 0 to disable timer checks.") RULE_BOOL(Bots, PreventBotCampOnFD, true, "True Default. If true, players will not be able to camp bots while feign death.") diff --git a/zone/bot.cpp b/zone/bot.cpp index 2653ebabd..c1d3e59ad 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2163,7 +2163,8 @@ void Bot::AI_Process() // COMBAT RANGE CALCS bool atCombatRange = false; - bool behindMob = false; + bool behindMob = BehindMob(tar, GetX(), GetY()); + bool frontMob = InFrontMob(tar, GetX(), GetY()); uint8 stopMeleeLevel = GetStopMeleeLevel(); const EQ::ItemInstance* p_item; const EQ::ItemInstance* s_item; @@ -2230,7 +2231,7 @@ void Bot::AI_Process() } if (!jitterCooldown && AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) { - DoCombatPositioning(tar, Goal, stopMeleeLevel, tar_distance, melee_distance_min, melee_distance, melee_distance_max, behindMob); + DoCombatPositioning(tar, Goal, stopMeleeLevel, tar_distance, melee_distance_min, melee_distance, melee_distance_max, behindMob, frontMob); return; } else { @@ -2363,10 +2364,10 @@ bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, g else { Goal = follow_mob->GetPosition(); } + float destination_distance = DistanceSquared(GetPosition(), Goal); if ((!bot_owner->GetBotPulling() || PULLING_BOT) && (destination_distance > GetFollowDistance())) { - if (!IsRooted()) { if (rest_timer.Enabled()) { rest_timer.Disable(); @@ -2380,7 +2381,6 @@ bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, g else { if (IsMoving()) { - StopMoving(); return true; } @@ -2733,7 +2733,7 @@ bool Bot::TryEvade(Mob* tar) { return false; } -void Bot::CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, bool& behindMob, const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item, float& melee_distance_min, float& melee_distance, float& melee_distance_max, uint8 stopMeleeLevel) { +void Bot::CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, bool behindMob, const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item, float& melee_distance_min, float& melee_distance, float& melee_distance_max, uint8 stopMeleeLevel) { atCombatRange= false; p_item = GetBotItem(EQ::invslot::slotPrimary); @@ -2742,32 +2742,46 @@ void Bot::CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, bo bool backstab_weapon = false; if (GetBehindMob()) { - behindMob = BehindMob(tar, GetX(), GetY()); // Can be separated for other future use if (GetClass() == Class::Rogue) { backstab_weapon = p_item && p_item->GetItemBackstabDamage(); } } // Calculate melee distances - CalcMeleeDistances(tar, p_item, s_item, behindMob, backstab_weapon, melee_distance_min, melee_distance, melee_distance_max, stopMeleeLevel); + CalcMeleeDistances(tar, p_item, s_item, backstab_weapon, behindMob, melee_distance_min, melee_distance, melee_distance_max, stopMeleeLevel); - //LogTestDebugDetail("{} is {} {}. They are currently {} away {} to be between [{} - {}] away. MMR is {}." - // , GetCleanName() - // , (tar_distance < melee_distance_min ? "too close to" : (tar_distance <= melee_distance ? "within range of" : "too far away from")) - // , tar->GetCleanName() - // , tar_distance - // , (tar_distance <= melee_distance ? "but only needed" : "but need to be") - // , melee_distance_min - // , melee_distance - // , melee_distance_max - //); //deleteme + if (!GetCombatRoundForAlerts()) { + SetCombatRoundForAlerts(); + LogTestDebugDetail("{} says, 'I'm {} {}. I am currently {} away {} to be between [{} - {}] away. MMR is {}.'" + , GetCleanName() + , (tar_distance < melee_distance_min ? "too close to" : (tar_distance <= melee_distance ? "within range of" : "too far away from")) + , tar->GetCleanName() + , tar_distance + , (tar_distance <= melee_distance ? "but only needed" : "but need to be") + , melee_distance_min + , melee_distance + , melee_distance_max + ); //deleteme + LogTestDebugDetail("{} says, 'My stance is {} #{}, I am {} taunting. I am set to {} {}, {} at MMR, distanceranged {}, sml {} [{}]'" + , GetCleanName() + , Stance::GetName(GetBotStance()) + , GetBotStance() + , (IsTaunting() ? "currently" : "not") + , (BehindMob() ? "stay behind" : "not stay behind") + , tar->GetCleanName() + , (GetMaxMeleeRange() ? "I stay" : "I do not stay") + , GetBotDistanceRanged() + , GetStopMeleeLevel() + , ((GetLevel() <= GetStopMeleeLevel()) ? "disabled" : "enabled") + ); //deleteme + } if (tar_distance <= melee_distance) { atCombatRange = true; } } -void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_item, const EQ::ItemInstance* const& s_item, bool behindMob, bool backstab_weapon, float& melee_distance_min, float& melee_distance, float& melee_distance_max, uint8 stopMeleeLevel) { +void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_item, const EQ::ItemInstance* const& s_item, bool backstab_weapon, bool behindMob, float& melee_distance_min, float& melee_distance, float& melee_distance_max, uint8 stopMeleeLevel) { float size_mod = GetSize(); float other_size_mod = tar->GetSize(); @@ -2880,27 +2894,26 @@ void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_it melee_distance = RuleR(Bots, MaxDistanceForMelee); } - melee_distance_min = melee_distance_max * RuleR(Bots, PercentMinMeleeDistance); + melee_distance_min = melee_distance * RuleR(Bots, PercentMinMeleeDistance); - if (taunting) { - melee_distance_min = melee_distance_max * RuleR(Bots, PercentTauntMinMeleeDistance); - melee_distance = melee_distance_max * RuleR(Bots, TauntNormalMeleeRangeDistance); + if (IsTaunting()) { + melee_distance_min = melee_distance * RuleR(Bots, PercentTauntMinMeleeDistance); + melee_distance = melee_distance * RuleR(Bots, TauntNormalMeleeRangeDistance); } bool isStopMeleeLevel = GetLevel() >= stopMeleeLevel; - if (!taunting && !IsBotRanged() && !isStopMeleeLevel && GetMaxMeleeRange()) { - melee_distance = melee_distance_max * RuleR(Bots, PercentMaxMeleeRangeDistance); + if (!IsTaunting() && !IsBotRanged() && !isStopMeleeLevel && GetMaxMeleeRange()) { melee_distance_min = melee_distance_max * RuleR(Bots, PercentMinMaxMeleeRangeDistance); + melee_distance = melee_distance_max * RuleR(Bots, PercentMaxMeleeRangeDistance); } if (isStopMeleeLevel && !IsBotRanged()) { float desiredRange = GetBotDistanceRanged(); - melee_distance_min = std::min(melee_distance, (desiredRange / 2)); + melee_distance_min = std::max(melee_distance, (desiredRange / 2)); melee_distance = std::max((melee_distance + 1), desiredRange); } - /* Archer Checks*/ if (IsBotRanged()) { float minDistance = RuleI(Combat, MinRangedAttackDist); float maxDistance = GetBotRangedValue(); @@ -3003,7 +3016,6 @@ Mob* Bot::GetBotTarget(Client* bot_owner) } bool Bot::ReturningFlagChecks(Client* bot_owner, float fm_distance) {// Need to make it back to group before clearing return flag - if (fm_distance <= GetFollowDistance()) { // Once we're back, clear blocking flags so everyone else can join in @@ -3022,13 +3034,12 @@ bool Bot::ReturningFlagChecks(Client* bot_owner, float fm_distance) {// Need to WipeHateList(); return false; } + return true; } bool Bot::PullingFlagChecks(Client* bot_owner) { - if (!GetTarget()) { - WipeHateList(); SetTarget(nullptr); SetPullingFlag(false); @@ -3042,7 +3053,6 @@ bool Bot::PullingFlagChecks(Client* bot_owner) { return false; } else if (GetTarget()->GetHateList().size()) { - WipeHateList(); SetTarget(nullptr); SetPullingFlag(false); @@ -3064,7 +3074,6 @@ bool Bot::PullingFlagChecks(Client* bot_owner) { } void Bot::HealRotationChecks() { - if (IsMyHealRotationSet()) { if (AIHealRotation(HealRotationTarget(), UseHealRotationFastHeals())) { m_member_of_heal_rotation->SetMemberIsCasting(this); @@ -3079,7 +3088,6 @@ void Bot::HealRotationChecks() { } bool Bot::IsAIProcessValid(const Client* bot_owner, const Group* bot_group, const Raid* raid) { - if (!bot_owner || (!bot_group && !raid) || !IsAIControlled()) { return false; } @@ -3089,11 +3097,11 @@ bool Bot::IsAIProcessValid(const Client* bot_owner, const Group* bot_group, cons SetBotOwner(nullptr); return false; } + return true; } bool Bot::CheckIfCasting(float fm_distance) { - if (IsCasting()) { if (IsHealRotationMember() && m_member_of_heal_rotation->CastingOverride() && @@ -3105,12 +3113,10 @@ bool Bot::CheckIfCasting(float fm_distance) { InterruptSpell(); } else if (AmICastingForHealRotation() && m_member_of_heal_rotation->CastingMember() == this) { - AdvanceHealRotation(false); return true; } else if (GetClass() != Class::Bard) { - if (IsEngaged()) { return true; } @@ -3128,13 +3134,12 @@ bool Bot::CheckIfCasting(float fm_distance) { else if (IsHealRotationMember()) { m_member_of_heal_rotation->SetMemberIsCasting(this, false); } + return false; } bool Bot::CheckIfIncapacitated() { - if (GetPauseAI() || IsStunned() || IsMezzed() || (GetAppearance() == eaDead)) { - if (IsCasting()) { InterruptSpell(); } @@ -3143,6 +3148,7 @@ bool Bot::CheckIfIncapacitated() { AdvanceHealRotation(false); m_member_of_heal_rotation->SetMemberIsCasting(this, false); } + return true; } @@ -3188,29 +3194,29 @@ void Bot::SetBerserkState() {// Berserk updates should occur if primary AI crite Mob* Bot::SetFollowMob(Client* leash_owner) { Mob* follow_mob = entity_list.GetMob(GetFollowID()); - if (!follow_mob) { + if (!follow_mob) { follow_mob = leash_owner; SetFollowID(leash_owner->GetID()); } + return follow_mob; } Client* Bot::SetLeashOwner(Client* bot_owner, Group* bot_group, Raid* raid, uint32 r_group) const { - Client* leash_owner = nullptr; + if (raid && r_group < MAX_RAID_GROUPS && raid->GetGroupLeader(r_group)) { leash_owner = raid->GetGroupLeader(r_group) && raid->GetGroupLeader(r_group)->IsClient() ? raid->GetGroupLeader(r_group)->CastToClient() : bot_owner; - } else if (bot_group) { leash_owner = (bot_group->GetLeader() && bot_group->GetLeader()->IsClient() ? bot_group->GetLeader()->CastToClient() : bot_owner); - } else { leash_owner = bot_owner; } + return leash_owner; } @@ -3235,6 +3241,7 @@ void Bot::SetOwnerTarget(Client* bot_owner) { AddToHateList(attack_target, 1); SetTarget(attack_target); SetAttackingFlag(); + if (GetPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { GetPet()->WipeHateList(); GetPet()->AddToHateList(attack_target, 1); @@ -3275,8 +3282,8 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) { SetTarget(pull_target); SetPullingFlag(); bot_owner->SetBotPulling(); - if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) { + if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) { GetPet()->WipeHateList(); GetPet()->SetTarget(nullptr); m_previous_pet_order = GetPet()->GetPetOrder(); @@ -5132,7 +5139,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { } } - if (taunting && target->IsNPC() && taunt_time) { + if (IsTaunting() && target->IsNPC() && taunt_time) { if (GetTarget() && GetTarget()->GetHateTop() && GetTarget()->GetHateTop() != this) { BotGroupSay( this, @@ -6758,6 +6765,7 @@ void Bot::Zone() { bool Bot::IsAtRange(Mob *target) { bool result = false; + if (target) { float range = (GetBotRangedValue() + 5.0); range *= range; @@ -7501,6 +7509,7 @@ void EntityList::ScanCloseClientMobs(std::unordered_map& close_mob } float distance = DistanceSquared(scanning_mob->GetPosition(), mob->GetPosition()); + if (distance <= scan_range) { close_mobs.insert(std::pair(mob->GetID(), mob)); } @@ -9480,7 +9489,7 @@ bool Bot::CastChecks(uint16 spellid, Mob* tar, uint16 spellType, bool doPrecheck return false; } - if (!IsCommandedSpell() && !taunting && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting(), spellid) && !tar->IsFleeing()) { + if (!IsCommandedSpell() && !IsTaunting() && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting(), spellid) && !tar->IsFleeing()) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to HasOrMayGetAggro.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme return false; } @@ -10831,7 +10840,7 @@ bool Bot::AttemptAICastSpell(uint16 spellType) { Mob* tar = GetTarget(); - if (!taunting && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting())) { + if (!IsTaunting() && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting())) { LogBotPreChecksDetail("{} says, 'Cancelling cast of [{}] due to GetSpellTypeAggroCheck and HasOrMayGetAggro.'", GetCleanName(), GetSpellTypeNameByID(spellType)); //deleteme return result; } @@ -11058,63 +11067,81 @@ void Bot::SetCombatJitter() { } } -void Bot::DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stopMeleeLevel, float tar_distance, float melee_distance_min, float melee_distance, float melee_distance_max, bool behindMob) { +void Bot::DoCombatPositioning( + Mob* tar, + glm::vec3 Goal, + bool stopMeleeLevel, + float tar_distance, + float melee_distance_min, + float melee_distance, + float melee_distance_max, + bool behindMob, + bool frontMob +) { + //LogTestDebug("{} says, 'DoCombatPositioning. {} #{}", GetCleanName(), __FILE__, __LINE__); //deleteme + if (HasTargetReflection()) { - if (!taunting && !tar->IsFeared() && !tar->IsStunned()) { + if (!IsTaunting() && !tar->IsFeared() && !tar->IsStunned()) { if (TryEvade(tar)) { return; } } - - if (tar->IsRooted() && !taunting) { // Move non-taunters out of range - Above already checks if bot is targeted, otherwise they would stay + else if (tar->IsRooted() && !IsTaunting()) { // Move non-taunters out of range - Above already checks if bot is targeted, otherwise they would stay if (tar_distance <= melee_distance_max) { - if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 2), false, taunting)) { + if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 2), false, IsTaunting())) { RunToGoalWithJitter(Goal); + return; } } } - - if (taunting && tar_distance < melee_distance_min) { // Back up any bots that are too close - if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, taunting)) { + else if (IsTaunting() && ((tar_distance < melee_distance_min) || !frontMob)) { // Back up any bots that are too close + if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, IsTaunting())) { RunToGoalWithJitter(Goal); + return; } } } else { if (!tar->IsFeared()) { - if (taunting) { // Taunting adjustments + if (IsTaunting()) { // Taunting adjustments Mob* mobTar = tar->GetTarget(); + if (!mobTar || mobTar == nullptr) { DoFaceCheckNoJitter(tar); + return; } if (RuleB(Bots, TauntingBotsFollowTopHate)) { // If enabled, taunting bots will stick to top hate - if ((DistanceSquared(m_Position, mobTar->GetPosition()) > pow(RuleR(Bots, DistanceTauntingBotsStickMainHate), 2))) { + if (Distance(m_Position, mobTar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate)) { Goal = mobTar->GetPosition(); RunToGoalWithJitter(Goal); + return; } } else { // Otherwise, stick to any other bots that are taunting - if (mobTar->IsBot() && mobTar->CastToBot()->taunting && (DistanceSquared(m_Position, mobTar->GetPosition()) > pow(RuleR(Bots, DistanceTauntingBotsStickMainHate), 2))) { + if (mobTar->IsBot() && mobTar->CastToBot()->IsTaunting() && (Distance(m_Position, mobTar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate))) { Goal = mobTar->GetPosition(); RunToGoalWithJitter(Goal); + return; } } } - else if (tar_distance < melee_distance_min || (GetBehindMob() && !behindMob) || !HasRequiredLoSForPositioning(tar)) { // Regular adjustment - if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), taunting)) { + else if (tar_distance < melee_distance_min || (GetBehindMob() && !behindMob) || (IsTaunting() && !frontMob)|| !HasRequiredLoSForPositioning(tar)) { // Regular adjustment + if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), IsTaunting())) { RunToGoalWithJitter(Goal); + return; } } - else if (tar->IsEnraged() && !taunting && !stopMeleeLevel && !behindMob) { // Move non-taunting melee bots behind target during enrage + else if (tar->IsEnraged() && !IsTaunting() && !stopMeleeLevel && !behindMob) { // Move non-taunting melee bots behind target during enrage if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) { RunToGoalWithJitter(Goal); + return; } } @@ -11122,6 +11149,7 @@ void Bot::DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stopMeleeLevel, flo } DoFaceCheckNoJitter(tar); + return; } diff --git a/zone/bot.h b/zone/bot.h index f29d86235..ff3c7c129 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -943,7 +943,7 @@ public: Mob* tar, float tar_distance, bool& atCombatRange, - bool& behindMob, + bool behindMob, const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item, float& melee_distance_min, @@ -957,7 +957,7 @@ public: void SetCombatOutOfRangeJitterFlag(bool flag = true) { m_combat_out_of_range_jitter_flag = flag; } void SetCombatJitter(); void SetCombatOutOfRangeJitter(); - void DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stopMeleeLevel, float tar_distance, float melee_distance_min, float melee_distance, float melee_distance_max, bool behindMob); + void DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stopMeleeLevel, float tar_distance, float melee_distance_min, float melee_distance, float melee_distance_max, bool behindMob, bool frontMob); void DoFaceCheckWithJitter(Mob* tar); void DoFaceCheckNoJitter(Mob* tar); void RunToGoalWithJitter(glm::vec3 Goal); diff --git a/zone/bot_commands/bot.cpp b/zone/bot_commands/bot.cpp index da2352a19..f9a3ecfe9 100644 --- a/zone/bot_commands/bot.cpp +++ b/zone/bot_commands/bot.cpp @@ -1695,6 +1695,7 @@ void bot_command_toggle_ranged(Client *c, const Seperator *sep) else { bot_iter->SetBotRangedSetting(ranged_state); } + bot_iter->ChangeBotRangedWeapons(bot_iter->IsBotRanged()); } } From 6d97536e38c049b4d6a75ae5b1c28b1975b77f5a Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 12 Nov 2024 22:19:16 -0600 Subject: [PATCH 28/97] Add loregroup 0 bypass for lore conflicts for bots like clients --- zone/bot.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index c1d3e59ad..aa793bd8a 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -7168,8 +7168,9 @@ void Bot::CalcBotStats(bool showtext) { } bool Bot::CheckLoreConflict(const EQ::ItemData* item) { - if (!item || !(item->LoreFlag)) + if (!item || !(item->LoreFlag) || (item->LoreGroup == 0)) { return false; + } if (item->LoreGroup == -1) // Standard lore items; look everywhere except the shared bank, return the result return (m_inv.HasItem(item->ID, 0, invWhereWorn) != INVALID_INDEX); From 2e0840db72f50dd66ee701b471fa56fe07daf7dc Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:10:47 -0600 Subject: [PATCH 29/97] add bot camp timer to prevent /camp exploits --- common/ruletypes.h | 1 + zone/client.cpp | 2 ++ zone/client.h | 1 + zone/client_packet.cpp | 2 ++ zone/client_process.cpp | 4 ++++ 5 files changed, 10 insertions(+) diff --git a/common/ruletypes.h b/common/ruletypes.h index 65239a07c..ec8c60513 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -871,6 +871,7 @@ RULE_INT(Bots, DefaultFollowDistance, 20, "Default 20. Distance a bot will follo RULE_INT(Bots, MaxFollowDistance, 300, "Default 300. Max distance a bot can be set to follow behind.") RULE_INT(Bots, MaxDistanceRanged, 300, "Default 300. Max distance a bot can be set to ranged.") RULE_BOOL(Bots, AllowAIMez, true, "If enabled bots will automatically mez/AE mez eligible targets.") +RULE_INT(Bots, CampTimer, 25, "Number of seconds after /camp has begun before bots camp out.") RULE_CATEGORY_END() RULE_CATEGORY(Chat) diff --git a/zone/client.cpp b/zone/client.cpp index a03e6f931..9d54f0b3d 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -148,6 +148,7 @@ Client::Client(EQStreamInterface *ieqs) : Mob( ), hpupdate_timer(2000), camp_timer(29000), + bot_camp_timer((RuleI(Bots, CampTimer) * 1000)), process_timer(100), consume_food_timer(CONSUMPTION_TIMER), zoneinpacket_timer(1000), @@ -252,6 +253,7 @@ Client::Client(EQStreamInterface *ieqs) : Mob( fishing_timer.Disable(); dead_timer.Disable(); camp_timer.Disable(); + bot_camp_timer.Disable(); autosave_timer.Disable(); GetMercTimer()->Disable(); instalog = false; diff --git a/zone/client.h b/zone/client.h index 71310dd84..77cf9a574 100644 --- a/zone/client.h +++ b/zone/client.h @@ -2036,6 +2036,7 @@ private: PTimerList p_timers; //persistent timers Timer hpupdate_timer; Timer camp_timer; + Timer bot_camp_timer; Timer process_timer; Timer consume_food_timer; Timer zoneinpacket_timer; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 67e49d815..648251a7b 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -4341,6 +4341,7 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app) return; } camp_timer.Start(29000, true); + bot_camp_timer.Start((RuleI(Bots, CampTimer) * 1000), true); return; } @@ -14760,6 +14761,7 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app) SetFeigned(false); BindWound(this, false, true); camp_timer.Disable(); + bot_camp_timer.Disable(); } else if (sa->parameter == Animation::Sitting) { SetAppearance(eaSitting); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index d0dab4c27..204312754 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -193,6 +193,10 @@ bool Client::Process() { return false; //delete client } + if (bot_camp_timer.Check()) { + Bot::BotOrderCampAll(this); + } + if (camp_timer.Check()) { Raid *myraid = entity_list.GetRaidByClient(this); if (myraid) { From 1e7739012f4760aa952bfcd6d36bb28fe1fde9b0 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 13 Nov 2024 22:19:11 -0600 Subject: [PATCH 30/97] Make command errors/failures yellow --- zone/bot_command.cpp | 46 ++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index b47edd5e3..81f7b21a1 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1237,7 +1237,7 @@ LinkedList cleanup_bot_command_list; int bot_command_not_avail(Client *c, const char *message) { - c->Message(Chat::White, "Bot commands not available."); + c->Message(Chat::Yellow, "Bot commands not available."); return -1; } @@ -1540,7 +1540,7 @@ int bot_command_real_dispatch(Client *c, const char *message) BotCommandRecord *cur = bot_command_list[cstr]; if(c->Admin() < cur->access){ - c->Message(Chat::White, "Your access level is not high enough to use this bot command."); + c->Message(Chat::Yellow, "Your access level is not high enough to use this bot command."); return(-1); } @@ -1569,13 +1569,13 @@ bool helper_bot_appearance_fail(Client *bot_owner, Bot *my_bot, BCEnum::AFType f { switch (fail_type) { case BCEnum::AFT_Value: - bot_owner->Message(Chat::White, "Failed to change '%s' for %s due to invalid value for this command", type_desc, my_bot->GetCleanName()); + bot_owner->Message(Chat::Yellow, "Failed to change '%s' for %s due to invalid value for this command", type_desc, my_bot->GetCleanName()); return true; case BCEnum::AFT_GenderRace: - bot_owner->Message(Chat::White, "Failed to change '%s' for %s due to invalid bot gender and/or race for this command", type_desc, my_bot->GetCleanName()); + bot_owner->Message(Chat::Yellow, "Failed to change '%s' for %s due to invalid bot gender and/or race for this command", type_desc, my_bot->GetCleanName()); return true; case BCEnum::AFT_Race: - bot_owner->Message(Chat::White, "Failed to change '%s' for %s due to invalid bot race for this command", type_desc, my_bot->GetCleanName()); + bot_owner->Message(Chat::Yellow, "Failed to change '%s' for %s due to invalid bot race for this command", type_desc, my_bot->GetCleanName()); return true; default: return false; @@ -1587,7 +1587,7 @@ void helper_bot_appearance_form_final(Client *bot_owner, Bot *my_bot) if (!MyBots::IsMyBot(bot_owner, my_bot)) return; if (!my_bot->Save()) { - bot_owner->Message(Chat::White, "Failed to save appearance change for %s due to unknown cause...", my_bot->GetCleanName()); + bot_owner->Message(Chat::Yellow, "Failed to save appearance change for %s due to unknown cause...", my_bot->GetCleanName()); return; } @@ -1631,7 +1631,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas if (!Bot::IsValidName(bot_name)) { bot_owner->Message( - Chat::White, + Chat::Yellow, fmt::format( "'{}' is an invalid name. You may only use characters 'A-Z' or 'a-z' and it must be between 4 and 15 characters. Mixed case {} allowed.", bot_name, RuleB(Bots, AllowCamelCaseNames) ? "is" : "is not" @@ -1643,7 +1643,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas bool available_flag = false; if (!database.botdb.QueryNameAvailablity(bot_name, available_flag)) { bot_owner->Message( - Chat::White, + Chat::Yellow, fmt::format( "'{}' is already in use or an invalid name.", bot_name @@ -1654,7 +1654,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas if (!available_flag) { bot_owner->Message( - Chat::White, + Chat::Yellow, fmt::format( "The name '{}' is already being used. Please choose a different name", bot_name @@ -1668,7 +1668,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas const std::string bot_class_name = GetClassIDName(bot_class); bot_owner->Message( - Chat::White, + Chat::Yellow, fmt::format( "{} {} is an invalid race-class combination, would you like to {} proper combinations for {}?", bot_race_name, @@ -1704,7 +1704,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas uint32 bot_count = 0; uint32 bot_class_count = 0; if (!database.botdb.QueryBotCount(bot_owner->CharacterID(), bot_class, bot_count, bot_class_count)) { - bot_owner->Message(Chat::White, "Failed to query bot count."); + bot_owner->Message(Chat::Yellow, "Failed to query bot count."); return bot_id; } @@ -1721,7 +1721,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas message = "You cannot create any bots."; } - bot_owner->Message(Chat::White, message.c_str()); + bot_owner->Message(Chat::Yellow, message.c_str()); return bot_id; } @@ -1742,7 +1742,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas ); } - bot_owner->Message(Chat::White, message.c_str()); + bot_owner->Message(Chat::Yellow, message.c_str()); return bot_id; } @@ -1753,7 +1753,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas bot_owner->GetLevel() < bot_character_level ) { bot_owner->Message( - Chat::White, + Chat::Yellow, fmt::format( "You must be level {} to use bots.", bot_character_level @@ -1769,7 +1769,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas bot_owner->GetLevel() < bot_character_level_class ) { bot_owner->Message( - Chat::White, + Chat::Yellow, fmt::format( "You must be level {} to use {} bots.", bot_character_level_class, @@ -1784,7 +1784,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas if (!my_bot->Save()) { bot_owner->Message( - Chat::White, + Chat::Yellow, fmt::format( "Failed to create '{}' due to unknown cause.", my_bot->GetCleanName() @@ -1918,7 +1918,7 @@ bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id, bool helper_command_disabled(Client* bot_owner, bool rule_value, const char* command) { if (!rule_value) { - bot_owner->Message(Chat::White, "Bot command %s is not enabled on this server.", command); + bot_owner->Message(Chat::Yellow, "Bot command %s is not enabled on this server.", command); return true; } @@ -1929,7 +1929,7 @@ bool helper_command_alias_fail(Client *bot_owner, const char* command_handler, c { auto alias_iter = bot_command_aliases.find(&alias[1]); if (alias_iter == bot_command_aliases.end() || alias_iter->second.compare(command)) { - bot_owner->Message(Chat::White, "Undefined linker usage in %s (%s)", command_handler, &alias[1]); + bot_owner->Message(Chat::Yellow, "Undefined linker usage in %s (%s)", command_handler, &alias[1]); return true; } @@ -1951,12 +1951,12 @@ void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_b } if (!druid_bot && !wizard_bot) { - bot_owner->Message(Chat::White, "No bots are capable of performing this action"); + bot_owner->Message(Chat::Yellow, "No bots are capable of performing this action"); return; } if (!local_list) { - bot_owner->Message(Chat::White, "There are no destinations you can be taken to."); + bot_owner->Message(Chat::Yellow, "There are no destinations you can be taken to."); return; } @@ -2030,7 +2030,7 @@ void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_b } if (!destination_count) { - bot_owner->Message(Chat::White, "There are no destinations you can be taken to."); + bot_owner->Message(Chat::Yellow, "There are no destinations you can be taken to."); } } @@ -2049,7 +2049,7 @@ bool helper_no_available_bots(Client *bot_owner, Bot *my_bot) if (!bot_owner) return true; if (!my_bot) { - bot_owner->Message(Chat::White, "No bots are capable of performing this action"); + bot_owner->Message(Chat::Yellow, "No bots are capable of performing this action"); return true; } @@ -2108,7 +2108,7 @@ bool helper_spell_check_fail(STBaseEntry* local_entry) bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::SpType spell_type) { if (!spell_list || spell_list->empty()) { - bot_owner->Message(Chat::White, "%s", required_bots_map[spell_type].c_str()); + bot_owner->Message(Chat::Yellow, "%s", required_bots_map[spell_type].c_str()); return true; } From 677a9fdfe0ebf4d4bd4b3da5d63271b9cb368731 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 14 Nov 2024 20:08:57 -0600 Subject: [PATCH 31/97] Add more checks to bot names to prevent spacing or invalid characters --- zone/bot.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index aa793bd8a..2e310f2d5 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -1323,7 +1323,7 @@ bool Bot::IsValidName() bool Bot::IsValidName(std::string& name) { - if (name.length() < 4 || name.length() > 15) { + if (name.empty() || name.length() < 4 || name.length() > 15) { return false; } @@ -1332,10 +1332,15 @@ bool Bot::IsValidName(std::string& name) } for (char c : name.substr(1)) { - if (!RuleB(Bots, AllowCamelCaseNames) && !islower(c)) { + if (c == '_') { return false; } - if (isdigit(c) || ispunct(c)) { + + if (!isalpha(c)) { + return false; + } + + if (!RuleB(Bots, AllowCamelCaseNames) && !islower(c)) { return false; } } From 5c73581f90a854212feaa6753534e6bd2dffdde5 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 14 Nov 2024 20:09:19 -0600 Subject: [PATCH 32/97] Add AllowBotEquipAnyClassGear to bot trades --- zone/bot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 2e310f2d5..03abe808e 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -4206,7 +4206,7 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* } if ( - !trade_instance->IsClassEquipable(GetClass()) || + (!trade_instance->IsClassEquipable(GetClass()) && !RuleB(Bots, AllowBotEquipAnyClassGear))|| GetLevel() < trade_instance->GetItem()->ReqLevel || (!trade_instance->IsRaceEquipable(GetBaseRace()) && !RuleB(Bots, AllowBotEquipAnyRaceGear)) ) { From e6aeb01ddf445ffd0bdcf387d46227fdd2d86da9 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Fri, 15 Nov 2024 13:26:00 -0600 Subject: [PATCH 33/97] update and expand ^itemuse options and add lore checks --- zone/bot_commands/item_use.cpp | 258 +++++++++++++++++++++++---------- 1 file changed, 185 insertions(+), 73 deletions(-) diff --git a/zone/bot_commands/item_use.cpp b/zone/bot_commands/item_use.cpp index 975d30b90..0db5983dc 100644 --- a/zone/bot_commands/item_use.cpp +++ b/zone/bot_commands/item_use.cpp @@ -4,16 +4,17 @@ void bot_command_item_use(Client* c, const Seperator* sep) { if (helper_is_help_or_usage(sep->arg[1])) { c->Message(Chat::White, "usage: [%s empty] will display only bots that can use the item in an empty slot.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s byclass classID] - Example: [%s byclass 7] will display only bots that match the class that can use the item. Example is a Monk, use [^create help] for a list of class IDs.", sep->arg[0], sep->arg[0]); - c->Message(Chat::White, "usage: [%s casteronly] will display only caster bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s hybridonly] will display only hybrid bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s meleeonly] will display only melee bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s wiscasteronly] will display only Wisdom-based Caster bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s intcasteronly] will display only Intelligence-based Caster bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s plateonly] will display only Plate-wearing bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s chainonly] will display only Chain-wearing bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s leatheronly] will display only Leather-wearing bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s clothonly] will display only Cloth-wearing bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s caster] will display only caster bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s hybrid] will display only hybrid bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s melee] will display only melee bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s wiscaster] will display only Wisdom-based Caster bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s intcaster] will display only Intelligence-based Caster bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s plate] will display only Plate-wearing bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s chain] will display only Chain-wearing bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s leather] will display only Leather-wearing bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s cloth] will display only Cloth-wearing bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s haste] will display bots that have no or lesser haste than the item.", sep->arg[0]); + c->Message(Chat::White, "usage: You can also use empty or haste as an argument to narrow down further, for example [%s caster empty], [%s plate haste] or even [%s empty haste]", sep->arg[0], sep->arg[0], sep->arg[0]); return; } @@ -28,136 +29,252 @@ void bot_command_item_use(Client* c, const Seperator* sep) bool chain_only = false; bool leather_only = false; bool cloth_only = false; + bool haste_only = false; + int haste_value = 0; + int ab_arg = 2; std::string arg1 = sep->arg[1]; std::string arg2 = sep->arg[2]; - if (arg1.compare("empty") == 0) { + + if (arg1.compare("empty") == 0 || arg2.compare("empty") == 0) { empty_only = true; + + if (arg2.compare("empty") == 0) { + ++ab_arg; + } } - else if (arg1.compare("byclass") == 0) { - if (Strings::IsNumber(sep->arg[2])) { - class_mask = Strings::ToUnsignedInt(sep->arg[2]); - if (!(class_mask >= Class::Warrior && class_mask <= Class::Berserker)) { - c->Message(Chat::White, "Invalid class range, you must choose between 1 (Warrior) and 15 (Beastlord)"); + + if (arg1.compare("haste") == 0 || arg2.compare("haste") == 0) { + haste_only = true; + + if (arg2.compare("haste") == 0) { + ++ab_arg; + } + } + + if (arg1.compare("caster") == 0) { + caster_only = true; + } + else if (arg1.compare("hybrid") == 0) { + hybrid_only = true; + } + else if (arg1.compare("melee") == 0) { + melee_only = true; + } + else if (arg1.compare("wiscaster") == 0) { + wis_caster_only = true; + } + else if (arg1.compare("intcaster") == 0) { + int_caster_only = true; + } + else if (arg1.compare("plate") == 0) { + plate_only = true; + } + else if (arg1.compare("chain") == 0) { + chain_only = true; + } + else if (arg1.compare("leather") == 0) { + leather_only = true; + } + else if (arg1.compare("cloth") == 0) { + cloth_only = true; + } + else { + if (arg1.empty()) { + --ab_arg; + } + else { + if (!(arg1.compare("empty") == 0) && !(arg1.compare("haste") == 0)) { + c->Message(Chat::White, "Please choose the correct subtype. For help use %s help.", sep->arg[0]); + return; } } } - else if (arg1.compare("casteronly") == 0) { - caster_only = true; - } - else if (arg1.compare("hybridonly") == 0) { - hybrid_only = true; - } - else if (arg1.compare("meleeonly") == 0) { - melee_only = true; - } - else if (arg1.compare("wiscasteronly") == 0) { - wis_caster_only = true; - } - else if (arg1.compare("intcasteronly") == 0) { - int_caster_only = true; - } - else if (arg1.compare("plateonly") == 0) { - plate_only = true; - } - else if (arg1.compare("chainonly") == 0) { - chain_only = true; - } - else if (arg1.compare("leatheronly") == 0) { - leather_only = true; - } - else if (arg1.compare("clothonly") == 0) { - cloth_only = true; - } - else if (!arg1.empty()) { - c->Message(Chat::White, "Please choose the correct subtype. For help use %s help.", sep->arg[0]); - return; - } + const auto item_instance = c->GetInv().GetItem(EQ::invslot::slotCursor); + if (!item_instance) { - c->Message(Chat::White, "No item found on cursor!"); + c->Message(Chat::Yellow, "No item found on cursor! For help use %s help.", sep->arg[0]); + return; } auto item_data = item_instance->GetItem(); + if (!item_data) { - c->Message(Chat::White, "No data found for cursor item!"); + c->Message(Chat::Yellow, "No data found for cursor item!"); + return; } if (item_data->ItemClass != EQ::item::ItemClassCommon || item_data->Slots == 0) { - c->Message(Chat::White, "'%s' is not an equipable item!", item_data->Name); + c->Message(Chat::Yellow, "'%s' is not an equipable item!", item_data->Name); + return; } std::vector equipable_slot_list; + for (int16 equipable_slot = EQ::invslot::EQUIPMENT_BEGIN; equipable_slot <= EQ::invslot::EQUIPMENT_END; ++equipable_slot) { if (item_data->Slots & (1 << equipable_slot)) { equipable_slot_list.emplace_back(equipable_slot); } } - EQ::SayLinkEngine linker; - linker.SetLinkType(EQ::saylink::SayLinkItemInst); + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionableArg = sep->arg[ab_arg]; + + if (actionableArg.empty()) { + actionableArg = "spawned"; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - if (class_mask) { - ActionableBots::Filter_ByClasses(c, sbl, GetPlayerClassBit(class_mask)); + if (ActionableBots::PopulateSBL(c, actionableArg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; } + sbl.remove(nullptr); + + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemData); + for (const auto& bot_iter : sbl) { if (!bot_iter) { continue; } + if (caster_only && !IsCasterClass(bot_iter->GetClass())) { continue; } + if (hybrid_only && !IsSpellFighterClass(bot_iter->GetClass())) { continue; } + if (melee_only && !IsNonSpellFighterClass(bot_iter->GetClass())) { continue; } + if (wis_caster_only && !IsWISCasterClass(bot_iter->GetClass())) { continue; } + if (int_caster_only && !IsINTCasterClass(bot_iter->GetClass())) { continue; } + if (plate_only && !IsPlateClass(bot_iter->GetClass())) { continue; } + if (chain_only && !IsChainClass(bot_iter->GetClass())) { continue; } + if (leather_only && !IsLeatherClass(bot_iter->GetClass())) { continue; } + if (cloth_only && !IsClothClass(bot_iter->GetClass())) { continue; } - if (((~item_data->Races) & GetPlayerRaceBit(bot_iter->GetRace())) || ((~item_data->Classes) & GetPlayerClassBit(bot_iter->GetClass()))) { + + if ( + (!RuleB(Bots, AllowBotEquipAnyRaceGear) && ((~item_data->Races) & GetPlayerRaceBit(bot_iter->GetRace()))) || + (!RuleB(Bots, AllowBotEquipAnyClassGear) && ((~item_data->Classes) & GetPlayerClassBit(bot_iter->GetClass()))) + ) { continue; } + std::list refined_equipable_slot_list; + bool skip_bot = false; + const EQ::ItemData* equipped_item = nullptr; + const EQ::ItemInstance* equipped_inst = nullptr; + for (const auto& slot_iter : equipable_slot_list) { // needs more failure criteria - this should cover the bulk for now if (slot_iter == EQ::invslot::slotSecondary && item_data->Damage && !bot_iter->CanThisClassDualWield()) { continue; } - auto equipped_item = bot_iter->GetInv()[slot_iter]; + if (item_data->ReqLevel > bot_iter->GetLevel()) { + continue; + } - if (equipped_item && !empty_only) { - linker.SetItemInst(equipped_item); + haste_value = 0; + equipped_item = nullptr; + equipped_inst = nullptr; + + + for (int16 equipable_slot = EQ::invslot::EQUIPMENT_BEGIN; equipable_slot <= EQ::invslot::EQUIPMENT_END; ++equipable_slot) { + equipped_inst = bot_iter->GetInv()[equipable_slot]; + if (equipped_inst && equipped_inst->GetItem()) { + equipped_item = equipped_inst->GetItem(); + + if (item_data->CheckLoreConflict(equipped_item)) { + skip_bot = true; + break; + } + + if (haste_only) { + if (equipped_item->Haste > haste_value) { + haste_value = equipped_item->Haste; + } + } + } + } + + if (skip_bot) { + break; + } + + if (haste_only && item_data->Haste < haste_value) { + continue; + } + + equipped_inst = bot_iter->GetInv()[slot_iter]; + + if (equipped_inst && empty_only) { + continue; + } + + refined_equipable_slot_list.push_back(slot_iter); + } + + if (skip_bot) { + continue; + } + + if (refined_equipable_slot_list.empty()) { + continue; + } + + for (auto slot_iter : refined_equipable_slot_list) { + equipped_item = nullptr; + equipped_inst = nullptr; + + equipped_inst = bot_iter->GetInv()[slot_iter]; + + if (equipped_inst && equipped_inst->GetItem()) { + equipped_item = equipped_inst->GetItem(); + } + + if (equipped_item) { + linker.SetItemData(equipped_item); c->Message( Chat::Say, fmt::format( - "{} says, 'I can use that for my {} instead of my {}! Would you like to {} my {}?'", + "{} says, 'I can use that for my {} instead of my {}! Would you like to {}?'", Saylink::Silent( fmt::format( "^inventorygive byname {}", @@ -173,21 +290,16 @@ void bot_command_item_use(Client* c, const Seperator* sep) slot_iter, bot_iter->GetCleanName() ), - "remove" - ), - linker.GenerateLink() + "remove my item" + ) ).c_str() ); - - if (RuleB(Bots, DoResponseAnimations)) { - bot_iter->DoAnim(29); - } } - else if (!equipped_item) { + else { c->Message( Chat::Say, fmt::format( - "{} says, 'I can use that for my {}! Would you like to {} it to me?'", + "{} says, 'I can use that for my {}! Would you like to {}?'", Saylink::Silent( fmt::format( "^inventorygive byname {}", @@ -201,14 +313,14 @@ void bot_command_item_use(Client* c, const Seperator* sep) "^inventorygive byname {}", bot_iter->GetCleanName() ), - "give" + "give it to me" ) ).c_str() ); + } - if (RuleB(Bots, DoResponseAnimations)) { - bot_iter->DoAnim(29); - } + if (RuleB(Bots, DoResponseAnimations)) { + bot_iter->DoAnim(29); } } } From d97f691255c96f152ea6f36a9e0e6a13e5c12cbf Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 26 Nov 2024 09:09:08 -0600 Subject: [PATCH 34/97] misc cleanup --- zone/bot.cpp | 220 +++++++++++++++++++++---------------------- zone/bot.h | 22 ++--- zone/botspellsai.cpp | 18 ++-- 3 files changed, 130 insertions(+), 130 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 03abe808e..e5d560005 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9406,14 +9406,14 @@ bool Bot::PrecastChecks(Mob* tar, uint16 spellType) { return true; } -bool Bot::CastChecks(uint16 spellid, Mob* tar, uint16 spellType, bool doPrechecks, bool AECheck) { +bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechecks, bool AECheck) { if (!tar) { LogBotPreChecksDetail("{} says, 'Cancelling cast due to CastChecks !tar.'", GetCleanName()); //deleteme return false; } if (doPrechecks) { - if (spells[spellid].target_type == ST_Self && tar != this) { + if (spells[spell_id].target_type == ST_Self && tar != this) { tar = this; } @@ -9425,105 +9425,105 @@ bool Bot::CastChecks(uint16 spellid, Mob* tar, uint16 spellType, bool doPrecheck LogBotPreChecksDetail("{} says, 'Running [{}] CastChecks on [{}].'", GetCleanName(), GetSpellTypeNameByID(spellType), tar->GetCleanName()); //deleteme - if (!IsValidSpell(spellid)) { + if (!IsValidSpell(spell_id)) { LogBotPreChecksDetail("{} says, 'Cancelling cast due to !IsValidSpell.'", GetCleanName()); //deleteme return false; } - if (spells[spellid].target_type == ST_Self && tar != this) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to ST_Self.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if (spells[spell_id].target_type == ST_Self && tar != this) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to ST_Self.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - if (!CheckSpellRecastTimer(spellid)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} due to !CheckSpellRecastTimer.'", GetCleanName(), GetSpellName(spellid)); //deleteme + if (!CheckSpellRecastTimer(spell_id)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} due to !CheckSpellRecastTimer.'", GetCleanName(), GetSpellName(spell_id)); //deleteme return false; } - if (!BotHasEnoughMana(spellid)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} due to !BotHasEnoughMana.'", GetCleanName(), GetSpellName(spellid)); //deleteme + if (!BotHasEnoughMana(spell_id)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} due to !BotHasEnoughMana.'", GetCleanName(), GetSpellName(spell_id)); //deleteme return false; } - if (zone->IsSpellBlocked(spellid, glm::vec3(GetPosition()))) { - LogBotPreChecks("{} says, 'Cancelling cast of {} due to IsSpellBlocked.'", GetCleanName(), GetSpellName(spellid)); //deleteme + if (zone->IsSpellBlocked(spell_id, glm::vec3(GetPosition()))) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to IsSpellBlocked.'", GetCleanName(), GetSpellName(spell_id)); //deleteme return false; } - if (!zone->CanLevitate() && IsEffectInSpell(spellid, SE_Levitate)) { - LogBotPreChecks("{} says, 'Cancelling cast of {} due to !CanLevitate.'", GetCleanName(), GetSpellName(spellid)); //deleteme + if (!zone->CanLevitate() && IsEffectInSpell(spell_id, SE_Levitate)) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to !CanLevitate.'", GetCleanName(), GetSpellName(spell_id)); //deleteme return false; } - if (spells[spellid].time_of_day == SpellTimeRestrictions::Day && !zone->zone_time.IsDayTime()) { - LogBotPreChecks("{} says, 'Cancelling cast of {} due to !IsDayTime.'", GetCleanName(), GetSpellName(spellid)); //deleteme + if (spells[spell_id].time_of_day == SpellTimeRestrictions::Day && !zone->zone_time.IsDayTime()) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to !IsDayTime.'", GetCleanName(), GetSpellName(spell_id)); //deleteme return false; } - if (spells[spellid].time_of_day == SpellTimeRestrictions::Night && !zone->zone_time.IsNightTime()) { - LogBotPreChecks("{} says, 'Cancelling cast of {} due to !IsNightTime.'", GetCleanName(), GetSpellName(spellid)); //deleteme + if (spells[spell_id].time_of_day == SpellTimeRestrictions::Night && !zone->zone_time.IsNightTime()) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to !IsNightTime.'", GetCleanName(), GetSpellName(spell_id)); //deleteme return false; } - if (spells[spellid].zone_type == 1 && !zone->CanCastOutdoor()) { - LogBotPreChecks("{} says, 'Cancelling cast of {} due to !CanCastOutdoor.'", GetCleanName(), GetSpellName(spellid)); //deleteme + if (spells[spell_id].zone_type == 1 && !zone->CanCastOutdoor()) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to !CanCastOutdoor.'", GetCleanName(), GetSpellName(spell_id)); //deleteme return false; } - if (!AECheck && !IsValidSpellRange(spellid, tar)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsValidSpellRange.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if (!AECheck && !IsValidSpellRange(spell_id, tar)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsValidSpellRange.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - if (!IsValidTargetType(spellid, GetSpellTargetType(spellid), tar->GetBodyType())) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsValidTargetType.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if (!IsValidTargetType(spell_id, GetSpellTargetType(spell_id), tar->GetBodyType())) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsValidTargetType.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } if (tar->GetSpecialAbility(SpecialAbility::CastingFromRangeImmunity) && !CombatRange(tar)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IMMUNE_CASTING_FROM_RANGE.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IMMUNE_CASTING_FROM_RANGE.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - if (tar->IsImmuneToBotSpell(spellid, this)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsImmuneToBotSpell.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if (tar->IsImmuneToBotSpell(spell_id, this)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsImmuneToBotSpell.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - if (!DoResistCheckBySpellType(tar, spellid, spellType)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to DoResistCheckBySpellType.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if (!DoResistCheckBySpellType(tar, spell_id, spellType)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to DoResistCheckBySpellType.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - if (!IsCommandedSpell() && !IsTaunting() && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting(), spellid) && !tar->IsFleeing()) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to HasOrMayGetAggro.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if (!IsCommandedSpell() && !IsTaunting() && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting(), spell_id) && !tar->IsFleeing()) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to HasOrMayGetAggro.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } if ( - (RequiresStackCheck(spellType) || (!RequiresStackCheck(spellType) && CalcBuffDuration(this, tar, spellid) != 0)) + (RequiresStackCheck(spellType) || (!RequiresStackCheck(spellType) && CalcBuffDuration(this, tar, spell_id) != 0)) && - tar->CanBuffStack(spellid, GetLevel(), true) < 0 + tar->CanBuffStack(spell_id, GetLevel(), true) < 0 ) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanBuffStack.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanBuffStack.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - if (IsBeneficialSpell(spellid) && tar->BuffCount() >= tar->GetCurrentBuffSlots() && CalcBuffDuration(this, tar, spellid) != 0) { + if (IsBeneficialSpell(spell_id) && tar->BuffCount() >= tar->GetCurrentBuffSlots() && CalcBuffDuration(this, tar, spell_id) != 0) { return false; } - LogBotPreChecksDetail("{} says, 'Doing CanCastSpellType checks of {} on {}.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme - if (!CanCastSpellType(spellType, spellid, tar)) { + LogBotPreChecksDetail("{} says, 'Doing CanCastSpellType checks of {} on {}.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + if (!CanCastSpellType(spellType, spell_id, tar)) { return false; } return true; } -bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { - if (!spellid || !tar) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to failsafe checks.'", GetCleanName(), (spellid ? GetSpellName(spellid) : (spellType ? GetSpellTypeNameByID(spellType) : "Unknown")), (tar ? tar->GetCleanName() : "Unknown")); //deleteme +bool Bot::CanCastSpellType(uint16 spellType, uint16 spell_id, Mob* tar) { + if (!spell_id || !tar) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to failsafe checks.'", GetCleanName(), (spell_id ? GetSpellName(spell_id) : (spellType ? GetSpellTypeNameByID(spellType) : "Unknown")), (tar ? tar->GetCleanName() : "Unknown")); //deleteme return false; } @@ -9540,44 +9540,44 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { case BotSpellTypes::PetResistBuffs: if ( !( - spells[spellid].target_type == ST_Target || - spells[spellid].target_type == ST_Pet || - (tar == this && spells[spellid].target_type != ST_TargetsTarget) || - spells[spellid].target_type == ST_Group || - spells[spellid].target_type == ST_GroupTeleport + spells[spell_id].target_type == ST_Target || + spells[spell_id].target_type == ST_Pet || + (tar == this && spells[spell_id].target_type != ST_TargetsTarget) || + spells[spell_id].target_type == ST_Group || + spells[spell_id].target_type == ST_GroupTeleport ) ) { - LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to target_type checks.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to target_type checks.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - if (tar->IsBlockedBuff(spellid)) { - LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to IsBlockedBuff.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if (tar->IsBlockedBuff(spell_id)) { + LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to IsBlockedBuff.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - if (IsEffectInSpell(spellid, SE_Teleport) || IsEffectInSpell(spellid, SE_Succor)) { - LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to Teleport.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if ((spellType != BotSpellTypes::Teleport && spellType != BotSpellTypes::Succor) && (IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Succor))) { + LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to Teleport.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - if (tar->IsPet() && !RuleB(Bots, CanCastIllusionsOnPets) && IsEffectInSpell(spellid, SE_Illusion)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to PetSE_Illusion.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if (tar->IsPet() && !RuleB(Bots, CanCastIllusionsOnPets) && IsEffectInSpell(spell_id, SE_Illusion)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to PetSE_Illusion.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - if (spells[spellid].target_type == ST_Pet && (!tar->IsPet() || (tar->GetOwner() != this && !RuleB(Bots, CanCastPetOnlyOnOthersPets)))) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to PetOnly.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if (spells[spell_id].target_type == ST_Pet && (!tar->IsPet() || (tar->GetOwner() != this && !RuleB(Bots, CanCastPetOnlyOnOthersPets)))) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to PetOnly.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - if ((IsGroupSpell(spellid) && tar->IsPet()) && (!tar->GetOwner() || (RuleB(Bots, RequirePetAffinity) && !tar->GetOwner()->HasPetAffinity()))) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to PetGroupSpellTarget.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if ((IsGroupSpell(spell_id) && tar->IsPet()) && (!tar->GetOwner() || (RuleB(Bots, RequirePetAffinity) && !tar->GetOwner()->HasPetAffinity()))) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to PetGroupSpellTarget.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - if (!IsCommandedSpell() && IsTargetAlreadyReceivingSpell(tar, spellid)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsTargetAlreadyReceivingSpell.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if (!IsCommandedSpell() && IsTargetAlreadyReceivingSpell(tar, spell_id)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsTargetAlreadyReceivingSpell.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } @@ -9587,21 +9587,21 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { if ( tar->IsBot() && tar->GetLevel() > tar->CastToBot()->GetStopMeleeLevel() && ( - IsEffectInSpell(spellid, SE_AttackSpeed) || IsEffectInSpell(spellid, SE_ReverseDS)) || - (SpellEffectsCount(spellid) == 1 && (IsEffectInSpell(spellid, SE_ATK) || IsEffectInSpell(spellid, SE_STR)) + IsEffectInSpell(spell_id, SE_AttackSpeed) || IsEffectInSpell(spell_id, SE_ReverseDS)) || + (SpellEffectsCount(spell_id) == 1 && (IsEffectInSpell(spell_id, SE_ATK) || IsEffectInSpell(spell_id, SE_STR)) ) ) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Caster.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Caster.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } break; case Archetype::Melee: if ( - IsEffectInSpell(spellid, SE_IncreaseSpellHaste) || IsEffectInSpell(spellid, SE_ManaPool) || - IsEffectInSpell(spellid, SE_CastingLevel) || IsEffectInSpell(spellid, SE_ManaRegen_v2) || - IsEffectInSpell(spellid, SE_CurrentMana) + IsEffectInSpell(spell_id, SE_IncreaseSpellHaste) || IsEffectInSpell(spell_id, SE_ManaPool) || + IsEffectInSpell(spell_id, SE_CastingLevel) || IsEffectInSpell(spell_id, SE_ManaRegen_v2) || + IsEffectInSpell(spell_id, SE_CurrentMana) ) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Melee.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Melee.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } break; @@ -9613,14 +9613,14 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { // Differences for each type if (spellType != BotSpellTypes::InCombatBuff) { - if (IsEffectInSpell(spellid, SE_AbsorbMagicAtt) || IsEffectInSpell(spellid, SE_Rune)) { + if (IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune)) { for (int i = 0; i < tar->GetMaxTotalSlots(); i++) { uint32 buff_count = tar->GetMaxTotalSlots(); for (unsigned int j = 0; j < buff_count; j++) { if (IsValidSpell(tar->GetBuffs()[j].spellid)) { if (IsLichSpell(tar->GetBuffs()[j].spellid)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsLichSpell.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsLichSpell.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } } @@ -9633,8 +9633,8 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { case BotSpellTypes::PreCombatBuffSong: case BotSpellTypes::InCombatBuffSong: case BotSpellTypes::OutOfCombatBuffSong: - if (!IsCommandedSpell() && IsTargetAlreadyReceivingSpell(tar, spellid)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsTargetAlreadyReceivingSpell.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + if (!IsCommandedSpell() && IsTargetAlreadyReceivingSpell(tar, spell_id)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsTargetAlreadyReceivingSpell.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } @@ -9644,21 +9644,21 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { if ( tar->IsBot() && tar->GetLevel() > tar->CastToBot()->GetStopMeleeLevel() && ( - IsEffectInSpell(spellid, SE_AttackSpeed) || IsEffectInSpell(spellid, SE_ReverseDS)) || - (SpellEffectsCount(spellid) == 1 && (IsEffectInSpell(spellid, SE_ATK) || IsEffectInSpell(spellid, SE_STR)) + IsEffectInSpell(spell_id, SE_AttackSpeed) || IsEffectInSpell(spell_id, SE_ReverseDS)) || + (SpellEffectsCount(spell_id) == 1 && (IsEffectInSpell(spell_id, SE_ATK) || IsEffectInSpell(spell_id, SE_STR)) ) ) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Caster.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Caster.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } break; case Archetype::Melee: if ( - IsEffectInSpell(spellid, SE_IncreaseSpellHaste) || IsEffectInSpell(spellid, SE_ManaPool) || - IsEffectInSpell(spellid, SE_CastingLevel) || IsEffectInSpell(spellid, SE_ManaRegen_v2) || - IsEffectInSpell(spellid, SE_CurrentMana) + IsEffectInSpell(spell_id, SE_IncreaseSpellHaste) || IsEffectInSpell(spell_id, SE_ManaPool) || + IsEffectInSpell(spell_id, SE_CastingLevel) || IsEffectInSpell(spell_id, SE_ManaRegen_v2) || + IsEffectInSpell(spell_id, SE_CurrentMana) ) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Melee.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Melee.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } break; @@ -9673,7 +9673,7 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar) { break; } - LogBotPreChecksDetail("{} says, {} on {} passed CanCastSpellType.'", GetCleanName(), GetSpellName(spellid), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, {} on {} passed CanCastSpellType.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return true; } @@ -9692,9 +9692,9 @@ bool Bot::BotHasEnoughMana(uint16 spell_id) { return true; } -bool Bot::IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spellid) { +bool Bot::IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spell_id) { - if (!tar || !spellid) { + if (!tar || !spell_id) { return true; } @@ -9717,18 +9717,18 @@ bool Bot::IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spellid) { m->IsCasting() && m->CastToBot()->casting_spell_targetid && entity_list.GetMobID(m->CastToBot()->casting_spell_targetid) == entity_list.GetMobID(tar->GetID()) && - m->CastingSpellID() == spellid + m->CastingSpellID() == spell_id ) { return true; } else { - if (IsGroupSpell(spellid)) { + if (IsGroupSpell(spell_id)) { if ( m->IsBot() && m->IsCasting() && m->CastToBot()->casting_spell_targetid && - m->CastingSpellID() == spellid + m->CastingSpellID() == spell_id ) { std::vector x = GatherGroupSpellTargets(); @@ -9746,13 +9746,13 @@ bool Bot::IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spellid) { return false; } -bool Bot::DoResistCheck(Mob* tar, uint16 spellid, int32 resist_limit) { +bool Bot::DoResistCheck(Mob* tar, uint16 spell_id, int32 resist_limit) { - if (!tar || spellid == 0) { + if (!tar || spell_id == 0) { return false; } - int32 resist_difficulty = -spells[spellid].resist_difficulty; + int32 resist_difficulty = -spells[spell_id].resist_difficulty; int32 level_mod = (tar->GetLevel() - GetLevel()) * (tar->GetLevel() - GetLevel()) / 2; if (tar->GetLevel() - GetLevel() < 0) { @@ -9761,7 +9761,7 @@ bool Bot::DoResistCheck(Mob* tar, uint16 spellid, int32 resist_limit) { int32 targetResist = 0; - switch (GetSpellResistType(spellid)) { + switch (GetSpellResistType(spell_id)) { case RESIST_NONE: return true; case RESIST_MAGIC: @@ -9785,7 +9785,7 @@ bool Bot::DoResistCheck(Mob* tar, uint16 spellid, int32 resist_limit) { default: return true; } - //LogBotPreChecksDetail("DoResistCheck on {} for {} - TarResist [{}] LMod [{}] ResistDiff [{}] - Adjust [{}] > ResistLim [{}]", tar->GetCleanName(), GetSpellName(spellid), targetResist, level_mod, resist_difficulty, (targetResist + level_mod - resist_difficulty), resist_limit); //deleteme) + //LogBotPreChecksDetail("DoResistCheck on {} for {} - TarResist [{}] LMod [{}] ResistDiff [{}] - Adjust [{}] > ResistLim [{}]", tar->GetCleanName(), GetSpellName(spell_id), targetResist, level_mod, resist_difficulty, (targetResist + level_mod - resist_difficulty), resist_limit); //deleteme) if ((targetResist + level_mod - resist_difficulty) > resist_limit) { return false; } @@ -9793,8 +9793,8 @@ bool Bot::DoResistCheck(Mob* tar, uint16 spellid, int32 resist_limit) { return true; } -bool Bot::DoResistCheckBySpellType(Mob* tar, uint16 spellid, uint16 spellType) { - if (!tar || !IsValidSpell(spellid)) { +bool Bot::DoResistCheckBySpellType(Mob* tar, uint16 spell_id, uint16 spellType) { + if (!tar || !IsValidSpell(spell_id)) { return false; } @@ -9802,11 +9802,11 @@ bool Bot::DoResistCheckBySpellType(Mob* tar, uint16 spellid, uint16 spellType) { return true; } - return DoResistCheck(tar, spellid, GetSpellTypeResistLimit(spellType)); + return DoResistCheck(tar, spell_id, GetSpellTypeResistLimit(spellType)); } -bool Bot::IsValidTargetType(uint16 spellid, int targetType, uint8 bodyType) { - if (!spellid) { +bool Bot::IsValidTargetType(uint16 spell_id, int targetType, uint8 bodyType) { + if (!spell_id) { return false; } @@ -9890,7 +9890,7 @@ bool Bot::IsMobEngagedByAnyone(Mob* tar) { return false; } -bool Bot::IsValidMezTarget(Mob* owner, Mob* npc, uint16 spellid) { +bool Bot::IsValidMezTarget(Mob* owner, Mob* npc, uint16 spell_id) { if (npc->GetSpecialAbility(SpecialAbility::MesmerizeImmunity)) { return false; } @@ -9907,7 +9907,7 @@ bool Bot::IsValidMezTarget(Mob* owner, Mob* npc, uint16 spellid) { return false; } - if (!IsValidTargetType(spellid, GetSpellTargetType(spellid), npc->GetBodyType())) { + if (!IsValidTargetType(spell_id, GetSpellTargetType(spell_id), npc->GetBodyType())) { return false; } @@ -10952,58 +10952,58 @@ uint16 Bot::GetSpellListSpellType(uint16 spellType) { return spellType; } -bool Bot::IsValidSpellTypeBySpellID(uint16 spellType, uint16 spellid) { - if (IsAEBotSpellType(spellType) && !IsAnyAESpell(spellid)) { +bool Bot::IsValidSpellTypeBySpellID(uint16 spellType, uint16 spell_id) { + if (IsAEBotSpellType(spellType) && !IsAnyAESpell(spell_id)) { return false; } - if (IsGroupBotSpellType(spellType) && !IsGroupSpell(spellid)) { + if (IsGroupBotSpellType(spellType) && !IsGroupSpell(spell_id)) { return false; } switch (spellType) { case BotSpellTypes::Buff: case BotSpellTypes::PetBuffs: - if (IsResistanceOnlySpell(spellid) || IsDamageShieldOnlySpell(spellid) || IsDamageShieldAndResistanceSpellOnly(spellid)) { + if (IsResistanceOnlySpell(spell_id) || IsDamageShieldOnlySpell(spell_id) || IsDamageShieldAndResistanceSpellOnly(spell_id)) { return false; } return true; case BotSpellTypes::ResistBuffs: case BotSpellTypes::PetResistBuffs: - if (IsResistanceOnlySpell(spellid)) { + if (IsResistanceOnlySpell(spell_id)) { return true; } return false; case BotSpellTypes::DamageShields: case BotSpellTypes::PetDamageShields: - if (IsDamageShieldOnlySpell(spellid) || IsDamageShieldAndResistanceSpellOnly(spellid)) { + if (IsDamageShieldOnlySpell(spell_id) || IsDamageShieldAndResistanceSpellOnly(spell_id)) { return true; } return false; case BotSpellTypes::PBAENuke: - if (IsPBAENukeSpell(spellid) && !IsStunSpell(spellid)) { + if (IsPBAENukeSpell(spell_id) && !IsStunSpell(spell_id)) { return true; } return false; case BotSpellTypes::AERains: - if (IsAERainNukeSpell(spellid) && !IsStunSpell(spellid)) { + if (IsAERainNukeSpell(spell_id) && !IsStunSpell(spell_id)) { return true; } return false; case BotSpellTypes::AEStun: case BotSpellTypes::Stun: - if (IsStunSpell(spellid)) { + if (IsStunSpell(spell_id)) { return true; } return false; case BotSpellTypes::AENukes: case BotSpellTypes::Nuke: - if (!IsStunSpell(spellid)) { + if (!IsStunSpell(spell_id)) { return true; } @@ -11199,9 +11199,9 @@ bool Bot::HasRequiredLoSForPositioning(Mob* tar) { return true; } -bool Bot::HasValidAETarget(Bot* botCaster, uint16 spellid, uint16 spellType, Mob* tar) { - int spellRange = botCaster->GetActSpellRange(spellid, spells[spellid].range); - int spellAERange = botCaster->GetActSpellRange(spellid, spells[spellid].aoe_range); +bool Bot::HasValidAETarget(Bot* botCaster, uint16 spell_id, uint16 spellType, Mob* tar) { + int spellRange = botCaster->GetActSpellRange(spell_id, spells[spell_id].range); + int spellAERange = botCaster->GetActSpellRange(spell_id, spells[spell_id].aoe_range); int targetCount = 0; for (auto& close_mob : botCaster->m_close_mobs) { @@ -11244,14 +11244,14 @@ bool Bot::HasValidAETarget(Bot* botCaster, uint16 spellid, uint16 spellType, Mob continue; } - if (SpellBreaksMez(spellid) && m->IsMezzed()) { + if (SpellBreaksMez(spell_id) && m->IsMezzed()) { continue; } - if (IsPBAESpell(spellid)) { + if (IsPBAESpell(spell_id)) { if ( spellAERange >= Distance(botCaster->GetPosition(), m->GetPosition()) && - botCaster->CastChecks(spellid, m, spellType, true, true) + botCaster->CastChecks(spell_id, m, spellType, true, true) ) { ++targetCount; } @@ -11263,7 +11263,7 @@ bool Bot::HasValidAETarget(Bot* botCaster, uint16 spellid, uint16 spellType, Mob if ( spellAERange >= Distance(tar->GetPosition(), m->GetPosition()) && - botCaster->CastChecks(spellid, m, spellType, true, true) + botCaster->CastChecks(spell_id, m, spellType, true, true) ) { ++targetCount; } diff --git a/zone/bot.h b/zone/bot.h index ff3c7c129..b3f2ae171 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -445,13 +445,13 @@ public: std::vector GatherSpellTargets(bool entireRaid = false, bool noClients = false, bool noBots = false, bool noPets = false); bool PrecastChecks(Mob* tar, uint16 spellType); - bool CastChecks(uint16 spellid, Mob* tar, uint16 spellType, bool doPrechecks = false, bool AECheck = false); - bool CanCastSpellType(uint16 spellType, uint16 spellid, Mob* tar); + bool CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechecks = false, bool AECheck = false); + bool CanCastSpellType(uint16 spellType, uint16 spell_id, Mob* tar); bool BotHasEnoughMana(uint16 spell_id); - bool IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spellid); - bool DoResistCheck(Mob* target, uint16 spellid, int32 resist_limit); - bool DoResistCheckBySpellType(Mob* tar, uint16 spellid, uint16 spellType); - bool IsValidTargetType(uint16 spellid, int targetType, uint8 bodyType); + bool IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spell_id); + bool DoResistCheck(Mob* target, uint16 spell_id, int32 resist_limit); + bool DoResistCheckBySpellType(Mob* tar, uint16 spell_id, uint16 spellType); + bool IsValidTargetType(uint16 spell_id, int targetType, uint8 bodyType); bool IsMobEngagedByAnyone(Mob* tar); void SetBotSetting(uint8 settingType, uint16 botSetting, int settingValue); void CopySettings(Bot* to, uint8 settingType, uint16 spellType = UINT16_MAX); @@ -522,11 +522,11 @@ public: std::list GetSpellTypesPrioritized(uint8 priorityType); uint16 GetSpellListSpellType(uint16 spellType); - bool IsValidSpellTypeBySpellID(uint16 spellType, uint16 spellid); + bool IsValidSpellTypeBySpellID(uint16 spellType, uint16 spell_id); inline uint16 GetCastedSpellType() const { return _castedSpellType; } void SetCastedSpellType(uint16 spellType); - bool HasValidAETarget(Bot* botCaster, uint16 spellid, uint16 spellType, Mob* tar); + bool HasValidAETarget(Bot* botCaster, uint16 spell_id, uint16 spellType, Mob* tar); void CheckBotSpells(); @@ -592,8 +592,8 @@ public: static BotSpell GetBestBotSpellForGroupCompleteHeal(Bot* botCaster, Mob* tar, uint16 spellType = BotSpellTypes::RegularHeal); static BotSpell GetBestBotSpellForGroupHeal(Bot* botCaster, Mob* tar, uint16 spellType = BotSpellTypes::RegularHeal); - static Mob* GetFirstIncomingMobToMez(Bot* botCaster, int16 spellid, uint16 spellType, bool AE = false); - bool IsValidMezTarget(Mob* owner, Mob* npc, uint16 spellid); + static Mob* GetFirstIncomingMobToMez(Bot* botCaster, int16 spell_id, uint16 spellType, bool AE = false); + bool IsValidMezTarget(Mob* owner, Mob* npc, uint16 spell_id); static BotSpell GetBestBotSpellForMez(Bot* botCaster, uint16 spellType = BotSpellTypes::Mez); static BotSpell GetBestBotMagicianPetSpell(Bot* botCaster, uint16 spellType = BotSpellTypes::Pet); static std::string GetBotMagicianPetType(Bot* botCaster); @@ -735,7 +735,7 @@ public: inline const InspectMessage_Struct& GetInspectMessage() const { return _botInspectMessage; } // "Quest API" Methods - bool HasBotSpellEntry(uint16 spellid); + bool HasBotSpellEntry(uint16 spell_id); void ApplySpell(int spell_id, int duration = 0, int level = -1, ApplySpellType apply_type = ApplySpellType::Solo, bool allow_pets = false, bool is_raid_group_only = true); void BreakInvis(); void Escape(); diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 5adbdb3e9..0138d58ad 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -1429,11 +1429,11 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* botCaster, uint16 spellType) { return result; } -Mob* Bot::GetFirstIncomingMobToMez(Bot* botCaster, int16 spellid, uint16 spellType, bool AE) { +Mob* Bot::GetFirstIncomingMobToMez(Bot* botCaster, int16 spell_id, uint16 spellType, bool AE) { Mob* result = nullptr; if (botCaster && botCaster->GetOwner()) { - int spellRange = (!AE ? botCaster->GetActSpellRange(spellid, spells[spellid].range) : botCaster->GetActSpellRange(spellid, spells[spellid].aoe_range)); + int spellRange = (!AE ? botCaster->GetActSpellRange(spell_id, spells[spell_id].range) : botCaster->GetActSpellRange(spell_id, spells[spell_id].aoe_range)); int buff_count = 0; NPC* npc = nullptr; @@ -1445,7 +1445,7 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* botCaster, int16 spellid, uint16 spellTy continue; } - if (!botCaster->IsValidMezTarget(botCaster->GetOwner(), npc, spellid)) { + if (!botCaster->IsValidMezTarget(botCaster->GetOwner(), npc, spell_id)) { continue; } @@ -1459,11 +1459,11 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* botCaster, int16 spellid, uint16 spellTy continue; } - if (!botCaster->IsValidMezTarget(botCaster->GetOwner(), m, spellid)) { + if (!botCaster->IsValidMezTarget(botCaster->GetOwner(), m, spell_id)) { continue; } - if (IsPBAESpell(spellid)) { + if (IsPBAESpell(spell_id)) { if (spellRange < Distance(botCaster->GetPosition(), m->GetPosition())) { continue; } @@ -1474,7 +1474,7 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* botCaster, int16 spellid, uint16 spellTy } } - if (botCaster->CastChecks(spellid, m, spellType, true, true)) { + if (botCaster->CastChecks(spell_id, m, spellType, true, true)) { ++targetCount; } @@ -1499,7 +1499,7 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* botCaster, int16 spellid, uint16 spellTy continue; } - if (!botCaster->CastChecks(spellid, npc, spellType, true)) { + if (!botCaster->CastChecks(spell_id, npc, spellType, true)) { continue; } @@ -2631,7 +2631,7 @@ void Bot::AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot) { } } -bool Bot::HasBotSpellEntry(uint16 spellid) { +bool Bot::HasBotSpellEntry(uint16 spell_id) { auto* spell_list = content_db.GetBotSpells(GetBotSpellID()); if (!spell_list) { @@ -2640,7 +2640,7 @@ bool Bot::HasBotSpellEntry(uint16 spellid) { // Check if Spell ID is found in Bot Spell Entries for (auto& e : spell_list->entries) { - if (spellid == e.spellid) { + if (spell_id == e.spellid) { return true; } } From bdea548460dfd1745d4264eae10ab9de19e12285 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:16:58 -0600 Subject: [PATCH 35/97] fix resistbuffs and damageshields spell type checks --- common/spdat.cpp | 21 ++------------------- common/spdat.h | 1 - zone/bot.cpp | 4 ++-- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index 8211ecfdc..7b0e54089 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -3214,14 +3214,6 @@ bool IsResistanceOnlySpell(uint16 spell_id) { } bool IsDamageShieldOnlySpell(uint16 spell_id) { - if (SpellEffectsCount(spell_id) == 1 && IsEffectInSpell(spell_id, SE_DamageShield)) { - return true; - } - - return false; -} - -bool IsDamageShieldAndResistanceSpellOnly(uint16 spell_id) { if (!IsValidSpell(spell_id)) { return false; } @@ -3234,19 +3226,10 @@ bool IsDamageShieldAndResistanceSpellOnly(uint16 spell_id) { } if ( - spell.effect_id[i] == SE_DamageShield || - spell.effect_id[i] == SE_ResistFire || - spell.effect_id[i] == SE_ResistCold || - spell.effect_id[i] == SE_ResistPoison || - spell.effect_id[i] == SE_ResistDisease || - spell.effect_id[i] == SE_ResistMagic || - spell.effect_id[i] == SE_ResistCorruption || - spell.effect_id[i] == SE_ResistAll + spell.effect_id[i] != SE_DamageShield ) { - continue; + return false; } - - return false; } return true; diff --git a/common/spdat.h b/common/spdat.h index 60257a401..4c1b52eca 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -1726,6 +1726,5 @@ bool IsResurrectSpell(uint16 spell_id); bool RequiresStackCheck(uint16 spellType); bool IsResistanceOnlySpell(uint16 spell_id); bool IsDamageShieldOnlySpell(uint16 spell_id); -bool IsDamageShieldAndResistanceSpellOnly(uint16 spell_id); #endif diff --git a/zone/bot.cpp b/zone/bot.cpp index e5d560005..988c0d4e3 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -10964,7 +10964,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spellType, uint16 spell_id) { switch (spellType) { case BotSpellTypes::Buff: case BotSpellTypes::PetBuffs: - if (IsResistanceOnlySpell(spell_id) || IsDamageShieldOnlySpell(spell_id) || IsDamageShieldAndResistanceSpellOnly(spell_id)) { + if (IsResistanceOnlySpell(spell_id) || IsDamageShieldOnlySpell(spell_id)) { return false; } @@ -10978,7 +10978,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spellType, uint16 spell_id) { return false; case BotSpellTypes::DamageShields: case BotSpellTypes::PetDamageShields: - if (IsDamageShieldOnlySpell(spell_id) || IsDamageShieldAndResistanceSpellOnly(spell_id)) { + if (IsDamageShieldOnlySpell(spell_id)) { return true; } From 1f82e12b34c2d71770f561295cb71a12250d7e33 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:18:11 -0600 Subject: [PATCH 36/97] Command help cleanup --- common/ruletypes.h | 1 + zone/bot.h | 2 - zone/bot_command.cpp | 81 ++++++++++++-------- zone/bot_command.h | 4 + zone/bot_commands/behind_mob.cpp | 22 +++--- zone/bot_commands/cast.cpp | 13 +++- zone/bot_commands/copy_settings.cpp | 24 +++--- zone/bot_commands/default_settings.cpp | 24 +++--- zone/bot_commands/illusion_block.cpp | 22 +++--- zone/bot_commands/max_melee_range.cpp | 22 +++--- zone/bot_commands/sit_hp_percent.cpp | 22 +++--- zone/bot_commands/sit_in_combat.cpp | 22 +++--- zone/bot_commands/sit_mana_percent.cpp | 22 +++--- zone/bot_commands/spell_aggro_checks.cpp | 25 +++--- zone/bot_commands/spell_delays.cpp | 25 +++--- zone/bot_commands/spell_engaged_priority.cpp | 25 +++--- zone/bot_commands/spell_holds.cpp | 25 +++--- zone/bot_commands/spell_idle_priority.cpp | 25 +++--- zone/bot_commands/spell_max_hp_pct.cpp | 25 +++--- zone/bot_commands/spell_max_mana_pct.cpp | 25 +++--- zone/bot_commands/spell_max_thresholds.cpp | 25 +++--- zone/bot_commands/spell_min_hp_pct.cpp | 25 +++--- zone/bot_commands/spell_min_mana_pct.cpp | 25 +++--- zone/bot_commands/spell_min_thresholds.cpp | 25 +++--- zone/bot_commands/spell_pursue_priority.cpp | 25 +++--- zone/bot_commands/spell_target_count.cpp | 25 +++--- zone/bot_commands/spelltypes.cpp | 9 +++ 27 files changed, 300 insertions(+), 315 deletions(-) create mode 100644 zone/bot_commands/spelltypes.cpp diff --git a/common/ruletypes.h b/common/ruletypes.h index ec8c60513..8096c037b 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -872,6 +872,7 @@ RULE_INT(Bots, MaxFollowDistance, 300, "Default 300. Max distance a bot can be s RULE_INT(Bots, MaxDistanceRanged, 300, "Default 300. Max distance a bot can be set to ranged.") RULE_BOOL(Bots, AllowAIMez, true, "If enabled bots will automatically mez/AE mez eligible targets.") RULE_INT(Bots, CampTimer, 25, "Number of seconds after /camp has begun before bots camp out.") +RULE_BOOL(Bots, SendClassRaceOnHelp, true, "If enabled a reminder of how to check class/race IDs will be sent when using compatible commands.") RULE_CATEGORY_END() RULE_CATEGORY(Chat) diff --git a/zone/bot.h b/zone/bot.h index b3f2ae171..8e85448e0 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -517,8 +517,6 @@ public: void SetManaWhenToMed(uint8 value) { _ManaWhenToMed = value; } void SetHasLoS(bool hasLoS) { _hasLoS = hasLoS; } bool HasLoS() const { return _hasLoS; } - - void SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, std::string arg2, bool helpPrompt = false); std::list GetSpellTypesPrioritized(uint8 priorityType); uint16 GetSpellListSpellType(uint16 spellType); diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 81f7b21a1..15ea7eba8 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1375,6 +1375,8 @@ int bot_command_init(void) bot_command_add("spellsettingstoggle", "Toggle a bot spell use", AccountStatus::Player, bot_command_spell_settings_toggle) || bot_command_add("spellsettingsupdate", "Update a bot spell setting entry", AccountStatus::Player, bot_command_spell_settings_update) || bot_command_add("summoncorpse", "Orders a bot to summon a corpse to its feet", AccountStatus::Player, bot_command_summon_corpse) || + bot_command_add("spelltypeids", "Lists spelltypes by ID", AccountStatus::Player, bot_command_spelltype_ids) || + bot_command_add("spelltypenames", "Lists spelltypes by shortname", AccountStatus::Player, bot_command_spelltype_names) || bot_command_add("suspend", "Suspends a bot's AI processing until released", AccountStatus::Player, bot_command_suspend) || bot_command_add("taunt", "Toggles taunt use by a bot", AccountStatus::Player, bot_command_taunt) || bot_command_add("timer", "Checks or clears timers of the chosen type.", AccountStatus::GMMgmt, bot_command_timer) || @@ -2115,43 +2117,54 @@ bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::Sp return false; } -void Bot::SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, std::string arg2, bool helpPrompt) { - if (helpPrompt) { +void SendSpellTypePrompts(Client *c, bool commandedTypes) { + c->Message( + Chat::Yellow, + fmt::format( + "You can view spell types by ID or shortname: {}, {}, {} / {}, {}, {}", + Saylink::Silent( + fmt::format("^spelltypeids 0-19"), "ID 0-19" + ), + Saylink::Silent( + fmt::format("^spelltypeids 20-39"), "20-39" + ), + Saylink::Silent( + fmt::format("^spelltypeids 40+"), "40+" + ), + Saylink::Silent( + fmt::format("^spelltypenames 0-19"), "Shortname 0-19" + ), + Saylink::Silent( + fmt::format("^spelltypenames 20-39"), "20-39" + ), + Saylink::Silent( + fmt::format("^spelltypenames 40+"), "40+" + ) + ).c_str() + ); + + if (commandedTypes) { c->Message( Chat::Yellow, fmt::format( - "Use {}, {}, {} for a list of spell types by ID", + "You can view commanded spell types by ID or shortname: {} / {}", Saylink::Silent( - fmt::format("{} listid 0-19", arg0) + fmt::format("^spelltypeids commanded"), "ID" ), Saylink::Silent( - fmt::format("{} listid 20-39", arg0) - ), - Saylink::Silent( - fmt::format("{} listid 40+", arg0) + fmt::format("^spelltypenames commanded"), "Shortname" ) ).c_str() ); - - c->Message( - Chat::Yellow, - fmt::format( - "Use {}, {}, {} for a list of spell types by short name", - Saylink::Silent( - fmt::format("{} listname 0-19", arg0) - ), - Saylink::Silent( - fmt::format("{} listname 20-39", arg0) - ), - Saylink::Silent( - fmt::format("{} listname 40+", arg0) - ) - ).c_str() - ); - - return; } + return; +} + +void SendSpellTypeWindow(Client *c, const Seperator* sep) { + std::string arg0 = sep->arg[0]; + std::string arg1 = sep->arg[1]; + uint8 minCount = 0; uint8 maxCount = 0; @@ -2159,18 +2172,22 @@ void Bot::SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, st minCount = BotSpellTypes::START; maxCount = BotSpellTypes::END; } - else if (!arg2.compare("0-19")) { + else if (!arg1.compare("0-19")) { minCount = BotSpellTypes::START; maxCount = 19; } - else if (!arg2.compare("20-39")) { + else if (!arg1.compare("20-39")) { minCount = std::min(static_cast(20), static_cast(BotSpellTypes::END)); maxCount = std::min(static_cast(39), static_cast(BotSpellTypes::END)); } - else if (!arg2.compare("40+")) { + else if (!arg1.compare("40+")) { minCount = std::min(static_cast(40), static_cast(BotSpellTypes::END)); maxCount = BotSpellTypes::END; } + else if (!arg1.compare("commanded")) { + minCount = BotSpellTypes::COMMANDED_START; + maxCount = BotSpellTypes::COMMANDED_END; + } else { c->Message(Chat::Yellow, "You must choose a valid range option"); @@ -2199,7 +2216,7 @@ void Bot::SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, st DialogueWindow::TableCell( fmt::format( "{}", - (!arg1.compare("listid") ? DialogueWindow::ColorMessage(goldenrod, idField) : DialogueWindow::ColorMessage(goldenrod, shortnameField)) + (!arg0.compare("^spelltypeids") ? DialogueWindow::ColorMessage(goldenrod, idField) : DialogueWindow::ColorMessage(goldenrod, shortnameField)) ) ) ); @@ -2231,7 +2248,7 @@ void Bot::SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, st DialogueWindow::TableCell( fmt::format( "{}", - (!arg1.compare("listid") ? DialogueWindow::ColorMessage(slate_blue, std::to_string(i)) : DialogueWindow::ColorMessage(slate_blue, c->GetSpellTypeShortNameByID(i))) + (!arg0.compare("^spelltypeids") ? DialogueWindow::ColorMessage(slate_blue, std::to_string(i)) : DialogueWindow::ColorMessage(slate_blue, c->GetSpellTypeShortNameByID(i))) ) ) ); @@ -2242,7 +2259,6 @@ void Bot::SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, st c->SendPopupToClient("Spell Types", popup_text.c_str()); } - #include "bot_commands/actionable.cpp" #include "bot_commands/aggressive.cpp" #include "bot_commands/appearance.cpp" @@ -2310,6 +2326,7 @@ void Bot::SendSpellTypesWindow(Client* c, std::string arg0, std::string arg1, st #include "bot_commands/spell_min_thresholds.cpp" #include "bot_commands/spell_pursue_priority.cpp" #include "bot_commands/spell_target_count.cpp" +#include "bot_commands/spelltypes.cpp" #include "bot_commands/summon.cpp" #include "bot_commands/summon_corpse.cpp" #include "bot_commands/suspend.cpp" diff --git a/zone/bot_command.h b/zone/bot_command.h index 373fbd1e5..8e200bd6b 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -1733,6 +1733,8 @@ void bot_command_spell_settings_delete(Client* c, const Seperator *sep); void bot_command_spell_settings_list(Client* c, const Seperator *sep); void bot_command_spell_settings_toggle(Client* c, const Seperator *sep); void bot_command_spell_settings_update(Client* c, const Seperator *sep); +void bot_command_spelltype_ids(Client* c, const Seperator* sep); +void bot_command_spelltype_names(Client* c, const Seperator* sep); void bot_spell_info_dialogue_window(Client* c, const Seperator *sep); void bot_command_enforce_spell_list(Client* c, const Seperator* sep); void bot_command_summon_corpse(Client *c, const Seperator *sep); @@ -1820,4 +1822,6 @@ void helper_send_available_subcommands(Client *bot_owner, const char* command_si void helper_send_usage_required_bots(Client *bot_owner, BCEnum::SpType spell_type, uint8 bot_class = Class::None); bool helper_spell_check_fail(STBaseEntry* local_entry); bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::SpType spell_type); +void SendSpellTypePrompts(Client *c, bool commandedTypes = false); +void SendSpellTypeWindow(Client *c, const Seperator* sep); #endif diff --git a/zone/bot_commands/behind_mob.cpp b/zone/bot_commands/behind_mob.cpp index a7710185f..3abde3c1a 100644 --- a/zone/bot_commands/behind_mob.cpp +++ b/zone/bot_commands/behind_mob.cpp @@ -64,24 +64,22 @@ void bot_command_behind_mob(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - int ab_arg = 1; bool current_check = false; uint32 typeValue = 0; diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index fd7dc5aa8..ab02eb950 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -94,7 +94,8 @@ void bot_command_cast(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); + SendSpellTypePrompts(c, true); + c->Message( Chat::Yellow, fmt::format( @@ -103,6 +104,16 @@ void bot_command_cast(Client* c, const Seperator* sep) ).c_str() ); + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + return; } diff --git a/zone/bot_commands/copy_settings.cpp b/zone/bot_commands/copy_settings.cpp index e2745442f..8e41f971a 100644 --- a/zone/bot_commands/copy_settings.cpp +++ b/zone/bot_commands/copy_settings.cpp @@ -97,25 +97,23 @@ void bot_command_copy_settings(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - int ab_arg = 2; bool validOption = false; uint16 spellType = UINT16_MAX; diff --git a/zone/bot_commands/default_settings.cpp b/zone/bot_commands/default_settings.cpp index ddf809a75..8ed6733a6 100644 --- a/zone/bot_commands/default_settings.cpp +++ b/zone/bot_commands/default_settings.cpp @@ -91,25 +91,23 @@ void bot_command_default_settings(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - int ab_arg = 2; bool validOption = false; uint16 spellType = UINT16_MAX; diff --git a/zone/bot_commands/illusion_block.cpp b/zone/bot_commands/illusion_block.cpp index 94faafc42..47d2d6b26 100644 --- a/zone/bot_commands/illusion_block.cpp +++ b/zone/bot_commands/illusion_block.cpp @@ -63,13 +63,16 @@ void bot_command_illusion_block(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; @@ -77,11 +80,6 @@ void bot_command_illusion_block(Client* c, const Seperator* sep) std::string arg1 = sep->arg[1]; - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - int ab_arg = 1; bool current_check = false; uint32 typeValue = 0; diff --git a/zone/bot_commands/max_melee_range.cpp b/zone/bot_commands/max_melee_range.cpp index c9e32dec1..6c4460ac2 100644 --- a/zone/bot_commands/max_melee_range.cpp +++ b/zone/bot_commands/max_melee_range.cpp @@ -63,24 +63,22 @@ void bot_command_max_melee_range(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - int ab_arg = 1; bool current_check = false; uint32 typeValue = 0; diff --git a/zone/bot_commands/sit_hp_percent.cpp b/zone/bot_commands/sit_hp_percent.cpp index fa4a183bf..38b42ffd9 100644 --- a/zone/bot_commands/sit_hp_percent.cpp +++ b/zone/bot_commands/sit_hp_percent.cpp @@ -63,24 +63,22 @@ void bot_command_sit_hp_percent(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - int ab_arg = 1; bool current_check = false; uint32 typeValue = 0; diff --git a/zone/bot_commands/sit_in_combat.cpp b/zone/bot_commands/sit_in_combat.cpp index 210372f56..eaf08e0ae 100644 --- a/zone/bot_commands/sit_in_combat.cpp +++ b/zone/bot_commands/sit_in_combat.cpp @@ -63,24 +63,22 @@ void bot_command_sit_in_combat(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - int ab_arg = 1; bool current_check = false; uint32 typeValue = 0; diff --git a/zone/bot_commands/sit_mana_percent.cpp b/zone/bot_commands/sit_mana_percent.cpp index a5751f9f4..820be6a75 100644 --- a/zone/bot_commands/sit_mana_percent.cpp +++ b/zone/bot_commands/sit_mana_percent.cpp @@ -63,24 +63,22 @@ void bot_command_sit_mana_percent(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - int ab_arg = 1; bool current_check = false; uint32 typeValue = 0; diff --git a/zone/bot_commands/spell_aggro_checks.cpp b/zone/bot_commands/spell_aggro_checks.cpp index 2dfeef486..cbcc4706b 100644 --- a/zone/bot_commands/spell_aggro_checks.cpp +++ b/zone/bot_commands/spell_aggro_checks.cpp @@ -92,25 +92,22 @@ void bot_command_spell_aggro_checks(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; diff --git a/zone/bot_commands/spell_delays.cpp b/zone/bot_commands/spell_delays.cpp index 2a5de1017..9fd37b70a 100644 --- a/zone/bot_commands/spell_delays.cpp +++ b/zone/bot_commands/spell_delays.cpp @@ -98,25 +98,22 @@ void bot_command_spell_delays(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; diff --git a/zone/bot_commands/spell_engaged_priority.cpp b/zone/bot_commands/spell_engaged_priority.cpp index 70425ee11..f27387b59 100644 --- a/zone/bot_commands/spell_engaged_priority.cpp +++ b/zone/bot_commands/spell_engaged_priority.cpp @@ -96,25 +96,22 @@ void bot_command_spell_engaged_priority(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; diff --git a/zone/bot_commands/spell_holds.cpp b/zone/bot_commands/spell_holds.cpp index fd17749d1..e30911bd4 100644 --- a/zone/bot_commands/spell_holds.cpp +++ b/zone/bot_commands/spell_holds.cpp @@ -82,25 +82,22 @@ void bot_command_spell_holds(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; diff --git a/zone/bot_commands/spell_idle_priority.cpp b/zone/bot_commands/spell_idle_priority.cpp index d6f0a0c35..cdb741d44 100644 --- a/zone/bot_commands/spell_idle_priority.cpp +++ b/zone/bot_commands/spell_idle_priority.cpp @@ -96,25 +96,22 @@ void bot_command_spell_idle_priority(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; diff --git a/zone/bot_commands/spell_max_hp_pct.cpp b/zone/bot_commands/spell_max_hp_pct.cpp index 7f8cd150b..bcd3614a5 100644 --- a/zone/bot_commands/spell_max_hp_pct.cpp +++ b/zone/bot_commands/spell_max_hp_pct.cpp @@ -92,25 +92,22 @@ void bot_command_spell_max_hp_pct(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; diff --git a/zone/bot_commands/spell_max_mana_pct.cpp b/zone/bot_commands/spell_max_mana_pct.cpp index 44cc7e8d5..84cb9abd9 100644 --- a/zone/bot_commands/spell_max_mana_pct.cpp +++ b/zone/bot_commands/spell_max_mana_pct.cpp @@ -92,25 +92,22 @@ void bot_command_spell_max_mana_pct(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; diff --git a/zone/bot_commands/spell_max_thresholds.cpp b/zone/bot_commands/spell_max_thresholds.cpp index 7ff651514..385258ddd 100644 --- a/zone/bot_commands/spell_max_thresholds.cpp +++ b/zone/bot_commands/spell_max_thresholds.cpp @@ -98,25 +98,22 @@ void bot_command_spell_max_thresholds(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; diff --git a/zone/bot_commands/spell_min_hp_pct.cpp b/zone/bot_commands/spell_min_hp_pct.cpp index 371b44820..be6fe1b9e 100644 --- a/zone/bot_commands/spell_min_hp_pct.cpp +++ b/zone/bot_commands/spell_min_hp_pct.cpp @@ -92,25 +92,22 @@ void bot_command_spell_min_hp_pct(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; diff --git a/zone/bot_commands/spell_min_mana_pct.cpp b/zone/bot_commands/spell_min_mana_pct.cpp index b053c462c..f4ffe1bad 100644 --- a/zone/bot_commands/spell_min_mana_pct.cpp +++ b/zone/bot_commands/spell_min_mana_pct.cpp @@ -92,25 +92,22 @@ void bot_command_spell_min_mana_pct(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; diff --git a/zone/bot_commands/spell_min_thresholds.cpp b/zone/bot_commands/spell_min_thresholds.cpp index d690dede2..ffcecf8b7 100644 --- a/zone/bot_commands/spell_min_thresholds.cpp +++ b/zone/bot_commands/spell_min_thresholds.cpp @@ -100,25 +100,22 @@ void bot_command_spell_min_thresholds(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; diff --git a/zone/bot_commands/spell_pursue_priority.cpp b/zone/bot_commands/spell_pursue_priority.cpp index 872444f32..272135422 100644 --- a/zone/bot_commands/spell_pursue_priority.cpp +++ b/zone/bot_commands/spell_pursue_priority.cpp @@ -96,25 +96,22 @@ void bot_command_spell_pursue_priority(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; diff --git a/zone/bot_commands/spell_target_count.cpp b/zone/bot_commands/spell_target_count.cpp index 2f78a7468..ad1b897b8 100644 --- a/zone/bot_commands/spell_target_count.cpp +++ b/zone/bot_commands/spell_target_count.cpp @@ -92,25 +92,22 @@ void bot_command_spell_target_count(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], "", "", true); - c->Message( - Chat::Yellow, - fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") - ).c_str() - ); + SendSpellTypePrompts(c); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } return; } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; diff --git a/zone/bot_commands/spelltypes.cpp b/zone/bot_commands/spelltypes.cpp new file mode 100644 index 000000000..f91b3ed7e --- /dev/null +++ b/zone/bot_commands/spelltypes.cpp @@ -0,0 +1,9 @@ +void bot_command_spelltype_ids(Client* c, const Seperator* sep) +{ + SendSpellTypeWindow(c, sep); +} + +void bot_command_spelltype_names(Client* c, const Seperator* sep) +{ + SendSpellTypeWindow(c, sep); +} From b200bdd04e832e15e10c152e03089854da61a46e Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:17:36 -0600 Subject: [PATCH 37/97] implement commanded cast types --- .../database_update_manifest_bots.cpp | 901 +++++++++++++++--- common/ruletypes.h | 4 + common/spdat.cpp | 57 ++ common/spdat.h | 18 + common/version.h | 2 +- zone/bot.cpp | 181 ++++ zone/bot.h | 18 +- zone/bot_command.cpp | 38 +- zone/bot_command.h | 15 - zone/bot_commands/bind_affinity.cpp | 42 - zone/bot_commands/cast.cpp | 230 ++++- zone/bot_commands/charm.cpp | 54 -- zone/bot_commands/cure.cpp | 71 -- zone/bot_commands/escape.cpp | 44 - zone/bot_commands/identify.cpp | 38 - zone/bot_commands/invisibility.cpp | 57 -- zone/bot_commands/levitation.cpp | 38 - zone/bot_commands/mesmerize.cpp | 41 - zone/bot_commands/movement_speed.cpp | 50 - zone/bot_commands/resistance.cpp | 73 -- zone/bot_commands/resurrect.cpp | 57 -- zone/bot_commands/rune.cpp | 38 - zone/bot_commands/send_home.cpp | 47 - zone/bot_commands/size.cpp | 50 - zone/bot_commands/water_breathing.cpp | 38 - zone/botspellsai.cpp | 59 +- zone/mob.cpp | 125 +++ zone/mob.h | 3 +- 28 files changed, 1395 insertions(+), 994 deletions(-) delete mode 100644 zone/bot_commands/bind_affinity.cpp delete mode 100644 zone/bot_commands/charm.cpp delete mode 100644 zone/bot_commands/cure.cpp delete mode 100644 zone/bot_commands/escape.cpp delete mode 100644 zone/bot_commands/identify.cpp delete mode 100644 zone/bot_commands/invisibility.cpp delete mode 100644 zone/bot_commands/levitation.cpp delete mode 100644 zone/bot_commands/mesmerize.cpp delete mode 100644 zone/bot_commands/movement_speed.cpp delete mode 100644 zone/bot_commands/resistance.cpp delete mode 100644 zone/bot_commands/resurrect.cpp delete mode 100644 zone/bot_commands/rune.cpp delete mode 100644 zone/bot_commands/send_home.cpp delete mode 100644 zone/bot_commands/size.cpp delete mode 100644 zone/bot_commands/water_breathing.cpp diff --git a/common/database/database_update_manifest_bots.cpp b/common/database/database_update_manifest_bots.cpp index a57a6f075..96627d814 100644 --- a/common/database/database_update_manifest_bots.cpp +++ b/common/database/database_update_manifest_bots.cpp @@ -312,7 +312,7 @@ UPDATE `bot_spells_entries` SET `type` = 21 WHERE `type` = 2097152; .sql = R"( UPDATE bot_spells_entries b, spells_new s SET b.`type` = 22 -WHERE b.spellid = s.id +WHERE b.spell_id = s.id AND ( s.`effectid1` = 23 OR s.`effectid2` = 23 OR @@ -332,177 +332,764 @@ AND ( ManifestEntry{ .version = 9049, .description = "2024_05_18_correct_bot_spell_entries_types.sql", - .check = "SELECT * FROM `bot_spells_entries` where `npc_spells_id` = 3002 AND `spellid` = 14312", + .check = "SELECT * FROM `bot_spells_entries` where `npc_spells_id` = 3002 AND `spell_id` = 14312", .condition = "empty", .match = "", .sql = R"( -- Class fixes -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3002 WHERE b.`spellid` = 14312; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3002 WHERE b.`spellid` = 14313; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3002 WHERE b.`spellid` = 14314; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3005 WHERE b.`spellid` = 15186; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3005 WHERE b.`spellid` = 15187; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3005 WHERE b.`spellid` = 15188; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14446; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14447; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14467; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14468; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14469; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3003 WHERE b.`spellid` = 14955; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3003 WHERE b.`spellid` = 14956; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14387; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14388; -UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spellid` = 14389; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3002 WHERE b.`spell_id` = 14312; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3002 WHERE b.`spell_id` = 14313; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3002 WHERE b.`spell_id` = 14314; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3005 WHERE b.`spell_id` = 15186; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3005 WHERE b.`spell_id` = 15187; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3005 WHERE b.`spell_id` = 15188; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spell_id` = 14446; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spell_id` = 14447; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spell_id` = 14467; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spell_id` = 14468; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spell_id` = 14469; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3003 WHERE b.`spell_id` = 14955; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3003 WHERE b.`spell_id` = 14956; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spell_id` = 14387; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spell_id` = 14388; +UPDATE bot_spells_entries b SET b.`npc_spells_id` = 3006 WHERE b.`spell_id` = 14389; -- Minlevel fixes -UPDATE bot_spells_entries SET `minlevel` = 34 WHERE `spellid` = 1445 AND `npc_spells_id` = 3002; -UPDATE bot_spells_entries SET `minlevel` = 2 WHERE `spellid` = 229 AND `npc_spells_id` = 3011; -UPDATE bot_spells_entries SET `minlevel` = 13 WHERE `spellid` = 333 AND `npc_spells_id` = 3013; -UPDATE bot_spells_entries SET `minlevel` = 29 WHERE `spellid` = 106 AND `npc_spells_id` = 3013; -UPDATE bot_spells_entries SET `minlevel` = 38 WHERE `spellid` = 754 AND `npc_spells_id` = 3010; -UPDATE bot_spells_entries SET `minlevel` = 58 WHERE `spellid` = 2589 AND `npc_spells_id` = 3003; -UPDATE bot_spells_entries SET `minlevel` = 67 WHERE `spellid` = 5305 AND `npc_spells_id` = 3004; -UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 14267 AND `npc_spells_id` = 3002; -UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 14268 AND `npc_spells_id` = 3002; -UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 14269 AND `npc_spells_id` = 3002; -UPDATE bot_spells_entries SET `minlevel` = 23 WHERE `spellid` = 738 AND `npc_spells_id` = 3008; -UPDATE bot_spells_entries SET `minlevel` = 51 WHERE `spellid` = 1751 AND `npc_spells_id` = 3008; -UPDATE bot_spells_entries SET `minlevel` = 7 WHERE `spellid` = 734 AND `npc_spells_id` = 3008; -UPDATE bot_spells_entries SET `minlevel` = 5 WHERE `spellid` = 717 AND `npc_spells_id` = 3008; -UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 15186 AND `npc_spells_id` = 3005; -UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 15187 AND `npc_spells_id` = 3005; -UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 15188 AND `npc_spells_id` = 3005; -UPDATE bot_spells_entries SET `minlevel` = 80 WHERE `spellid` = 14446 AND `npc_spells_id` = 3006; -UPDATE bot_spells_entries SET `minlevel` = 80 WHERE `spellid` = 14447 AND `npc_spells_id` = 3006; -UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 14467 AND `npc_spells_id` = 3006; -UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 14468 AND `npc_spells_id` = 3006; -UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spellid` = 14469 AND `npc_spells_id` = 3006; -UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14955 AND `npc_spells_id` = 3003; -UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14956 AND `npc_spells_id` = 3003; -UPDATE bot_spells_entries SET `minlevel` = 78 WHERE `spellid` = 14387 AND `npc_spells_id` = 3006; -UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14388 AND `npc_spells_id` = 3006; -UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14389 AND `npc_spells_id` = 3006; -UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14312 AND `npc_spells_id` = 3002; -UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14313 AND `npc_spells_id` = 3002; -UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spellid` = 14314 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `minlevel` = 34 WHERE `spell_id` = 1445 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `minlevel` = 2 WHERE `spell_id` = 229 AND `npc_spells_id` = 3011; +UPDATE bot_spells_entries SET `minlevel` = 13 WHERE `spell_id` = 333 AND `npc_spells_id` = 3013; +UPDATE bot_spells_entries SET `minlevel` = 29 WHERE `spell_id` = 106 AND `npc_spells_id` = 3013; +UPDATE bot_spells_entries SET `minlevel` = 38 WHERE `spell_id` = 754 AND `npc_spells_id` = 3010; +UPDATE bot_spells_entries SET `minlevel` = 58 WHERE `spell_id` = 2589 AND `npc_spells_id` = 3003; +UPDATE bot_spells_entries SET `minlevel` = 67 WHERE `spell_id` = 5305 AND `npc_spells_id` = 3004; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spell_id` = 14267 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spell_id` = 14268 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spell_id` = 14269 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `minlevel` = 23 WHERE `spell_id` = 738 AND `npc_spells_id` = 3008; +UPDATE bot_spells_entries SET `minlevel` = 51 WHERE `spell_id` = 1751 AND `npc_spells_id` = 3008; +UPDATE bot_spells_entries SET `minlevel` = 7 WHERE `spell_id` = 734 AND `npc_spells_id` = 3008; +UPDATE bot_spells_entries SET `minlevel` = 5 WHERE `spell_id` = 717 AND `npc_spells_id` = 3008; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spell_id` = 15186 AND `npc_spells_id` = 3005; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spell_id` = 15187 AND `npc_spells_id` = 3005; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spell_id` = 15188 AND `npc_spells_id` = 3005; +UPDATE bot_spells_entries SET `minlevel` = 80 WHERE `spell_id` = 14446 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 80 WHERE `spell_id` = 14447 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spell_id` = 14467 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spell_id` = 14468 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 79 WHERE `spell_id` = 14469 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spell_id` = 14955 AND `npc_spells_id` = 3003; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spell_id` = 14956 AND `npc_spells_id` = 3003; +UPDATE bot_spells_entries SET `minlevel` = 78 WHERE `spell_id` = 14387 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spell_id` = 14388 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spell_id` = 14389 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spell_id` = 14312 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spell_id` = 14313 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `minlevel` = 77 WHERE `spell_id` = 14314 AND `npc_spells_id` = 3002; -- Maxlevel fixes -UPDATE bot_spells_entries SET `maxlevel` = 83 WHERE `spellid` = 14267 AND `npc_spells_id` = 3002; -UPDATE bot_spells_entries SET `maxlevel` = 83 WHERE `spellid` = 14268 AND `npc_spells_id` = 3002; -UPDATE bot_spells_entries SET `maxlevel` = 83 WHERE `spellid` = 14269 AND `npc_spells_id` = 3002; -UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spellid` = 14446 AND `npc_spells_id` = 3006; -UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spellid` = 14447 AND `npc_spells_id` = 3006; -UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spellid` = 14467 AND `npc_spells_id` = 3006; -UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spellid` = 14468 AND `npc_spells_id` = 3006; -UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spellid` = 14469 AND `npc_spells_id` = 3006; -UPDATE bot_spells_entries SET `maxlevel` = 81 WHERE `spellid` = 14312 AND `npc_spells_id` = 3002; -UPDATE bot_spells_entries SET `maxlevel` = 81 WHERE `spellid` = 14313 AND `npc_spells_id` = 3002; -UPDATE bot_spells_entries SET `maxlevel` = 81 WHERE `spellid` = 14314 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `maxlevel` = 83 WHERE `spell_id` = 14267 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `maxlevel` = 83 WHERE `spell_id` = 14268 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `maxlevel` = 83 WHERE `spell_id` = 14269 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spell_id` = 14446 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spell_id` = 14447 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spell_id` = 14467 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spell_id` = 14468 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spell_id` = 14469 AND `npc_spells_id` = 3006; +UPDATE bot_spells_entries SET `maxlevel` = 81 WHERE `spell_id` = 14312 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `maxlevel` = 81 WHERE `spell_id` = 14313 AND `npc_spells_id` = 3002; +UPDATE bot_spells_entries SET `maxlevel` = 81 WHERE `spell_id` = 14314 AND `npc_spells_id` = 3002; -- Type fixes -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 201; -UPDATE bot_spells_entries SET `type` = 17 WHERE `spellid` = 752; -UPDATE bot_spells_entries SET `type` = 17 WHERE `spellid` = 2117; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 2542; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 2544; -UPDATE bot_spells_entries SET `type` = 6 WHERE `spellid` = 2115; -UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 1403; -UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 1405; -UPDATE bot_spells_entries SET `type` = 9 WHERE `spellid` = 289; -UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 294; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 302; -UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 521; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 185; -UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 450; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 186; -UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 4074; -UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 195; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 1712; -UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 1703; -UPDATE bot_spells_entries SET `type` = 17 WHERE `spellid` = 3229; -UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 3345; -UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 5509; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 6826; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 270; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 281; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 505; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 526; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 110; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 506; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 162; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 111; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 507; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 527; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 163; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 112; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 1588; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 1573; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 1592; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 1577; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 1578; -UPDATE bot_spells_entries SET `type` = 1 WHERE `spellid` = 1576; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 3386; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 3387; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 4900; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 3395; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 5394; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 5392; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 6827; -UPDATE bot_spells_entries SET `type` = 1 WHERE `spellid` = 5416; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 1437; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 1436; -UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 5348; -UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 8008; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 2571; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 370; -UPDATE bot_spells_entries SET `type` = 17 WHERE `spellid` = 1741; -UPDATE bot_spells_entries SET `type` = 17 WHERE `spellid` = 1296; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 270; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 2634; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 2942; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 3462; -UPDATE bot_spells_entries SET `type` = 13 WHERE `spellid` = 6828; -UPDATE bot_spells_entries SET `type` = 4 WHERE `spellid` = 14312; -UPDATE bot_spells_entries SET `type` = 4 WHERE `spellid` = 14313; -UPDATE bot_spells_entries SET `type` = 4 WHERE `spellid` = 14314; -UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 18392; -UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 18393; -UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 18394; -UPDATE bot_spells_entries SET `type` = 10 WHERE `spellid` = 15186; -UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 15187; -UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 15188; -UPDATE bot_spells_entries SET `type` = 1 WHERE `spellid` = 14446; -UPDATE bot_spells_entries SET `type` = 1 WHERE `spellid` = 14447; -UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 14467; -UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 14468; -UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 14469; -UPDATE bot_spells_entries SET `type` = 0 WHERE `spellid` = 14267; -UPDATE bot_spells_entries SET `type` = 0 WHERE `spellid` = 14268; -UPDATE bot_spells_entries SET `type` = 0 WHERE `spellid` = 14269; -UPDATE bot_spells_entries SET `type` = 10 WHERE `spellid` = 14955; -UPDATE bot_spells_entries SET `type` = 10 WHERE `spellid` = 14956; -UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 14387; -UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 14388; -UPDATE bot_spells_entries SET `type` = 3 WHERE `spellid` = 14389; -UPDATE bot_spells_entries SET `type` = 4 WHERE `spellid` = 10436; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 201; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spell_id` = 752; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spell_id` = 2117; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 2542; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 2544; +UPDATE bot_spells_entries SET `type` = 6 WHERE `spell_id` = 2115; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 1403; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 1405; +UPDATE bot_spells_entries SET `type` = 9 WHERE `spell_id` = 289; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 294; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 302; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 521; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 185; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 450; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 186; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 4074; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 195; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 1712; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 1703; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spell_id` = 3229; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 3345; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 5509; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 6826; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 270; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 281; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 505; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 526; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 110; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 506; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 162; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 111; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 507; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 527; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 163; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 112; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 1588; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 1573; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 1592; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 1577; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 1578; +UPDATE bot_spells_entries SET `type` = 1 WHERE `spell_id` = 1576; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 3386; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 3387; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 4900; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 3395; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 5394; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 5392; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 6827; +UPDATE bot_spells_entries SET `type` = 1 WHERE `spell_id` = 5416; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 1437; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 1436; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 5348; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 8008; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 2571; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 370; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spell_id` = 1741; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spell_id` = 1296; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 270; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 2634; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 2942; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 3462; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 6828; +UPDATE bot_spells_entries SET `type` = 4 WHERE `spell_id` = 14312; +UPDATE bot_spells_entries SET `type` = 4 WHERE `spell_id` = 14313; +UPDATE bot_spells_entries SET `type` = 4 WHERE `spell_id` = 14314; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 18392; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 18393; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 18394; +UPDATE bot_spells_entries SET `type` = 10 WHERE `spell_id` = 15186; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 15187; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 15188; +UPDATE bot_spells_entries SET `type` = 1 WHERE `spell_id` = 14446; +UPDATE bot_spells_entries SET `type` = 1 WHERE `spell_id` = 14447; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 14467; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 14468; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 14469; +UPDATE bot_spells_entries SET `type` = 0 WHERE `spell_id` = 14267; +UPDATE bot_spells_entries SET `type` = 0 WHERE `spell_id` = 14268; +UPDATE bot_spells_entries SET `type` = 0 WHERE `spell_id` = 14269; +UPDATE bot_spells_entries SET `type` = 10 WHERE `spell_id` = 14955; +UPDATE bot_spells_entries SET `type` = 10 WHERE `spell_id` = 14956; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 14387; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 14388; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 14389; +UPDATE bot_spells_entries SET `type` = 4 WHERE `spell_id` = 10436; --- UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 3440; -- Ro's Illumination [#3440] from DoT [#8] to Debuff [#14] [Should be 0] --- UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 303; -- Whirl till you hurl [#303] from Nuke [#0] to Debuff [#14] [Should be 0] --- UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 619; -- Dyn's Dizzying Draught [#619] from Nuke [#0] to Debuff [#14] [Should be 0] +-- UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 3440; -- Ro's Illumination [#3440] from DoT [#8] to Debuff [#14] [Should be 0] +-- UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 303; -- Whirl till you hurl [#303] from Nuke [#0] to Debuff [#14] [Should be 0] +-- UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 619; -- Dyn's Dizzying Draught [#619] from Nuke [#0] to Debuff [#14] [Should be 0] --- UPDATE bot_spells_entries SET `type` = 14 WHERE `spellid` = 74; -- Mana Sieve [#74] from Nuke [#0] to Debuff [#14] --- UPDATE bot_spells_entries SET `type` = 6 WHERE `spellid` = 1686; -- Theft of Thought [#1686] from Nuke [#0] to Lifetap [#6] +-- UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 74; -- Mana Sieve [#74] from Nuke [#0] to Debuff [#14] +-- UPDATE bot_spells_entries SET `type` = 6 WHERE `spell_id` = 1686; -- Theft of Thought [#1686] from Nuke [#0] to Lifetap [#6] --- UPDATE bot_spells_entries SET `type` = 1 WHERE `spellid` = 3694; -- Stoicism [#3694] from In-Combat Buff [#10] to Regular Heal [#1] --- UPDATE bot_spells_entries SET `type` = 1 WHERE `spellid` = 4899; -- Breath of Trushar [#4899] from In-Combat Buff [#10] to Regular Heal [#1] +-- UPDATE bot_spells_entries SET `type` = 1 WHERE `spell_id` = 3694; -- Stoicism [#3694] from In-Combat Buff [#10] to Regular Heal [#1] +-- UPDATE bot_spells_entries SET `type` = 1 WHERE `spell_id` = 4899; -- Breath of Trushar [#4899] from In-Combat Buff [#10] to Regular Heal [#1] --- UPDATE bot_spells_entries SET `type` = 7 WHERE `spellid` = 738; -- Selo's Consonant Chain [#738] from Slow [#13] to Snare [#7] --- UPDATE bot_spells_entries SET `type` = 7 WHERE `spellid` = 1751; -- Largo's Assonant Binding [#1751] from Slow [#13] to Snare [#7] --- UPDATE bot_spells_entries SET `type` = 8 WHERE `spellid` = 1748; -- Angstlich's Assonance [#1748] from Slow [#13] to DoT [#8] --- UPDATE bot_spells_entries SET `type` = 7 WHERE `spellid` = 738; -- Selo's Consonant Chain [#738] from Slow [#13] to Snare [#7] --- UPDATE bot_spells_entries SET `type` = 7 WHERE `spellid` = 1751; -- Largo's Assonant Binding [#1751] from Slow [#13] to Snare [#7] --- UPDATE bot_spells_entries SET `type` = 7 WHERE `spellid` = 738; -- Selo's Consonant Chain [#738] from Slow [#13] to Snare [#7] +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spell_id` = 738; -- Selo's Consonant Chain [#738] from Slow [#13] to Snare [#7] +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spell_id` = 1751; -- Largo's Assonant Binding [#1751] from Slow [#13] to Snare [#7] +-- UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 1748; -- Angstlich's Assonance [#1748] from Slow [#13] to DoT [#8] +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spell_id` = 738; -- Selo's Consonant Chain [#738] from Slow [#13] to Snare [#7] +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spell_id` = 1751; -- Largo's Assonant Binding [#1751] from Slow [#13] to Snare [#7] +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spell_id` = 738; -- Selo's Consonant Chain [#738] from Slow [#13] to Snare [#7] +)" + }, + ManifestEntry{ + .version = 9050, + .description = "2024_11_26_add_commanded_spelltypes.sql", + .check = "SELECT * FROM `bot_spells_entries` where `type` = 100", + .condition = "empty", + .match = "", + .sql = R"( +INSERT INTO `bot_spells_entries` (`npc_spells_id`, `spell_id`, `type`, `minlevel`, `maxlevel`) +VALUES +(3006, 9957, 100, 20, 254), +(3006, 9956, 100, 20, 254), +(3006, 552, 100, 25, 254), +(3006, 550, 100, 25, 254), +(3006, 553, 100, 25, 254), +(3006, 2432, 100, 26, 254), +(3006, 2020, 100, 26, 254), +(3006, 551, 100, 27, 254), +(3006, 3792, 100, 28, 254), +(3006, 2419, 100, 29, 254), +(3006, 554, 100, 30, 254), +(3006, 557, 100, 31, 254), +(3006, 1434, 100, 32, 254), +(3006, 555, 100, 32, 254), +(3006, 25898, 100, 32, 254), +(3006, 25904, 100, 32, 254), +(3006, 556, 100, 32, 254), +(3006, 25698, 100, 33, 254), +(3006, 1517, 100, 33, 254), +(3006, 2424, 100, 33, 254), +(3006, 25689, 100, 33, 254), +(3006, 25899, 100, 34, 254), +(3006, 25690, 100, 35, 254), +(3006, 25903, 100, 35, 254), +(3006, 25900, 100, 35, 254), +(3006, 558, 100, 36, 254), +(3006, 2429, 100, 37, 254), +(3006, 1438, 100, 38, 254), +(3006, 3184, 100, 38, 254), +(3006, 25697, 100, 38, 254), +(3006, 25902, 100, 39, 254), +(3006, 25695, 100, 39, 254), +(3006, 25901, 100, 40, 254), +(3006, 25694, 100, 40, 254), +(3006, 1398, 100, 40, 254), +(3006, 25905, 100, 41, 254), +(3006, 25696, 100, 42, 254), +(3006, 1440, 100, 42, 254), +(3006, 25906, 100, 43, 254), +(3006, 25693, 100, 44, 254), +(3006, 25699, 100, 45, 254), +(3006, 24773, 100, 46, 254), +(3006, 8965, 100, 52, 254), +(3006, 24771, 100, 52, 254), +(3006, 8235, 100, 52, 254), +(3006, 24775, 100, 52, 254), +(3006, 4966, 100, 54, 254), +(3006, 6184, 100, 55, 254), +(3006, 5731, 100, 55, 254), +(3006, 24776, 100, 56, 254), +(3006, 25700, 100, 56, 254), +(3006, 25691, 100, 57, 254), +(3006, 24772, 100, 57, 254), +(3006, 25692, 100, 57, 254), +(3006, 11981, 100, 59, 254), +(3006, 9953, 100, 60, 254), +(3006, 9954, 100, 60, 254), +(3006, 11980, 100, 64, 254), +(3006, 6179, 100, 64, 254), +(3006, 24774, 100, 67, 254), +(3006, 9950, 100, 70, 254), +(3006, 9951, 100, 70, 254), +(3006, 15886, 100, 75, 254), +(3006, 15887, 100, 75, 254), +(3006, 21989, 100, 80, 254), +(3006, 20539, 100, 80, 254), +(3006, 21984, 100, 80, 254), +(3006, 20538, 100, 80, 254), +(3006, 17883, 100, 85, 254), +(3006, 17884, 100, 85, 254), +(3006, 28997, 100, 90, 254), +(3006, 28998, 100, 90, 254), +(3006, 29000, 100, 92, 254), +(3006, 29001, 100, 92, 254), +(3006, 34832, 100, 95, 254), +(3006, 40217, 100, 95, 254), +(3006, 34833, 100, 95, 254), +(3006, 40216, 100, 95, 254), +(3012, 10881, 100, 20, 254), +(3012, 10880, 100, 20, 254), +(3012, 562, 100, 25, 254), +(3012, 563, 100, 27, 254), +(3012, 3793, 100, 27, 254), +(3012, 561, 100, 28, 254), +(3012, 2420, 100, 29, 254), +(3012, 2944, 100, 29, 254), +(3012, 564, 100, 32, 254), +(3012, 565, 100, 33, 254), +(3012, 1418, 100, 33, 254), +(3012, 2425, 100, 33, 254), +(3012, 1516, 100, 34, 254), +(3012, 1338, 100, 35, 254), +(3012, 3833, 100, 35, 254), +(3012, 566, 100, 35, 254), +(3012, 1336, 100, 36, 254), +(3012, 2943, 100, 36, 254), +(3012, 1423, 100, 36, 254), +(3012, 567, 100, 36, 254), +(3012, 568, 100, 37, 254), +(3012, 1337, 100, 37, 254), +(3012, 3180, 100, 38, 254), +(3012, 1339, 100, 38, 254), +(3012, 2421, 100, 39, 254), +(3012, 2430, 100, 39, 254), +(3012, 1372, 100, 40, 254), +(3012, 2426, 100, 41, 254), +(3012, 1371, 100, 41, 254), +(3012, 1399, 100, 42, 254), +(3012, 1374, 100, 42, 254), +(3012, 1373, 100, 43, 254), +(3012, 1425, 100, 43, 254), +(3012, 1375, 100, 44, 254), +(3012, 3181, 100, 45, 254), +(3012, 2022, 100, 45, 254), +(3012, 666, 100, 46, 254), +(3012, 3849, 100, 46, 254), +(3012, 674, 100, 46, 254), +(3012, 2023, 100, 46, 254), +(3012, 2024, 100, 47, 254), +(3012, 2025, 100, 48, 254), +(3012, 2431, 100, 49, 254), +(3012, 8966, 100, 51, 254), +(3012, 8236, 100, 51, 254), +(3012, 4965, 100, 54, 254), +(3012, 8969, 100, 55, 254), +(3012, 8239, 100, 55, 254), +(3012, 6183, 100, 55, 254), +(3012, 5732, 100, 55, 254), +(3012, 4964, 100, 57, 254), +(3012, 6182, 100, 58, 254), +(3012, 5735, 100, 60, 254), +(3012, 10877, 100, 60, 254), +(3012, 10878, 100, 60, 254), +(3012, 6178, 100, 64, 254), +(3012, 6177, 100, 67, 254), +(3012, 11984, 100, 69, 254), +(3012, 10874, 100, 70, 254), +(3012, 10875, 100, 70, 254), +(3012, 11983, 100, 74, 254), +(3012, 15889, 100, 75, 254), +(3012, 15890, 100, 75, 254), +(3012, 21988, 100, 80, 254), +(3012, 20542, 100, 80, 254), +(3012, 21985, 100, 80, 254), +(3012, 20541, 100, 80, 254), +(3012, 17886, 100, 85, 254), +(3012, 17887, 100, 85, 254), +(3012, 29840, 100, 90, 254), +(3012, 29841, 100, 90, 254), +(3012, 29843, 100, 92, 254), +(3012, 29844, 100, 92, 254), +(3012, 40443, 100, 95, 254), +(3012, 35715, 100, 95, 254), +(3012, 40442, 100, 95, 254), +(3012, 35714, 100, 95, 254), +(3002, 208, 101, 1, 4), +(3002, 501, 101, 5, 14), +(3002, 47, 101, 15, 35), +(3002, 45, 101, 36, 64), +(3002, 1541, 101, 55, 254), +(3002, 3197, 101, 65, 254), +(3002, 5274, 101, 70, 70), +(3002, 9798, 101, 71, 75), +(3002, 9799, 101, 71, 75), +(3002, 9797, 101, 71, 75), +(3002, 14288, 101, 76, 80), +(3002, 14289, 101, 76, 80), +(3002, 14290, 101, 76, 80), +(3002, 18309, 101, 81, 85), +(3002, 18310, 101, 81, 85), +(3002, 18311, 101, 81, 85), +(3002, 25103, 101, 86, 90), +(3002, 25101, 101, 86, 90), +(3002, 25102, 101, 86, 90), +(3002, 28102, 101, 91, 95), +(3002, 28100, 101, 91, 95), +(3002, 28101, 101, 91, 95), +(3002, 34096, 101, 96, 254), +(3002, 34094, 101, 96, 254), +(3002, 34095, 101, 96, 254), +(3003, 208, 101, 10, 24), +(3003, 501, 101, 25, 42), +(3003, 47, 101, 43, 48), +(3003, 45, 101, 49, 254), +(3003, 25294, 101, 86, 90), +(3003, 25295, 101, 86, 90), +(3003, 25296, 101, 86, 90), +(3003, 28340, 101, 91, 95), +(3003, 28338, 101, 91, 95), +(3003, 28339, 101, 91, 95), +(3003, 34346, 101, 96, 254), +(3003, 34344, 101, 96, 254), +(3003, 34345, 101, 96, 254), +(3004, 240, 101, 4, 30), +(3004, 250, 101, 22, 254), +(3004, 513, 101, 31, 254), +(3004, 3601, 101, 39, 254), +(3004, 5316, 101, 68, 70), +(3004, 10112, 101, 71, 75), +(3004, 10110, 101, 71, 75), +(3004, 10111, 101, 71, 75), +(3004, 15037, 101, 76, 80), +(3004, 15035, 101, 76, 80), +(3004, 15036, 101, 76, 80), +(3004, 19168, 101, 81, 85), +(3004, 19169, 101, 81, 85), +(3004, 19167, 101, 81, 85), +(3004, 25417, 101, 86, 87), +(3004, 25418, 101, 86, 90), +(3004, 25419, 101, 86, 90), +(3004, 25466, 101, 88, 90), +(3004, 25467, 101, 88, 90), +(3004, 25465, 101, 88, 90), +(3004, 28479, 101, 91, 92), +(3004, 28480, 101, 91, 95), +(3004, 28481, 101, 91, 95), +(3004, 28542, 101, 93, 95), +(3004, 28543, 101, 93, 95), +(3004, 28544, 101, 93, 95), +(3004, 34500, 101, 96, 97), +(3004, 34502, 101, 96, 254), +(3004, 34501, 101, 96, 254), +(3004, 34565, 101, 98, 254), +(3004, 34563, 101, 98, 254), +(3004, 34564, 101, 98, 254), +(3005, 347, 101, 9, 51), +(3005, 448, 101, 52, 254), +(3006, 240, 101, 1, 14), +(3006, 250, 101, 5, 254), +(3006, 513, 101, 15, 254), +(3006, 3601, 101, 29, 254), +(3006, 5347, 101, 67, 70), +(3006, 9851, 101, 71, 75), +(3006, 9852, 101, 71, 75), +(3006, 9853, 101, 71, 75), +(3006, 14369, 101, 76, 80), +(3006, 14367, 101, 76, 80), +(3006, 14368, 101, 76, 80), +(3006, 18409, 101, 81, 85), +(3006, 18407, 101, 81, 85), +(3006, 18408, 101, 81, 85), +(3006, 25736, 101, 86, 90), +(3006, 25734, 101, 86, 90), +(3006, 25735, 101, 86, 90), +(3006, 28831, 101, 91, 95), +(3006, 28832, 101, 91, 95), +(3006, 28830, 101, 91, 95), +(3006, 34863, 101, 96, 254), +(3006, 34864, 101, 96, 254), +(3006, 34862, 101, 96, 254), +(3007, 4614, 101, 35, 49), +(3007, 4683, 101, 50, 56), +(3007, 4684, 101, 57, 63), +(3007, 4698, 101, 64, 64), +(3007, 5019, 101, 65, 254), +(3007, 5020, 101, 65, 254), +(3007, 6175, 101, 69, 70), +(3007, 10949, 101, 71, 75), +(3007, 10947, 101, 71, 75), +(3007, 10948, 101, 71, 75), +(3007, 14800, 101, 76, 80), +(3007, 14801, 101, 76, 80), +(3007, 14799, 101, 76, 80), +(3007, 18905, 101, 81, 85), +(3007, 18906, 101, 81, 85), +(3007, 18904, 101, 81, 85), +(3007, 25912, 101, 86, 90), +(3007, 25913, 101, 86, 90), +(3007, 25911, 101, 86, 90), +(3007, 29006, 101, 91, 95), +(3007, 29007, 101, 91, 95), +(3007, 29008, 101, 91, 95), +(3007, 35047, 101, 96, 254), +(3007, 35048, 101, 96, 254), +(3007, 35049, 101, 96, 254), +(3008, 728, 101, 8, 60), +(3008, 3361, 101, 61, 254), +(3008, 5370, 101, 66, 70), +(3008, 10403, 101, 71, 75), +(3008, 10401, 101, 71, 75), +(3008, 10402, 101, 71, 75), +(3008, 14002, 101, 76, 80), +(3008, 14000, 101, 76, 80), +(3008, 14001, 101, 76, 80), +(3008, 18001, 101, 81, 85), +(3008, 18002, 101, 81, 85), +(3008, 18000, 101, 81, 85), +(3008, 25978, 101, 86, 90), +(3008, 25979, 101, 86, 90), +(3008, 25977, 101, 86, 90), +(3008, 29079, 101, 91, 95), +(3008, 29080, 101, 91, 95), +(3008, 29078, 101, 91, 95), +(3008, 35131, 101, 96, 254), +(3008, 35132, 101, 96, 254), +(3008, 35133, 101, 96, 254), +(3011, 347, 101, 2, 22), +(3011, 448, 101, 23, 254), +(3014, 208, 101, 1, 5), +(3014, 501, 101, 6, 17), +(3014, 47, 101, 18, 34), +(3014, 45, 101, 35, 61), +(3014, 1541, 101, 51, 254), +(3014, 3197, 101, 62, 254), +(3014, 5506, 101, 67, 71), +(3014, 10601, 101, 72, 76), +(3014, 10599, 101, 72, 76), +(3014, 10600, 101, 72, 76), +(3014, 14510, 101, 77, 81), +(3014, 14511, 101, 77, 81), +(3014, 14509, 101, 77, 81), +(3014, 18568, 101, 82, 86), +(3014, 18569, 101, 82, 86), +(3014, 18567, 101, 82, 86), +(3014, 26921, 101, 87, 91), +(3014, 26922, 101, 87, 91), +(3014, 26920, 101, 87, 91), +(3014, 30054, 101, 92, 96), +(3014, 30055, 101, 92, 96), +(3014, 30056, 101, 92, 96), +(3014, 36116, 101, 97, 254), +(3014, 36117, 101, 97, 254), +(3014, 36118, 101, 97, 254), +(3006, 2183, 102, 18, 56), +(3006, 1567, 102, 57, 254), +(3012, 2184, 102, 18, 56), +(3012, 2558, 102, 56, 64), +(3012, 1628, 102, 57, 254), +(3012, 3244, 102, 65, 254), +(3002, 35, 103, 10, 254), +(3006, 35, 103, 12, 254), +(3010, 35, 103, 14, 254), +(3011, 35, 103, 12, 254), +(3012, 35, 103, 12, 254), +(3013, 35, 103, 12, 254), +(3014, 35, 103, 12, 254), +(3008, 737, 104, 14, 254), +(3011, 305, 104, 17, 254), +(3012, 305, 104, 14, 254), +(3013, 305, 104, 13, 254), +(3014, 305, 104, 15, 254), +(3004, 261, 105, 35, 64), +(3004, 2517, 105, 65, 254), +(3006, 261, 105, 14, 49), +(3006, 2894, 105, 50, 53), +(3006, 2517, 105, 54, 254), +(3006, 3185, 105, 62, 254), +(3008, 718, 105, 31, 50), +(3008, 1750, 105, 51, 254), +(3010, 261, 105, 10, 50), +(3010, 2894, 105, 51, 254), +(3011, 457, 105, 41, 254), +(3011, 1391, 105, 45, 254), +(3012, 261, 105, 22, 49), +(3012, 2894, 105, 50, 254), +(3014, 261, 105, 15, 50), +(3014, 2894, 105, 51, 254), +(3015, 261, 105, 32, 254), +(3003, 1743, 106, 55, 254), +(3008, 714, 106, 41, 254), +(3008, 748, 106, 47, 57), +(3008, 1450, 106, 49, 254), +(3008, 1752, 106, 52, 254), +(3008, 1763, 106, 58, 72), +(3008, 11881, 106, 73, 77), +(3008, 11879, 106, 73, 77), +(3008, 11880, 106, 73, 77), +(3008, 14055, 106, 78, 82), +(3008, 14056, 106, 78, 82), +(3008, 14054, 106, 78, 82), +(3008, 18040, 106, 83, 87), +(3008, 18041, 106, 83, 87), +(3008, 18039, 106, 83, 87), +(3008, 26026, 106, 88, 92), +(3008, 26027, 106, 88, 92), +(3008, 26025, 106, 88, 92), +(3008, 29120, 106, 93, 97), +(3008, 29121, 106, 93, 97), +(3008, 29122, 106, 93, 97), +(3008, 35170, 106, 98, 254), +(3008, 35171, 106, 98, 254), +(3008, 35172, 106, 98, 254), +(3012, 2559, 106, 58, 254), +(3014, 481, 106, 13, 21), +(3014, 21, 106, 19, 21), +(3014, 482, 106, 22, 32), +(3014, 483, 106, 33, 39), +(3014, 648, 106, 38, 39), +(3014, 484, 106, 40, 57), +(3014, 176, 106, 47, 57), +(3014, 1689, 106, 52, 57), +(3014, 1713, 106, 58, 60), +(3014, 3343, 106, 61, 66), +(3014, 6739, 106, 61, 68), +(3014, 3351, 106, 63, 66), +(3014, 5504, 106, 67, 70), +(3014, 5514, 106, 69, 70), +(3014, 6671, 106, 69, 254), +(3014, 10598, 106, 71, 75), +(3014, 10596, 106, 71, 75), +(3014, 10597, 106, 71, 75), +(3014, 10643, 106, 74, 75), +(3014, 10641, 106, 74, 75), +(3014, 10642, 106, 74, 75), +(3014, 11887, 106, 74, 78), +(3014, 11885, 106, 74, 78), +(3014, 11886, 106, 74, 78), +(3014, 14507, 106, 76, 80), +(3014, 14508, 106, 76, 80), +(3014, 14506, 106, 76, 80), +(3014, 14543, 106, 79, 80), +(3014, 14544, 106, 79, 80), +(3014, 14542, 106, 79, 80), +(3014, 14582, 106, 79, 83), +(3014, 14583, 106, 79, 83), +(3014, 14581, 106, 79, 83), +(3014, 18564, 106, 81, 85), +(3014, 18565, 106, 81, 85), +(3014, 18566, 106, 81, 85), +(3014, 18600, 106, 84, 85), +(3014, 18601, 106, 84, 85), +(3014, 18602, 106, 84, 85), +(3014, 18641, 106, 84, 88), +(3014, 18639, 106, 84, 88), +(3014, 18640, 106, 84, 88), +(3014, 26901, 106, 86, 90), +(3014, 26899, 106, 86, 90), +(3014, 26900, 106, 86, 90), +(3014, 26999, 106, 89, 90), +(3014, 26997, 106, 89, 90), +(3014, 26998, 106, 89, 90), +(3014, 27025, 106, 89, 93), +(3014, 27026, 106, 89, 93), +(3014, 27024, 106, 89, 93), +(3014, 30028, 106, 91, 95), +(3014, 30029, 106, 91, 95), +(3014, 30027, 106, 91, 95), +(3014, 30132, 106, 93, 95), +(3014, 30133, 106, 93, 95), +(3014, 30131, 106, 93, 95), +(3014, 30140, 106, 94, 95), +(3014, 30141, 106, 94, 95), +(3014, 30142, 106, 94, 95), +(3014, 30167, 106, 94, 98), +(3014, 30168, 106, 94, 98), +(3014, 30169, 106, 94, 98), +(3014, 36091, 106, 96, 254), +(3014, 36089, 106, 96, 254), +(3014, 36090, 106, 96, 254), +(3014, 36189, 106, 98, 254), +(3014, 36187, 106, 98, 254), +(3014, 36188, 106, 98, 254), +(3014, 36216, 106, 99, 254), +(3014, 36196, 106, 99, 254), +(3014, 36217, 106, 99, 254), +(3014, 36194, 106, 99, 254), +(3014, 36215, 106, 99, 254), +(3014, 36195, 106, 99, 254), +(3004, 86, 107, 20, 254), +(3006, 86, 107, 6, 49), +(3006, 2881, 107, 50, 254), +(3008, 729, 107, 16, 254), +(3010, 86, 107, 12, 50), +(3010, 2881, 107, 51, 254), +(3011, 457, 107, 41, 254), +(3011, 1391, 107, 45, 254), +(3014, 86, 107, 12, 43), +(3014, 3696, 107, 44, 50), +(3014, 2881, 107, 51, 254), +(3015, 86, 107, 25, 254), +(3010, 345, 108, 15, 254), +(3010, 2522, 108, 16, 254), +(3015, 345, 108, 23, 254), +(3002, 235, 109, 11, 254), +(3002, 1726, 109, 51, 254), +(3002, 6125, 109, 66, 254), +(3002, 14348, 109, 77, 81), +(3002, 14346, 109, 77, 81), +(3002, 14347, 109, 77, 81), +(3002, 18369, 109, 82, 254), +(3002, 18367, 109, 82, 254), +(3002, 18368, 109, 82, 254), +(3003, 235, 109, 17, 254), +(3004, 247, 109, 14, 46), +(3004, 80, 109, 32, 64), +(3004, 34, 109, 47, 254), +(3004, 2517, 109, 65, 254), +(3005, 235, 109, 4, 254), +(3006, 247, 109, 4, 17), +(3006, 255, 109, 8, 254), +(3006, 80, 109, 13, 53), +(3006, 34, 109, 18, 254), +(3006, 2516, 109, 52, 254), +(3006, 4058, 109, 52, 254), +(3006, 2517, 109, 54, 254), +(3006, 3185, 109, 62, 254), +(3006, 6123, 109, 68, 254), +(3008, 719, 109, 19, 50), +(3008, 735, 109, 24, 254), +(3008, 1750, 109, 51, 254), +(3010, 79, 109, 7, 55), +(3010, 255, 109, 10, 254), +(3010, 42, 109, 27, 254), +(3010, 1575, 109, 56, 254), +(3010, 2886, 109, 58, 254), +(3011, 235, 109, 1, 254), +(3011, 457, 109, 41, 254), +(3011, 1391, 109, 45, 254), +(3011, 6124, 109, 68, 254), +(3012, 80, 109, 4, 39), +(3012, 42, 109, 16, 254), +(3012, 3811, 109, 40, 254), +(3012, 6120, 109, 67, 254), +(3012, 15513, 109, 76, 80), +(3012, 15511, 109, 76, 80), +(3012, 15512, 109, 76, 80), +(3012, 19701, 109, 81, 254), +(3012, 19699, 109, 81, 254), +(3012, 19700, 109, 81, 254), +(3013, 42, 109, 8, 254), +(3013, 80, 109, 16, 254), +(3014, 42, 109, 4, 254), +(3014, 80, 109, 6, 43), +(3014, 235, 109, 14, 254), +(3014, 3696, 109, 44, 254), +(3014, 6122, 109, 66, 254), +(3015, 79, 109, 29, 64), +(3015, 42, 109, 43, 254), +(3015, 1575, 109, 65, 254), +(3004, 278, 110, 28, 64), +(3004, 4054, 110, 41, 64), +(3004, 4055, 110, 49, 254), +(3004, 2517, 110, 65, 254), +(3006, 278, 110, 10, 53), +(3006, 424, 110, 26, 254), +(3006, 4054, 110, 30, 53), +(3006, 169, 110, 35, 61), +(3006, 4055, 110, 35, 254), +(3006, 3579, 110, 45, 254), +(3006, 4058, 110, 52, 254), +(3006, 1554, 110, 53, 254), +(3006, 2517, 110, 54, 254), +(3006, 3185, 110, 62, 254), +(3008, 717, 110, 5, 48), +(3008, 4395, 110, 25, 48), +(3008, 2605, 110, 49, 50), +(3008, 1750, 110, 51, 254), +(3010, 278, 110, 9, 254), +(3010, 424, 110, 22, 254), +(3010, 4054, 110, 29, 254), +(3010, 4055, 110, 34, 254), +(3010, 2524, 110, 36, 254), +(3010, 1554, 110, 52, 254), +(3015, 278, 110, 24, 254), +(3015, 4054, 110, 39, 254), +(3015, 4055, 110, 44, 254), +(3012, 1422, 111, 50, 254), +(3012, 1334, 111, 52, 254), +(3005, 2213, 112, 12, 34), +(3005, 3, 112, 35, 56), +(3005, 1773, 112, 57, 70), +(3005, 10042, 112, 71, 75), +(3005, 14823, 112, 76, 80), +(3005, 18928, 112, 81, 85), +(3005, 25555, 112, 86, 90), +(3005, 28632, 112, 91, 95), +(3005, 34662, 112, 96, 254), +(3011, 2213, 112, 12, 34), +(3011, 3, 112, 35, 56), +(3011, 1773, 112, 57, 70), +(3011, 10042, 112, 71, 75), +(3011, 14823, 112, 76, 80), +(3011, 18928, 112, 81, 85), +(3011, 25555, 112, 86, 90), +(3011, 28632, 112, 91, 95), +(3011, 34662, 112, 96, 254); )" } // -- template; copy/paste this when you need to create a new entry diff --git a/common/ruletypes.h b/common/ruletypes.h index 8096c037b..32c8378fb 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -871,6 +871,10 @@ RULE_INT(Bots, DefaultFollowDistance, 20, "Default 20. Distance a bot will follo RULE_INT(Bots, MaxFollowDistance, 300, "Default 300. Max distance a bot can be set to follow behind.") RULE_INT(Bots, MaxDistanceRanged, 300, "Default 300. Max distance a bot can be set to ranged.") RULE_BOOL(Bots, AllowAIMez, true, "If enabled bots will automatically mez/AE mez eligible targets.") +RULE_BOOL(Bots, AllowCommandedCharm, true, "If enabled bots can be commanded to charm NPCs.") +RULE_BOOL(Bots, AllowCommandedMez, true, "If enabled bots can be commanded to mez NPCs.") +RULE_BOOL(Bots, AllowCommandedResurrect, true, "If enabled bots can be commanded to resurrect players.") +RULE_BOOL(Bots, AllowCommandedSummonCorpse, true, "If enabled bots can be commanded to summon other's corpses.") RULE_INT(Bots, CampTimer, 25, "Number of seconds after /camp has begun before bots camp out.") RULE_BOOL(Bots, SendClassRaceOnHelp, true, "If enabled a reminder of how to check class/race IDs will be sent when using compatible commands.") RULE_CATEGORY_END() diff --git a/common/spdat.cpp b/common/spdat.cpp index 7b0e54089..a691d5197 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -2842,6 +2842,7 @@ bool BOT_SPELL_TYPES_DETRIMENTAL(uint16 spellType, uint8 cls) { case BotSpellTypes::AEDoT: case BotSpellTypes::AELifetap: case BotSpellTypes::PBAENuke: + case BotSpellTypes::Lull: return true; case BotSpellTypes::InCombatBuff: if (cls == Class::ShadowKnight) { @@ -2885,6 +2886,18 @@ bool BOT_SPELL_TYPES_BENEFICIAL(uint16 spellType, uint8 cls) { case BotSpellTypes::PetResistBuffs: case BotSpellTypes::ResistBuffs: case BotSpellTypes::Resurrect: + case BotSpellTypes::Teleport: + case BotSpellTypes::Succor: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::Identify: + case BotSpellTypes::Levitate: + case BotSpellTypes::Rune: + case BotSpellTypes::WaterBreathing: + case BotSpellTypes::Size: + case BotSpellTypes::Invisibility: + case BotSpellTypes::MovementSpeed: + case BotSpellTypes::SendHome: + case BotSpellTypes::SummonCorpse: return true; case BotSpellTypes::InCombatBuff: if (cls == Class::ShadowKnight) { @@ -2922,6 +2935,18 @@ bool BOT_SPELL_TYPES_OTHER_BENEFICIAL(uint16 spellType) { case BotSpellTypes::PetBuffs: case BotSpellTypes::ResistBuffs: case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::Teleport: + case BotSpellTypes::Succor: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::Identify: + case BotSpellTypes::Levitate: + case BotSpellTypes::Rune: + case BotSpellTypes::WaterBreathing: + case BotSpellTypes::Size: + case BotSpellTypes::Invisibility: + case BotSpellTypes::MovementSpeed: + case BotSpellTypes::SendHome: + case BotSpellTypes::SummonCorpse: return true; default: return false; @@ -2953,6 +2978,7 @@ bool BOT_SPELL_TYPES_INNATE(uint16 spellType) { case BotSpellTypes::Stun: case BotSpellTypes::AEMez: case BotSpellTypes::Mez: + case BotSpellTypes::Lull: return true; default: return false; @@ -3234,3 +3260,34 @@ bool IsDamageShieldOnlySpell(uint16 spell_id) { return true; } + +bool IsCommandedSpellType(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::Lull: + case BotSpellTypes::Teleport: + case BotSpellTypes::Succor: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::Identify: + case BotSpellTypes::Levitate: + case BotSpellTypes::Rune: + case BotSpellTypes::WaterBreathing: + case BotSpellTypes::Size: + case BotSpellTypes::Invisibility: + case BotSpellTypes::MovementSpeed: + case BotSpellTypes::SendHome: + case BotSpellTypes::SummonCorpse: + //case BotSpellTypes::Charm: + //case BotSpellTypes::Resurrect: + //case BotSpellTypes::Cure: + //case BotSpellTypes::GroupCures: + //case BotSpellTypes::DamageShields: + //case BotSpellTypes::PetDamageShields: + //case BotSpellTypes::ResistBuffs: + //case BotSpellTypes::PetResistBuffs: + return true; + default: + return false; + } + + return false; +} diff --git a/common/spdat.h b/common/spdat.h index 4c1b52eca..cefac6784 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -708,9 +708,26 @@ namespace BotSpellTypes constexpr uint16 ResistBuffs = 52; constexpr uint16 PetDamageShields = 53; constexpr uint16 PetResistBuffs = 54; + + // Command Spell Types + constexpr uint16 Teleport = 100; // this is handled by ^depart so uses other logic + constexpr uint16 Lull = 101; + constexpr uint16 Succor = 102; + constexpr uint16 BindAffinity = 103; + constexpr uint16 Identify = 104; + constexpr uint16 Levitate = 105; + constexpr uint16 Rune = 106; + constexpr uint16 WaterBreathing = 107; + constexpr uint16 Size = 108; + constexpr uint16 Invisibility = 109; + constexpr uint16 MovementSpeed = 110; + constexpr uint16 SendHome = 111; + constexpr uint16 SummonCorpse = 112; constexpr uint16 START = BotSpellTypes::Nuke; // Do not remove or change this constexpr uint16 END = BotSpellTypes::PetResistBuffs; // Do not remove this, increment as needed + constexpr uint16 COMMANDED_START = BotSpellTypes::Lull; // Do not remove or change this + constexpr uint16 COMMANDED_END = BotSpellTypes::SummonCorpse; // Do not remove this, increment as needed } const uint32 SPELL_TYPES_DETRIMENTAL = (SpellType_Nuke | SpellType_Root | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Charm | SpellType_Debuff | SpellType_Slow); @@ -730,6 +747,7 @@ bool IsClientBotSpellType(uint16 spellType); bool IsHealBotSpellType(uint16 spellType); bool SpellTypeRequiresLoS(uint16 spellType, uint16 cls = 0); bool SpellTypeRequiresTarget(uint16 spellType, uint16 cls = 0); +bool IsCommandedSpellType(uint16 spellType); // These should not be used to determine spell category.. // They are a graphical affects (effects?) index only diff --git a/common/version.h b/common/version.h index 7cf293612..d9d57e7ca 100644 --- a/common/version.h +++ b/common/version.h @@ -43,7 +43,7 @@ */ #define CURRENT_BINARY_DATABASE_VERSION 9284 -#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9049 //TODO update as needed +#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9050 //TODO update as needed #endif diff --git a/zone/bot.cpp b/zone/bot.cpp index 988c0d4e3..56736942e 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9538,6 +9538,17 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spell_id, Mob* tar) { case BotSpellTypes::PetDamageShields: case BotSpellTypes::ResistBuffs: case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::Teleport: + case BotSpellTypes::Succor: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::Identify: + case BotSpellTypes::Levitate: + case BotSpellTypes::Rune: + case BotSpellTypes::WaterBreathing: + case BotSpellTypes::Size: + case BotSpellTypes::Invisibility: + case BotSpellTypes::MovementSpeed: + case BotSpellTypes::SendHome: if ( !( spells[spell_id].target_type == ST_Target || @@ -10904,6 +10915,18 @@ uint16 Bot::GetSpellListSpellType(uint16 spellType) { case BotSpellTypes::PetDamageShields: case BotSpellTypes::ResistBuffs: case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::Teleport: + case BotSpellTypes::Succor: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::Identify: + case BotSpellTypes::Levitate: + case BotSpellTypes::Rune: + case BotSpellTypes::WaterBreathing: + case BotSpellTypes::Size: + case BotSpellTypes::Invisibility: + case BotSpellTypes::MovementSpeed: + case BotSpellTypes::SendHome: + case BotSpellTypes::SummonCorpse: return BotSpellTypes::Buff; case BotSpellTypes::AEMez: case BotSpellTypes::Mez: @@ -10945,6 +10968,7 @@ uint16 Bot::GetSpellListSpellType(uint16 spellType) { case BotSpellTypes::PreCombatBuff: case BotSpellTypes::PreCombatBuffSong: case BotSpellTypes::Resurrect: + case BotSpellTypes::Lull: default: return spellType; } @@ -11007,6 +11031,84 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spellType, uint16 spell_id) { return true; } + return false; + case BotSpellTypes::Lull: + if (!IsHarmonySpell(spell_id)) { + return true; + } + + return false; + case BotSpellTypes::Teleport: + if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))) { + return true; + } + + return false; + case BotSpellTypes::Succor: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Succor)) { + return true; + } + + return false; + case BotSpellTypes::BindAffinity: + if (IsEffectInSpell(spell_id, SE_BindAffinity)) { + return true; + } + + return false; + case BotSpellTypes::Identify: + if (IsEffectInSpell(spell_id, SE_Identify)) { + return true; + } + + return false; + case BotSpellTypes::Levitate: + if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Levitate))) { + return true; + } + + return false; + case BotSpellTypes::Rune: + if (IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune)) { + return true; + } + + return false; + case BotSpellTypes::WaterBreathing: + if (IsEffectInSpell(spell_id, SE_WaterBreathing)) { + return true; + } + + return false; + case BotSpellTypes::Size: + if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))) { + return true; + } + + return false; + case BotSpellTypes::Invisibility: + if (IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id)) { + return true; + } + + return false; + case BotSpellTypes::MovementSpeed: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) { + return true; + } + + return false; + case BotSpellTypes::SendHome: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_GateToHomeCity)) { + return true; + } + + return false; + case BotSpellTypes::SummonCorpse: + if (IsEffectInSpell(spell_id, SE_SummonCorpse)) { + return true; + } + return false; default: return true; @@ -11502,3 +11604,82 @@ bool Bot::BotPassiveCheck() { return false; } + +bool Bot::IsValidSpellTypeSubType(uint16 spellType, uint16 subType, uint16 spell_id) { + if (subType == UINT16_MAX) { + return true; + } + + switch (subType) { + case CommandedSubTypes::SingleTarget: + if (!IsAnyAESpell(spell_id) && !IsGroupSpell(spell_id)) { + return true; + } + + break; + case CommandedSubTypes::GroupTarget: + if (IsGroupSpell(spell_id)) { + return true; + } + + break; + case CommandedSubTypes::AETarget: + if (IsAnyAESpell(spell_id)) { + return true; + } + + break; + case CommandedSubTypes::SeeInvis: + if (IsEffectInSpell(spell_id, SE_SeeInvis)) { + return true; + } + + break; + case CommandedSubTypes::Invis: + if (IsEffectInSpell(spell_id, SE_Invisibility) || IsEffectInSpell(spell_id, SE_Invisibility2)) { + return true; + } + + break; + case CommandedSubTypes::InvisUndead: + if (IsEffectInSpell(spell_id, SE_InvisVsUndead) || IsEffectInSpell(spell_id, SE_InvisVsUndead2)) { + return true; + } + + break; + case CommandedSubTypes::InvisAnimals: + if (IsEffectInSpell(spell_id, SE_InvisVsAnimals) || IsEffectInSpell(spell_id, SE_ImprovedInvisAnimals)) { + return true; + } + + break; + case CommandedSubTypes::Shrink: + if ( + (IsEffectInSpell(spell_id, SE_ModelSize) && CalcSpellEffectValue(spell_id, GetSpellEffectIndex(spell_id, SE_ModelSize), GetLevel()) < 100) || + (IsEffectInSpell(spell_id, SE_ChangeHeight) && CalcSpellEffectValue(spell_id, GetSpellEffectIndex(spell_id, SE_ChangeHeight), GetLevel()) < 100) + ) { + return true; + } + + break; + case CommandedSubTypes::Grow: + if ( + (IsEffectInSpell(spell_id, SE_ModelSize) && CalcSpellEffectValue(spell_id, GetSpellEffectIndex(spell_id, SE_ModelSize), GetLevel()) > 100) || + (IsEffectInSpell(spell_id, SE_ChangeHeight) && CalcSpellEffectValue(spell_id, GetSpellEffectIndex(spell_id, SE_ChangeHeight), GetLevel()) > 100) + ) { + return true; + } + + break; + case CommandedSubTypes::Selo: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed) && IsBardSong(spell_id)) { + return true; + } + + break; + default: + break; + } + + return false; +} diff --git a/zone/bot.h b/zone/bot.h index 8e85448e0..2360d73b4 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -147,6 +147,19 @@ namespace BotBaseSettings { constexpr uint16 END = BotBaseSettings::ManaWhenToMed; // Increment as needed }; +namespace CommandedSubTypes { + constexpr uint16 SingleTarget = 1; + constexpr uint16 GroupTarget = 2; + constexpr uint16 AETarget = 3; + constexpr uint16 SeeInvis = 4; + constexpr uint16 Invis = 5; + constexpr uint16 InvisUndead = 6; + constexpr uint16 InvisAnimals = 7; + constexpr uint16 Shrink = 8; + constexpr uint16 Grow = 9; + constexpr uint16 Selo = 10; +}; + class Bot : public NPC { friend class Mob; public: @@ -387,7 +400,7 @@ public: void AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot); // AI Methods - bool AICastSpell(Mob* tar, uint8 iChance, uint16 spellType); + bool AICastSpell(Mob* tar, uint8 iChance, uint16 spellType, uint16 subTargetType = UINT16_MAX, uint16 subType = UINT16_MAX); bool AttemptAICastSpell(uint16 spellType); bool AI_EngagedCastCheck() override; bool AI_PursueCastCheck() override; @@ -523,6 +536,7 @@ public: bool IsValidSpellTypeBySpellID(uint16 spellType, uint16 spell_id); inline uint16 GetCastedSpellType() const { return _castedSpellType; } void SetCastedSpellType(uint16 spellType); + bool IsValidSpellTypeSubType(uint16 spellType, uint16 subType, uint16 spell_id); bool HasValidAETarget(Bot* botCaster, uint16 spell_id, uint16 spellType, Mob* tar); @@ -577,7 +591,7 @@ public: static std::list GetBotSpellsForSpellEffect(Bot* botCaster, uint16 spellType, int spellEffect); static std::list GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, uint16 spellType, int spellEffect, SpellTargetType targetType); static std::list GetBotSpellsBySpellType(Bot* botCaster, uint16 spellType); - static std::list GetPrioritizedBotSpellsBySpellType(Bot* botCaster, uint16 spellType, Mob* tar, bool AE = false); + static std::list GetPrioritizedBotSpellsBySpellType(Bot* botCaster, uint16 spellType, Mob* tar, bool AE = false, uint16 subTargetType = UINT16_MAX, uint16 subType = UINT16_MAX); static BotSpell GetFirstBotSpellBySpellType(Bot* botCaster, uint16 spellType); static BotSpell GetBestBotSpellForVeryFastHeal(Bot* botCaster, Mob* tar, uint16 spellType = BotSpellTypes::RegularHeal); diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 15ea7eba8..7af4109f5 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1251,7 +1251,6 @@ int bot_command_init(void) bot_command_add("applypoison", "Applies cursor-held poison to a rogue bot's weapon", AccountStatus::Player, bot_command_apply_poison) || bot_command_add("attack", "Orders bots to attack a designated target", AccountStatus::Player, bot_command_attack) || bot_command_add("behindmob", "Toggles whether or not your bot tries to stay behind a mob", AccountStatus::Player, bot_command_behind_mob) || - bot_command_add("bindaffinity", "Orders a bot to attempt an affinity binding", AccountStatus::Player, bot_command_bind_affinity) || bot_command_add("bot", "Lists the available bot management [subcommands]", AccountStatus::Player, bot_command_bot) || bot_command_add("botappearance", "Lists the available bot appearance [subcommands]", AccountStatus::Player, bot_command_appearance) || bot_command_add("botbeardcolor", "Changes the beard color of a bot", AccountStatus::Player, bot_command_beard_color) || @@ -1286,16 +1285,13 @@ int bot_command_init(void) bot_command_add("botwoad", "Changes the Barbarian woad of a bot", AccountStatus::Player, bot_command_woad) || bot_command_add("cast", "Tells the first found specified bot to cast the given spell type", AccountStatus::Player, bot_command_cast) || bot_command_add("distanceranged", "Controls the range casters and ranged will try to stay away from a mob", AccountStatus::Player, bot_command_distance_ranged) || - bot_command_add("charm", "Attempts to have a bot charm your target", AccountStatus::Player, bot_command_charm) || bot_command_add("classracelist", "Lists the classes and races and their appropriate IDs", AccountStatus::Player, bot_command_class_race_list) || bot_command_add("clickitem", "Orders your targeted bot to click the item in the provided inventory slot.", AccountStatus::Player, bot_command_click_item) || bot_command_add("copysettings", "Copies settings from one bot to another", AccountStatus::Player, bot_command_copy_settings) || - bot_command_add("cure", "Orders a bot to remove any ailments", AccountStatus::Player, bot_command_cure) || bot_command_add("defaultsettings", "Restores a bot back to default settings", AccountStatus::Player, bot_command_default_settings) || bot_command_add("defensive", "Orders a bot to use a defensive discipline", AccountStatus::Player, bot_command_defensive) || - bot_command_add("depart", "Orders a bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_depart) || + bot_command_add("depart", "Orders a bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_depart) || //TODO bot rewrite - deleteme bot_command_add("enforcespellsettings", "Toggles your Bot to cast only spells in their spell settings list.", AccountStatus::Player, bot_command_enforce_spell_list) || - bot_command_add("escape", "Orders a bot to send a target group to a safe location within the zone", AccountStatus::Player, bot_command_escape) || bot_command_add("findaliases", "Find available aliases for a bot command", AccountStatus::Player, bot_command_find_aliases) || bot_command_add("follow", "Orders bots to follow a designated target (option 'chain' auto-links eligible spawned bots)", AccountStatus::Player, bot_command_follow) || bot_command_add("guard", "Orders bots to guard their current positions", AccountStatus::Player, bot_command_guard) || @@ -1322,20 +1318,15 @@ int bot_command_init(void) bot_command_add("healrotationstop", "Stops a heal rotation", AccountStatus::Player, bot_command_heal_rotation_stop) || bot_command_add("help", "List available commands and their description - specify partial command as argument to search", AccountStatus::Player, bot_command_help) || bot_command_add("hold", "Prevents a bot from attacking until released", AccountStatus::Player, bot_command_hold) || - bot_command_add("identify", "Orders a bot to cast an item identification spell", AccountStatus::Player, bot_command_identify) || bot_command_add("illusionblock", "Control whether or not illusion effects will land on the bot if casted by another player or bot", AccountStatus::Player, bot_command_illusion_block) || bot_command_add("inventory", "Lists the available bot inventory [subcommands]", AccountStatus::Player, bot_command_inventory) || bot_command_add("inventorygive", "Gives the item on your cursor to a bot", AccountStatus::Player, bot_command_inventory_give) || bot_command_add("inventorylist", "Lists all items in a bot's inventory", AccountStatus::Player, bot_command_inventory_list) || bot_command_add("inventoryremove", "Removes an item from a bot's inventory", AccountStatus::Player, bot_command_inventory_remove) || bot_command_add("inventorywindow", "Displays all items in a bot's inventory in a pop-up window", AccountStatus::Player, bot_command_inventory_window) || - bot_command_add("invisibility", "Orders a bot to cast a cloak of invisibility, or allow them to be seen", AccountStatus::Player, bot_command_invisibility) || bot_command_add("itemuse", "Elicits a report from spawned bots that can use the item on your cursor (option 'empty' yields only empty slots)", AccountStatus::Player, bot_command_item_use) || - bot_command_add("levitation", "Orders a bot to cast a levitation spell", AccountStatus::Player, bot_command_levitation) || - bot_command_add("lull", "Orders a bot to cast a pacification spell", AccountStatus::Player, bot_command_lull) || + bot_command_add("lull", "Orders a bot to cast a pacification spell", AccountStatus::Player, bot_command_lull) || //TODO bot rewrite - IMPLEMENT bot_command_add("maxmeleerange", "Toggles whether your bot is at max melee range or not. This will disable all special abilities, including taunt.", AccountStatus::Player, bot_command_max_melee_range) || - bot_command_add("mesmerize", "Orders a bot to cast a mesmerization spell", AccountStatus::Player, bot_command_mesmerize) || - bot_command_add("movementspeed", "Orders a bot to cast a movement speed enhancement spell", AccountStatus::Player, bot_command_movement_speed) || bot_command_add("owneroption", "Sets options available to bot owners", AccountStatus::Player, bot_command_owner_option) || bot_command_add("pet", "Lists the available bot pet [subcommands]", AccountStatus::Player, bot_command_pet) || bot_command_add("petgetlost", "Orders a bot to remove its summoned pet", AccountStatus::Player, bot_command_pet_get_lost) || @@ -1346,14 +1337,9 @@ int bot_command_init(void) bot_command_add("precombat", "Sets flag used to determine pre-combat behavior", AccountStatus::Player, bot_command_precombat) || bot_command_add("pull", "Orders a designated bot to 'pull' an enemy", AccountStatus::Player, bot_command_pull) || bot_command_add("release", "Releases a suspended bot's AI processing (with hate list wipe)", AccountStatus::Player, bot_command_release) || - bot_command_add("resistance", "Orders a bot to cast a specified resistance buff", AccountStatus::Player, bot_command_resistance) || - bot_command_add("resurrect", "Orders a bot to resurrect a player's (players') corpse(s)", AccountStatus::Player, bot_command_resurrect) || - bot_command_add("rune", "Orders a bot to cast a rune of protection", AccountStatus::Player, bot_command_rune) || - bot_command_add("sendhome", "Orders a bot to open a magical doorway home", AccountStatus::Player, bot_command_send_home) || bot_command_add("sithppercent", "HP threshold for a bot to start sitting in combat if allowed", AccountStatus::Player, bot_command_sit_hp_percent) || bot_command_add("sitincombat", "Toggles whether or a not a bot will attempt to med or sit to heal in combat", AccountStatus::Player, bot_command_sit_in_combat) || bot_command_add("sitmanapercent", "Mana threshold for a bot to start sitting in combat if allowed", AccountStatus::Player, bot_command_sit_mana_percent) || - bot_command_add("size", "Orders a bot to change a player's size", AccountStatus::Player, bot_command_size) || bot_command_add("spellaggrochecks", "Toggles whether or not bots will cast a spell type if they think it will get them aggro", AccountStatus::Player, bot_command_spell_aggro_checks) || bot_command_add("spellengagedpriority", "Controls the order of casts by spell type when engaged in combat", AccountStatus::Player, bot_command_spell_engaged_priority) || bot_command_add("spelldelays", "Controls the delay between casts for a specific spell type", AccountStatus::Player, bot_command_spell_delays) || @@ -1374,15 +1360,14 @@ int bot_command_init(void) bot_command_add("spellsettingsdelete", "Delete a bot spell setting entry", AccountStatus::Player, bot_command_spell_settings_delete) || bot_command_add("spellsettingstoggle", "Toggle a bot spell use", AccountStatus::Player, bot_command_spell_settings_toggle) || bot_command_add("spellsettingsupdate", "Update a bot spell setting entry", AccountStatus::Player, bot_command_spell_settings_update) || - bot_command_add("summoncorpse", "Orders a bot to summon a corpse to its feet", AccountStatus::Player, bot_command_summon_corpse) || bot_command_add("spelltypeids", "Lists spelltypes by ID", AccountStatus::Player, bot_command_spelltype_ids) || bot_command_add("spelltypenames", "Lists spelltypes by shortname", AccountStatus::Player, bot_command_spelltype_names) || + bot_command_add("summoncorpse", "Orders a bot to summon a corpse to its feet", AccountStatus::Player, bot_command_summon_corpse) || //TODO bot rewrite - IMPLEMENT bot_command_add("suspend", "Suspends a bot's AI processing until released", AccountStatus::Player, bot_command_suspend) || bot_command_add("taunt", "Toggles taunt use by a bot", AccountStatus::Player, bot_command_taunt) || bot_command_add("timer", "Checks or clears timers of the chosen type.", AccountStatus::GMMgmt, bot_command_timer) || bot_command_add("track", "Orders a capable bot to track enemies", AccountStatus::Player, bot_command_track) || - bot_command_add("viewcombos", "Views bot race class combinations", AccountStatus::Player, bot_command_view_combos) || - bot_command_add("waterbreathing", "Orders a bot to cast a water breathing spell", AccountStatus::Player, bot_command_water_breathing) + bot_command_add("viewcombos", "Views bot race class combinations", AccountStatus::Player, bot_command_view_combos) ) { bot_command_deinit(); return -1; @@ -2266,36 +2251,27 @@ void SendSpellTypeWindow(Client *c, const Seperator* sep) { #include "bot_commands/apply_potion.cpp" #include "bot_commands/attack.cpp" #include "bot_commands/behind_mob.cpp" -#include "bot_commands/bind_affinity.cpp" #include "bot_commands/bot.cpp" #include "bot_commands/bot_settings.cpp" #include "bot_commands/cast.cpp" -#include "bot_commands/charm.cpp" #include "bot_commands/class_race_list.cpp" #include "bot_commands/click_item.cpp" #include "bot_commands/copy_settings.cpp" -#include "bot_commands/cure.cpp" #include "bot_commands/default_settings.cpp" #include "bot_commands/defensive.cpp" #include "bot_commands/depart.cpp" #include "bot_commands/distance_ranged.cpp" -#include "bot_commands/escape.cpp" #include "bot_commands/find_aliases.cpp" #include "bot_commands/follow.cpp" #include "bot_commands/guard.cpp" #include "bot_commands/heal_rotation.cpp" #include "bot_commands/help.cpp" #include "bot_commands/hold.cpp" -#include "bot_commands/identify.cpp" #include "bot_commands/illusion_block.cpp" #include "bot_commands/inventory.cpp" -#include "bot_commands/invisibility.cpp" #include "bot_commands/item_use.cpp" -#include "bot_commands/levitation.cpp" #include "bot_commands/lull.cpp" #include "bot_commands/max_melee_range.cpp" -#include "bot_commands/mesmerize.cpp" -#include "bot_commands/movement_speed.cpp" #include "bot_commands/name.cpp" #include "bot_commands/owner_option.cpp" #include "bot_commands/pet.cpp" @@ -2304,14 +2280,9 @@ void SendSpellTypeWindow(Client *c, const Seperator* sep) { #include "bot_commands/precombat.cpp" #include "bot_commands/pull.cpp" #include "bot_commands/release.cpp" -#include "bot_commands/resistance.cpp" -#include "bot_commands/resurrect.cpp" -#include "bot_commands/rune.cpp" -#include "bot_commands/send_home.cpp" #include "bot_commands/sit_hp_percent.cpp" #include "bot_commands/sit_in_combat.cpp" #include "bot_commands/sit_mana_percent.cpp" -#include "bot_commands/size.cpp" #include "bot_commands/spell.cpp" #include "bot_commands/spell_aggro_checks.cpp" #include "bot_commands/spell_delays.cpp" @@ -2334,4 +2305,3 @@ void SendSpellTypeWindow(Client *c, const Seperator* sep) { #include "bot_commands/timer.cpp" #include "bot_commands/track.cpp" #include "bot_commands/view_combos.cpp" -#include "bot_commands/water_breathing.cpp" diff --git a/zone/bot_command.h b/zone/bot_command.h index 8e200bd6b..56be99680 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -1669,36 +1669,27 @@ 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_behind_mob(Client* c, const Seperator* sep); -void bot_command_bind_affinity(Client *c, const Seperator *sep); void bot_command_bot(Client *c, const Seperator *sep); void bot_command_bot_settings(Client* c, const Seperator* sep); void bot_command_cast(Client* c, const Seperator* sep); void bot_command_distance_ranged(Client* c, const Seperator* sep); -void bot_command_charm(Client *c, const Seperator *sep); void bot_command_class_race_list(Client* c, const Seperator* sep); void bot_command_click_item(Client* c, const Seperator* sep); void bot_command_copy_settings(Client* c, const Seperator* sep); -void bot_command_cure(Client *c, const Seperator *sep); void bot_command_default_settings(Client* c, const Seperator* sep); void bot_command_defensive(Client *c, const Seperator *sep); void bot_command_depart(Client *c, const Seperator *sep); -void bot_command_escape(Client *c, const Seperator *sep); void bot_command_find_aliases(Client *c, const Seperator *sep); void bot_command_follow(Client *c, const Seperator *sep); void bot_command_guard(Client *c, const Seperator *sep); void bot_command_heal_rotation(Client *c, const Seperator *sep); void bot_command_help(Client *c, const Seperator *sep); void bot_command_hold(Client *c, const Seperator *sep); -void bot_command_identify(Client *c, const Seperator *sep); void bot_command_illusion_block(Client* c, const Seperator* sep); void bot_command_inventory(Client *c, const Seperator *sep); -void bot_command_invisibility(Client *c, const Seperator *sep); void bot_command_item_use(Client *c, const Seperator *sep); -void bot_command_levitation(Client *c, const Seperator *sep); void bot_command_lull(Client *c, const Seperator *sep); void bot_command_max_melee_range(Client* c, const Seperator* sep); -void bot_command_mesmerize(Client *c, const Seperator *sep); -void bot_command_movement_speed(Client *c, const Seperator *sep); void bot_command_owner_option(Client *c, const Seperator *sep); void bot_command_pet(Client *c, const Seperator *sep); void bot_command_pick_lock(Client *c, const Seperator *sep); @@ -1706,14 +1697,9 @@ void bot_command_pickpocket(Client* c, const Seperator* sep); void bot_command_precombat(Client* c, const Seperator* sep); void bot_command_pull(Client *c, const Seperator *sep); void bot_command_release(Client *c, const Seperator *sep); -void bot_command_resistance(Client *c, const Seperator *sep); -void bot_command_resurrect(Client *c, const Seperator *sep); -void bot_command_rune(Client *c, const Seperator *sep); -void bot_command_send_home(Client *c, const Seperator *sep); void bot_command_sit_hp_percent(Client* c, const Seperator* sep); void bot_command_sit_in_combat(Client* c, const Seperator* sep); void bot_command_sit_mana_percent(Client* c, const Seperator* sep); -void bot_command_size(Client *c, const Seperator *sep); void bot_command_spell_aggro_checks(Client* c, const Seperator* sep); void bot_command_spell_delays(Client* c, const Seperator* sep); void bot_command_spell_engaged_priority(Client* c, const Seperator* sep); @@ -1743,7 +1729,6 @@ void bot_command_taunt(Client *c, const Seperator *sep); void bot_command_timer(Client* c, const Seperator* sep); void bot_command_track(Client *c, const Seperator *sep); void bot_command_view_combos(Client *c, const Seperator *sep); -void bot_command_water_breathing(Client *c, const Seperator *sep); // Bot Subcommands void bot_command_appearance(Client *c, const Seperator *sep); diff --git a/zone/bot_commands/bind_affinity.cpp b/zone/bot_commands/bind_affinity.cpp deleted file mode 100644 index a59c5947a..000000000 --- a/zone/bot_commands/bind_affinity.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "../bot_command.h" - -void bot_command_bind_affinity(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_BindAffinity]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_BindAffinity) || helper_command_alias_fail(c, "bot_command_bind_affinity", sep->arg[0], "bindaffinity")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s", sep->arg[0]); - c->Message(Chat::White, "note: Orders a bot to attempt an affinity binding", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_BindAffinity); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - // Cast effect message is not being generated - if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id)) - c->Message(Chat::White, "Successfully bound %s to this location", target_mob->GetCleanName()); - else - c->Message(Chat::White, "Failed to bind %s to this location", target_mob->GetCleanName()); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index ab02eb950..49112b869 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -99,8 +99,8 @@ void bot_command_cast(Client* c, const Seperator* sep) c->Message( Chat::Yellow, fmt::format( - "Use {} for information about race/class IDs.", - Saylink::Silent("^classracelist") + "Use help after any command type for more subtypes to use, for example: {}.", + Saylink::Silent("^cast invisibility help") ).c_str() ); @@ -118,9 +118,58 @@ void bot_command_cast(Client* c, const Seperator* sep) } std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; - if (!arg1.compare("listid") || !arg1.compare("listname")) { - c->CastToBot()->SendSpellTypesWindow(c, sep->arg[0], sep->arg[1], sep->arg[2]); + //Commanded type help prompts + if (!arg2.compare("help")) { + c->Message(Chat::Yellow, "You can also use [single], [group], [ae]. Ex: ^cast movementspeed group.", sep->arg[0]); + } + + if (!arg1.compare("invisibility") && !arg2.compare("help")) { + c->Message( + Chat::Yellow, + fmt::format( + "Available options for {} are: {}, {}, {}, {}.", + sep->arg[0], + Saylink::Silent("^cast invisibility see", "see"), + Saylink::Silent("^cast invisibility invis", "invis"), + Saylink::Silent("^cast invisibility undead", "undead"), + Saylink::Silent("^cast invisibility animals", "animals") + ).c_str() + ); + + return; + } + + if (!arg1.compare("size") && !arg2.compare("help")) { + c->Message( + Chat::Yellow, + fmt::format( + "Available options for {} are: {}, {}.", + sep->arg[0], + Saylink::Silent("^cast size grow", "grow"), + Saylink::Silent("^cast size shrink", "shrink") + ).c_str() + ); + + return; + } + + if (!arg1.compare("movementspeed") && !arg2.compare("help")) { + c->Message( + Chat::Yellow, + fmt::format( + "Available options for {} are: {}, {}.", + sep->arg[0], + Saylink::Silent("^cast movementspeed selo"), "selo" + ).c_str() + ); + + return; + } + + if (!arg2.compare("help")) { + c->Message(Chat::Yellow, "There are no additional options for {}.", sep->arg[0]); return; } @@ -131,8 +180,16 @@ void bot_command_cast(Client* c, const Seperator* sep) if (sep->IsNumber(1)) { spellType = atoi(sep->arg[1]); - if (spellType < BotSpellTypes::START || spellType > BotSpellTypes::END) { - c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + if (spellType < BotSpellTypes::START || (spellType > BotSpellTypes::END && spellType < BotSpellTypes::COMMANDED_START) || spellType > BotSpellTypes::COMMANDED_END) { + c->Message( + Chat::Yellow, + fmt::format( + "You must choose a valid spell type. Use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); return; } @@ -156,6 +213,88 @@ void bot_command_cast(Client* c, const Seperator* sep) } } + switch (spellType) { //Allowed command checks + case BotSpellTypes::Charm: + if (!RuleB(Bots, AllowCommandedCharm)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + break; + case BotSpellTypes::AEMez: + case BotSpellTypes::Mez: + if (!RuleB(Bots, AllowCommandedMez)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + break; + case BotSpellTypes::Resurrect: + if (!RuleB(Bots, AllowCommandedResurrect)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + break; + case BotSpellTypes::SummonCorpse: + if (!RuleB(Bots, AllowCommandedSummonCorpse)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + break; + default: + break; + } + + std::string argString = sep->arg[ab_arg]; + uint16 subType = UINT16_MAX; + uint16 subTargetType = UINT16_MAX; + + if (!argString.compare("shrink")) { + subType = CommandedSubTypes::Shrink; + ++ab_arg; + } + else if (!argString.compare("grow")) { + subType = CommandedSubTypes::Grow; + ++ab_arg; + } + else if (!argString.compare("see")) { + subType = CommandedSubTypes::SeeInvis; + ++ab_arg; + } + else if (!argString.compare("invis")) { + subType = CommandedSubTypes::Invis; + ++ab_arg; + } + else if (!argString.compare("undead")) { + subType = CommandedSubTypes::InvisUndead; + ++ab_arg; + } + else if (!argString.compare("animals")) { + subType = CommandedSubTypes::InvisAnimals; + ++ab_arg; + } + else if (!argString.compare("selo")) { + subType = CommandedSubTypes::Selo; + ++ab_arg; + } + + argString = sep->arg[ab_arg]; + + if (!argString.compare("single")) { + subTargetType = CommandedSubTypes::SingleTarget; + ++ab_arg; + } + else if (!argString.compare("group")) { + subTargetType = CommandedSubTypes::GroupTarget; + ++ab_arg; + } + else if (!argString.compare("ae")) { + subTargetType = CommandedSubTypes::AETarget; + ++ab_arg; + } + if ( spellType == BotSpellTypes::PetBuffs || spellType == BotSpellTypes::PetCompleteHeals || @@ -169,47 +308,63 @@ void bot_command_cast(Client* c, const Seperator* sep) } Mob* tar = c->GetTarget(); - LogTestDebug("{}: Attempting {} on {}", __LINE__, c->GetSpellTypeNameByID(spellType), (tar ? tar->GetCleanName() : "NOBODY")); //deleteme - if (spellType != BotSpellTypes::Escape && spellType != BotSpellTypes::Pet) { - if (!tar) { + LogTestDebug("{}: 'Attempting {} [{}] on {}'", __LINE__, c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (tar ? tar->GetCleanName() : "NOBODY")); //deleteme + + if (!tar) { + if (spellType != BotSpellTypes::Escape && spellType != BotSpellTypes::Pet) { c->Message(Chat::Yellow, "You need a target for that."); return; } - - if (BOT_SPELL_TYPES_DETRIMENTAL(spellType) && !c->IsAttackAllowed(tar)) { - c->Message(Chat::Yellow, "You cannot attack [%s].", tar->GetCleanName()); - return; - } - - if (BOT_SPELL_TYPES_BENEFICIAL(spellType)) { - if (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) { - c->Message(Chat::Yellow, "[%s] is an invalid target.", tar->GetCleanName()); - return; - } - } } - LogTestDebug("{}: Attempting {} on {}", __LINE__, c->GetSpellTypeNameByID(spellType), (tar ? tar->GetCleanName() : "NOBODY")); //deleteme - switch (spellType) { - case BotSpellTypes::Stun: - case BotSpellTypes::AEStun: - if (tar->GetSpecialAbility(SpecialAbility::StunImmunity)) { - c->Message(Chat::Yellow, "[%s] is immune to stuns.", tar->GetCleanName()); - return; - } - - break; + + switch (spellType) { //Target Checks case BotSpellTypes::Resurrect: if (!tar->IsCorpse() || !tar->CastToCorpse()->IsPlayerCorpse()) { - c->Message(Chat::Yellow, "[%s] is an invalid target. I can only resurrect player corpses.", tar->GetCleanName()); + c->Message(Chat::Yellow, "[%s] is not a player's corpse.", tar->GetCleanName()); + + return; + } + + break; + case BotSpellTypes::Identify: + case BotSpellTypes::SendHome: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::SummonCorpse: + if (!tar->IsClient() || !c->IsInGroupOrRaid(tar)) { + c->Message(Chat::Yellow, "[%s] is an invalid target. Only players in your group or raid are eligible targets.", tar->GetCleanName()); + return; } break; default: + if (BOT_SPELL_TYPES_DETRIMENTAL(spellType) && !c->IsAttackAllowed(tar)) { + c->Message(Chat::Yellow, "You cannot attack [%s].", tar->GetCleanName()); + + return; + } + + if (BOT_SPELL_TYPES_BENEFICIAL(spellType)) { + if ( + (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) || + ((tar->IsOfClientBot() && !c->IsInGroupOrRaid(tar)) || (tar->GetOwner() && tar->GetOwner()->IsOfClientBot() && !c->IsInGroupOrRaid(tar->GetOwner()))) + ) { + c->Message(Chat::Yellow, "[%s] is an invalid target. Only players in your group or raid are eligible targets.", tar->GetCleanName()); + + return; + } + } + break; } const int ab_mask = ActionableBots::ABM_Type1; + std::string actionableArg = sep->arg[ab_arg]; + + if (actionableArg.empty()) { + actionableArg = "spawned"; + } + std::string class_race_arg = sep->arg[ab_arg]; bool class_race_check = false; @@ -219,7 +374,7 @@ void bot_command_cast(Client* c, const Seperator* sep) std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + if (ActionableBots::PopulateSBL(c, actionableArg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } @@ -240,7 +395,8 @@ void bot_command_cast(Client* c, const Seperator* sep) /* TODO bot rewrite - - FIX: Snares, Group Cures, OOC Song, Precombat, HateRedux, Fear/AE Fear + FIX: Depart, SummonCorpse, Lull, + Group Cures, Precombat, Fear/AE Fear ICB (SK) casting hate on friendly but not hostile? NEED TO CHECK: precombat, AE Dispel, AE Lifetap DO I NEED A PBAE CHECK??? @@ -250,7 +406,7 @@ void bot_command_cast(Client* c, const Seperator* sep) } Mob* newTar = tar; - LogTestDebug("{}: Attempting {} on {}", __LINE__, c->GetSpellTypeNameByID(spellType), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme + LogTestDebug("{}: {} says, 'Attempting {} [{}] on {}'", __LINE__, bot_iter->GetCleanName(), c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme if (!SpellTypeRequiresTarget(spellType, bot_iter->GetClass())) { newTar = bot_iter; } @@ -279,11 +435,11 @@ void bot_command_cast(Client* c, const Seperator* sep) continue; } - LogTestDebug("{}: Attempting {} on {}", __LINE__, c->GetSpellTypeNameByID(spellType), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme + LogTestDebug("{}: {} says, 'Attempting {} [{}] on {}'", __LINE__, bot_iter->GetCleanName(), c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme bot_iter->SetCommandedSpell(true); - if (bot_iter->AICastSpell(newTar, 100, spellType)) { + if (bot_iter->AICastSpell(newTar, 100, spellType, subTargetType, subType)) { if (!firstFound) { firstFound = bot_iter; } diff --git a/zone/bot_commands/charm.cpp b/zone/bot_commands/charm.cpp deleted file mode 100644 index 2df4694b6..000000000 --- a/zone/bot_commands/charm.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "../bot_command.h" - -void bot_command_charm(Client *c, const Seperator *sep) -{ - auto local_list = &bot_command_spells[BCEnum::SpT_Charm]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Charm) || helper_command_alias_fail(c, "bot_command_charm", sep->arg[0], "charm")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: dire])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Charm); - return; - } - - bool dire = false; - std::string dire_arg = sep->arg[1]; - if (!dire_arg.compare("dire")) - dire = true; - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToCharm(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->dire != dire) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, ENEMY); - if (!target_mob) - continue; - if (target_mob->IsCharmed()) { - c->Message(Chat::White, "Your is already charmed"); - return; - } - - if (spells[local_entry->spell_id].max_value[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob, true); - if (!my_bot) - continue; - - uint32 dont_root_before = 0; - if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before)) - target_mob->SetDontRootMeBefore(dont_root_before); - - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/cure.cpp b/zone/bot_commands/cure.cpp deleted file mode 100644 index 4452f8088..000000000 --- a/zone/bot_commands/cure.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "../bot_command.h" - -void bot_command_cure(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Cure]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Cure) || helper_command_alias_fail(c, "bot_command_cure", sep->arg[0], "cure")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s [ailment: blindness | disease | poison | curse | corruption]", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Cure); - return; - } - - std::string ailment_arg = sep->arg[1]; - - auto ailment_type = BCEnum::AT_None; - if (!ailment_arg.compare("blindness")) - ailment_type = BCEnum::AT_Blindness; - else if (!ailment_arg.compare("disease")) - ailment_type = BCEnum::AT_Disease; - else if (!ailment_arg.compare("poison")) - ailment_type = BCEnum::AT_Poison; - else if (!ailment_arg.compare("curse")) - ailment_type = BCEnum::AT_Curse; - else if (!ailment_arg.compare("corruption")) - ailment_type = BCEnum::AT_Corruption; - - if (ailment_type == BCEnum::AT_None) { - c->Message(Chat::White, "You must specify a cure [ailment] to use this command"); - return; - } - - local_list->sort([ailment_type](STBaseEntry* l, STBaseEntry* r) { - auto _l = l->SafeCastToCure(), _r = r->SafeCastToCure(); - if (_l->cure_value[AILMENTIDTOINDEX(ailment_type)] < _r->cure_value[AILMENTIDTOINDEX(ailment_type)]) - return true; - if (_l->cure_value[AILMENTIDTOINDEX(ailment_type)] == _r->cure_value[AILMENTIDTOINDEX(ailment_type)] && spells[_l->spell_id].mana < spells[_r->spell_id].mana) - return true; - if (_l->cure_value[AILMENTIDTOINDEX(ailment_type)] == _r->cure_value[AILMENTIDTOINDEX(ailment_type)] && spells[_l->spell_id].mana == spells[_r->spell_id].mana && _l->cure_total < _r->cure_total) - return true; - - return false; - }); - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToCure(); - if (helper_spell_check_fail(local_entry)) - continue; - if (!local_entry->cure_value[AILMENTIDTOINDEX(ailment_type)]) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/escape.cpp b/zone/bot_commands/escape.cpp deleted file mode 100644 index 31fde82a6..000000000 --- a/zone/bot_commands/escape.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "../bot_command.h" - -void bot_command_escape(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Escape]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Escape) || helper_command_alias_fail(c, "bot_command_escape", sep->arg[0], "escape")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s ([option: lesser])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Escape); - return; - } - - bool use_lesser = false; - if (!strcasecmp(sep->arg[1], "lesser")) - use_lesser = true; - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToEscape(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->lesser != use_lesser) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/identify.cpp b/zone/bot_commands/identify.cpp deleted file mode 100644 index 7ac90a4e4..000000000 --- a/zone/bot_commands/identify.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "../bot_command.h" - -void bot_command_identify(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Identify]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Identify) || helper_command_alias_fail(c, "bot_command_identify", sep->arg[0], "identify")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Identify); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/invisibility.cpp b/zone/bot_commands/invisibility.cpp deleted file mode 100644 index dd826ebc8..000000000 --- a/zone/bot_commands/invisibility.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "../bot_command.h" - -void bot_command_invisibility(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Invisibility]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Invisibility) || helper_command_alias_fail(c, "bot_command_invisibility", sep->arg[0], "invisibility")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s [invisibility: living | undead | animal | see]", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Invisibility); - return; - } - - std::string invisibility = sep->arg[1]; - - BCEnum::IType invisibility_type = BCEnum::IT_None; - if (!invisibility.compare("living")) - invisibility_type = BCEnum::IT_Living; - else if (!invisibility.compare("undead")) - invisibility_type = BCEnum::IT_Undead; - else if (!invisibility.compare("animal")) - invisibility_type = BCEnum::IT_Animal; - else if (!invisibility.compare("see")) - invisibility_type = BCEnum::IT_See; - - if (invisibility_type == BCEnum::IT_None) { - c->Message(Chat::White, "You must specify an [invisibility]"); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToInvisibility(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->invis_type != invisibility_type) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/levitation.cpp b/zone/bot_commands/levitation.cpp deleted file mode 100644 index d4b5cee28..000000000 --- a/zone/bot_commands/levitation.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "../bot_command.h" - -void bot_command_levitation(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Levitation]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Levitation) || helper_command_alias_fail(c, "bot_command_levitation", sep->arg[0], "levitation")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Levitation); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/mesmerize.cpp b/zone/bot_commands/mesmerize.cpp deleted file mode 100644 index d86fba9f7..000000000 --- a/zone/bot_commands/mesmerize.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "../bot_command.h" - -void bot_command_mesmerize(Client *c, const Seperator *sep) -{ - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Mesmerize); - return; - } - - bool isSuccess = false; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - for (auto bot_iter : sbl) { - std::list botSpellList = bot_iter->GetPrioritizedBotSpellsBySpellType(bot_iter, BotSpellTypes::Mez, c->GetTarget(), IsAEBotSpellType(BotSpellTypes::Mez)); - - for (const auto& s : botSpellList) { - if (!IsValidSpell(s.SpellId)) { - continue; - } - - if (!bot_iter->IsInGroupOrRaid(c)) { - continue; - } - - if (!bot_iter->CastChecks(s.SpellId, c->GetTarget(), BotSpellTypes::Mez, false, false)) { - continue; - } - - if (bot_iter->CommandedDoSpellCast(s.SpellIndex, c->GetTarget(), s.ManaCost)) { - bot_iter->BotGroupSay(bot_iter, "Casting %s [%s] on %s.", GetSpellName(s.SpellId), bot_iter->GetSpellTypeNameByID(BotSpellTypes::Mez), c->GetTarget()->GetCleanName()); - isSuccess = true; - } - } - } - - if (!isSuccess) { - helper_no_available_bots(c); - } -} diff --git a/zone/bot_commands/movement_speed.cpp b/zone/bot_commands/movement_speed.cpp deleted file mode 100644 index d1b884154..000000000 --- a/zone/bot_commands/movement_speed.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "../bot_command.h" - -void bot_command_movement_speed(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_MovementSpeed]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_MovementSpeed) || helper_command_alias_fail(c, "bot_command_movement_speed", sep->arg[0], "movementspeed")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s ([group | sow])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_MovementSpeed); - return; - } - - bool group = false; - bool sow = false; - std::string arg1 = sep->arg[1]; - if (!arg1.compare("group")) - group = true; - else if (!arg1.compare("sow")) - sow = true; - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToMovementSpeed(); - if (helper_spell_check_fail(local_entry)) - continue; - if (!sow && (local_entry->group != group)) - continue; - if (sow && (local_entry->spell_id != 278)) // '278' = single-target "Spirit of Wolf" - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/resistance.cpp b/zone/bot_commands/resistance.cpp deleted file mode 100644 index 0b84b4b88..000000000 --- a/zone/bot_commands/resistance.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "../bot_command.h" - -void bot_command_resistance(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Resistance]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Resistance) || helper_command_alias_fail(c, "bot_command_resistance", sep->arg[0], "resistance")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s [resistance: fire | cold | poison | disease | magic | corruption]", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Resistance); - return; - } - - std::string resistance_arg = sep->arg[1]; - - auto resistance_type = BCEnum::RT_None; - if (!resistance_arg.compare("fire")) - resistance_type = BCEnum::RT_Fire; - else if (!resistance_arg.compare("cold")) - resistance_type = BCEnum::RT_Cold; - else if (!resistance_arg.compare("poison")) - resistance_type = BCEnum::RT_Poison; - else if (!resistance_arg.compare("disease")) - resistance_type = BCEnum::RT_Disease; - else if (!resistance_arg.compare("magic")) - resistance_type = BCEnum::RT_Magic; - else if (!resistance_arg.compare("corruption")) - resistance_type = BCEnum::RT_Corruption; - - if (resistance_type == BCEnum::RT_None) { - c->Message(Chat::White, "You must specify a [resistance]"); - return; - } - - local_list->sort([resistance_type](STBaseEntry* l, STBaseEntry* r) { - auto _l = l->SafeCastToResistance(), _r = r->SafeCastToResistance(); - if (_l->resist_value[RESISTANCEIDTOINDEX(resistance_type)] > _r->resist_value[RESISTANCEIDTOINDEX(resistance_type)]) - return true; - if (_l->resist_value[RESISTANCEIDTOINDEX(resistance_type)] == _r->resist_value[RESISTANCEIDTOINDEX(resistance_type)] && spells[_l->spell_id].mana < spells[_r->spell_id].mana) - return true; - if (_l->resist_value[RESISTANCEIDTOINDEX(resistance_type)] == _r->resist_value[RESISTANCEIDTOINDEX(resistance_type)] && spells[_l->spell_id].mana == spells[_r->spell_id].mana && _l->resist_total > _r->resist_total) - return true; - - return false; - }); - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToResistance(); - if (helper_spell_check_fail(local_entry)) - continue; - if (!local_entry->resist_value[RESISTANCEIDTOINDEX(resistance_type)]) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/resurrect.cpp b/zone/bot_commands/resurrect.cpp deleted file mode 100644 index 0b15ef3e1..000000000 --- a/zone/bot_commands/resurrect.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "../bot_command.h" - -void bot_command_resurrect(Client *c, const Seperator *sep) -{ - // Obscure bot spell code prohibits the aoe portion from working correctly... - - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Resurrect]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Resurrect) || helper_command_alias_fail(c, "bot_command_resurrect", sep->arg[0], "resurrect")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - //c->Message(Chat::White, "usage: %s ([option: aoe])", sep->arg[0]); - c->Message(Chat::White, "usage: %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Resurrect); - return; - } - - bool aoe = false; - //std::string aoe_arg = sep->arg[1]; - //if (!aoe_arg.compare("aoe")) - // aoe = true; - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToResurrect(); - if (helper_spell_check_fail(local_entry)) - continue; - //if (local_entry->aoe != aoe) - // continue; - if (local_entry->aoe) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - //if (!target_mob && !local_entry->aoe) - // continue; - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - //if (local_entry->aoe) - // target_mob = my_bot; - - uint32 dont_root_before = 0; - if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before)) - target_mob->SetDontRootMeBefore(dont_root_before); - - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/rune.cpp b/zone/bot_commands/rune.cpp deleted file mode 100644 index 71b1cf572..000000000 --- a/zone/bot_commands/rune.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "../bot_command.h" - -void bot_command_rune(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Rune]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Rune) || helper_command_alias_fail(c, "bot_command_rune", sep->arg[0], "rune")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Rune); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/send_home.cpp b/zone/bot_commands/send_home.cpp deleted file mode 100644 index 6950e2bee..000000000 --- a/zone/bot_commands/send_home.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "../bot_command.h" - -void bot_command_send_home(Client *c, const Seperator *sep) -{ - // Obscure bot spell code prohibits the aoe portion from working correctly... - - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_SendHome]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_SendHome) || helper_command_alias_fail(c, "bot_command_send_home", sep->arg[0], "sendhome")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s ([option: group])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_SendHome); - return; - } - - bool group = false; - std::string group_arg = sep->arg[1]; - if (!group_arg.compare("group")) - group = true; - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToSendHome(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->group != group) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/size.cpp b/zone/bot_commands/size.cpp deleted file mode 100644 index 69e2fd1a2..000000000 --- a/zone/bot_commands/size.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "../bot_command.h" - -void bot_command_size(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Size]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Size) || helper_command_alias_fail(c, "bot_command_size", sep->arg[0], "size")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s [grow | shrink]", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Size); - return; - } - - std::string size_arg = sep->arg[1]; - auto size_type = BCEnum::SzT_Reduce; - if (!size_arg.compare("grow")) { - size_type = BCEnum::SzT_Enlarge; - } - else if (size_arg.compare("shrink")) { - c->Message(Chat::White, "This command requires a [grow | shrink] argument"); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToSize(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->size_type != size_type) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/water_breathing.cpp b/zone/bot_commands/water_breathing.cpp deleted file mode 100644 index cb9b792c4..000000000 --- a/zone/bot_commands/water_breathing.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "../bot_command.h" - -void bot_command_water_breathing(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_WaterBreathing]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_WaterBreathing) || helper_command_alias_fail(c, "bot_command_water_breathing", sep->arg[0], "waterbreathing")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_WaterBreathing); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 0138d58ad..241854dfa 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -21,7 +21,7 @@ #include "../common/repositories/bot_spells_entries_repository.h" #include "../common/repositories/npc_spells_repository.h" -bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType) { +bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType, uint16 subTargetType, uint16 subType) { if (!tar) { return false; } @@ -48,7 +48,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType) { return false; } - if (spellType != BotSpellTypes::Resurrect && tar->GetAppearance() == eaDead) { + if ((spellType != BotSpellTypes::Resurrect && spellType != BotSpellTypes::SummonCorpse) && tar->GetAppearance() == eaDead) { if (!((tar->IsClient() && tar->CastToClient()->GetFeigned()) || tar->IsBot())) { return false; } @@ -83,7 +83,12 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType) { } break; - //SpecialAbility::PacifyImmunity -- TODO bot rewrite + case BotSpellTypes::Lull: + if (tar->GetSpecialAbility(SpecialAbility::PacifyImmunity)) { + return false; + } + + break; case BotSpellTypes::Fear: if (tar->GetSpecialAbility(SpecialAbility::FearImmunity)) { return false; @@ -128,6 +133,17 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType) { case BotSpellTypes::PetDamageShields: case BotSpellTypes::ResistBuffs: case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::Teleport: + case BotSpellTypes::Succor: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::Identify: + case BotSpellTypes::Levitate: + case BotSpellTypes::Rune: + case BotSpellTypes::WaterBreathing: + case BotSpellTypes::Size: + case BotSpellTypes::Invisibility: + case BotSpellTypes::MovementSpeed: + case BotSpellTypes::SendHome: if (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) { return false; } @@ -182,6 +198,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType) { return BotCastPet(tar, botClass, botSpell, spellType); case BotSpellTypes::Resurrect: + case BotSpellTypes::SummonCorpse: if (!tar->IsCorpse() || !tar->CastToCorpse()->IsPlayerCorpse()) { return false; } @@ -197,7 +214,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType) { break; } - std::list botSpellList = GetPrioritizedBotSpellsBySpellType(this, spellType, tar, IsAEBotSpellType(spellType)); + std::list botSpellList = GetPrioritizedBotSpellsBySpellType(this, spellType, tar, IsAEBotSpellType(spellType), subTargetType, subType); for (const auto& s : botSpellList) { @@ -643,7 +660,7 @@ bool Bot::AI_PursueCastCheck() { continue; } - if (currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. + if (IsCommandedSpellType(currentCast.spellType) || currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. continue; } @@ -706,7 +723,7 @@ bool Bot::AI_IdleCastCheck() { continue; } - if (currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. + if (IsCommandedSpellType(currentCast.spellType) || currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. continue; } @@ -756,7 +773,7 @@ bool Bot::AI_EngagedCastCheck() { continue; } - if (currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. + if (IsCommandedSpellType(currentCast.spellType) || currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. continue; } @@ -979,7 +996,7 @@ std::list Bot::GetBotSpellsBySpellType(Bot* botCaster, uint16 spellTyp return result; } -std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCaster, uint16 spellType, Mob* tar, bool AE) { +std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCaster, uint16 spellType, Mob* tar, bool AE, uint16 subTargetType, uint16 subType) { std::list result; if (botCaster && botCaster->AI_HasSpells()) { @@ -1001,6 +1018,16 @@ std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa (botSpellList[i].type == spellType || botSpellList[i].type == botCaster->GetSpellListSpellType(spellType)) && botCaster->IsValidSpellTypeBySpellID(spellType, botSpellList[i].spellid) ) { + if ( + botCaster->IsCommandedSpell() && + ( + !botCaster->IsValidSpellTypeSubType(spellType, subTargetType, botSpellList[i].spellid) || + !botCaster->IsValidSpellTypeSubType(spellType, subType, botSpellList[i].spellid) + ) + ) { + continue; + } + if (!AE && IsAnyAESpell(botSpellList[i].spellid) && !IsGroupSpell(botSpellList[i].spellid)) { continue; } @@ -2040,7 +2067,7 @@ BotSpell Bot::GetBestBotSpellForCure(Bot* botCaster, Mob* tar, uint16 spellType) return result; } -uint8 Bot::GetChanceToCastBySpellType(uint16 spellType) //TODO bot rewrite - adjust, move AEs to own rule? +uint8 Bot::GetChanceToCastBySpellType(uint16 spellType) { switch (spellType) { case BotSpellTypes::AENukes: @@ -2072,6 +2099,18 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spellType) //TODO bot rewrite - adj case BotSpellTypes::PetResistBuffs: case BotSpellTypes::DamageShields: case BotSpellTypes::PetDamageShields: + case BotSpellTypes::Teleport: + case BotSpellTypes::Succor: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::Identify: + case BotSpellTypes::Levitate: + case BotSpellTypes::Rune: + case BotSpellTypes::WaterBreathing: + case BotSpellTypes::Size: + case BotSpellTypes::Invisibility: + case BotSpellTypes::MovementSpeed: + case BotSpellTypes::SendHome: + case BotSpellTypes::SummonCorpse: return RuleI(Bots, PercentChanceToCastBuff); case BotSpellTypes::Escape: return RuleI(Bots, PercentChanceToCastEscape); @@ -2949,6 +2988,7 @@ void Bot::CheckBotSpells() { break; } break; + //TODO bot rewrite - add commanded types default: break; @@ -3044,6 +3084,7 @@ void Bot::CheckBotSpells() { else if (IsEffectInSpell(spell_id, SE_Revive)) { correctType = BotSpellTypes::Resurrect; } + //TODO bot rewrite - add commanded types if (!valid || (correctType == UINT16_MAX) || (s.type != correctType)) { LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]" diff --git a/zone/mob.cpp b/zone/mob.cpp index ef6659bed..4c1635655 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -8720,6 +8720,12 @@ uint16 Mob::GetSpellTypeIDByShortName(std::string spellTypeString) { } } + for (int i = BotSpellTypes::COMMANDED_START; i <= BotSpellTypes::COMMANDED_END; ++i) { + if (!Strings::ToLower(spellTypeString).compare(GetSpellTypeShortNameByID(i))) { + return i; + } + } + return UINT16_MAX; } @@ -8892,6 +8898,45 @@ std::string Mob::GetSpellTypeNameByID(uint16 spellType) { case BotSpellTypes::PetResistBuffs: spellTypeName = "Pet Resist Buff"; break; + case BotSpellTypes::Lull: + spellTypeName = "Lull"; + break; + case BotSpellTypes::Teleport: + spellTypeName = "Teleport"; + break; + case BotSpellTypes::Succor: + spellTypeName = "Succor"; + break; + case BotSpellTypes::BindAffinity: + spellTypeName = "Bind Affinity"; + break; + case BotSpellTypes::Identify: + spellTypeName = "Identify"; + break; + case BotSpellTypes::Levitate: + spellTypeName = "Levitate"; + break; + case BotSpellTypes::Rune: + spellTypeName = "Rune"; + break; + case BotSpellTypes::WaterBreathing: + spellTypeName = "Water Breathing"; + break; + case BotSpellTypes::Size: + spellTypeName = "Size"; + break; + case BotSpellTypes::Invisibility: + spellTypeName = "Invisibility"; + break; + case BotSpellTypes::MovementSpeed: + spellTypeName = "Movement Speed"; + break; + case BotSpellTypes::SendHome: + spellTypeName = "Send Home"; + break; + case BotSpellTypes::SummonCorpse: + spellTypeName = "Summon Corpse"; + break; default: break; } @@ -9068,6 +9113,45 @@ std::string Mob::GetSpellTypeShortNameByID(uint16 spellType) { case BotSpellTypes::PetResistBuffs: spellTypeName = "petresistbuffs"; break; + case BotSpellTypes::Lull: + spellTypeName = "lull"; + break; + case BotSpellTypes::Teleport: + spellTypeName = "teleport"; + break; + case BotSpellTypes::Succor: + spellTypeName = "succor"; + break; + case BotSpellTypes::BindAffinity: + spellTypeName = "bindaffinity"; + break; + case BotSpellTypes::Identify: + spellTypeName = "identify"; + break; + case BotSpellTypes::Levitate: + spellTypeName = "levitate"; + break; + case BotSpellTypes::Rune: + spellTypeName = "rune"; + break; + case BotSpellTypes::WaterBreathing: + spellTypeName = "waterbreathing"; + break; + case BotSpellTypes::Size: + spellTypeName = "size"; + break; + case BotSpellTypes::Invisibility: + spellTypeName = "invisibility"; + break; + case BotSpellTypes::MovementSpeed: + spellTypeName = "movementspeed"; + break; + case BotSpellTypes::SendHome: + spellTypeName = "sendhome"; + break; + case BotSpellTypes::SummonCorpse: + spellTypeName = "summoncorpse"; + break; default: break; } @@ -9075,6 +9159,47 @@ std::string Mob::GetSpellTypeShortNameByID(uint16 spellType) { return spellTypeName; } +std::string Mob::GetSubTypeNameByID(uint16 subType) { + std::string subTypeName = "null"; + + switch (subType) { + case CommandedSubTypes::SingleTarget: + subTypeName = "SingleTarget"; + break; + case CommandedSubTypes::GroupTarget: + subTypeName = "GroupTarget"; + break; + case CommandedSubTypes::AETarget: + subTypeName = "AETarget"; + break; + case CommandedSubTypes::SeeInvis: + subTypeName = "SeeInvis"; + break; + case CommandedSubTypes::Invis: + subTypeName = "Invis"; + break; + case CommandedSubTypes::InvisUndead: + subTypeName = "InvisUndead"; + break; + case CommandedSubTypes::InvisAnimals: + subTypeName = "InvisAnimals"; + break; + case CommandedSubTypes::Shrink: + subTypeName = "Shrink"; + break; + case CommandedSubTypes::Grow: + subTypeName = "Grow"; + break; + case CommandedSubTypes::Selo: + subTypeName = "Selo"; + break; + default: + break; + } + + return subTypeName; +} + bool Mob::GetDefaultSpellHold(uint16 spellType, uint8 stance) { switch (spellType) { case BotSpellTypes::Nuke: diff --git a/zone/mob.h b/zone/mob.h index 662798af5..865e768ba 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -435,7 +435,8 @@ public: uint16 GetSpellTypeIDByShortName(std::string spellTypeString); std::string GetSpellTypeNameByID(uint16 spellType); - std::string GetSpellTypeShortNameByID(uint16 spellType); + std::string GetSpellTypeShortNameByID(uint16 spellType); + std::string GetSubTypeNameByID(uint16 subType); bool GetDefaultSpellHold(uint16 spellType, uint8 stance = Stance::Balanced); uint16 GetDefaultSpellDelay(uint16 spellType, uint8 stance = Stance::Balanced); From 10effce2a6e1fbdcbeea60797859777f5c568b17 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:51:37 -0600 Subject: [PATCH 38/97] Implement more commanded types properly, move shadownight hate to hateline type... Add incapacitated checks to casting logic and checks. Add candocombat zone check, summon other's corpse for bot, in/out combat spell checks, mute checks, level restriction --- .../database_update_manifest_bots.cpp | 125 ++++++-- common/ruletypes.h | 1 + common/spdat.cpp | 67 ++-- common/spdat.h | 5 +- common/version.h | 2 +- zone/bot.cpp | 288 +++++++++++------- zone/bot_commands/cast.cpp | 26 +- zone/botspellsai.cpp | 241 +++++++++++---- zone/mob.cpp | 66 +++- zone/spells.cpp | 15 +- 10 files changed, 588 insertions(+), 248 deletions(-) diff --git a/common/database/database_update_manifest_bots.cpp b/common/database/database_update_manifest_bots.cpp index 96627d814..72e26142c 100644 --- a/common/database/database_update_manifest_bots.cpp +++ b/common/database/database_update_manifest_bots.cpp @@ -513,7 +513,7 @@ UPDATE bot_spells_entries SET `type` = 4 WHERE `spell_id` = 10436; .match = "", .sql = R"( INSERT INTO `bot_spells_entries` (`npc_spells_id`, `spell_id`, `type`, `minlevel`, `maxlevel`) -VALUES +VALUES (3006, 9957, 100, 20, 254), (3006, 9956, 100, 20, 254), (3006, 552, 100, 25, 254), @@ -764,31 +764,6 @@ VALUES (3006, 34863, 101, 96, 254), (3006, 34864, 101, 96, 254), (3006, 34862, 101, 96, 254), -(3007, 4614, 101, 35, 49), -(3007, 4683, 101, 50, 56), -(3007, 4684, 101, 57, 63), -(3007, 4698, 101, 64, 64), -(3007, 5019, 101, 65, 254), -(3007, 5020, 101, 65, 254), -(3007, 6175, 101, 69, 70), -(3007, 10949, 101, 71, 75), -(3007, 10947, 101, 71, 75), -(3007, 10948, 101, 71, 75), -(3007, 14800, 101, 76, 80), -(3007, 14801, 101, 76, 80), -(3007, 14799, 101, 76, 80), -(3007, 18905, 101, 81, 85), -(3007, 18906, 101, 81, 85), -(3007, 18904, 101, 81, 85), -(3007, 25912, 101, 86, 90), -(3007, 25913, 101, 86, 90), -(3007, 25911, 101, 86, 90), -(3007, 29006, 101, 91, 95), -(3007, 29007, 101, 91, 95), -(3007, 29008, 101, 91, 95), -(3007, 35047, 101, 96, 254), -(3007, 35048, 101, 96, 254), -(3007, 35049, 101, 96, 254), (3008, 728, 101, 8, 60), (3008, 3361, 101, 61, 254), (3008, 5370, 101, 66, 70), @@ -1090,6 +1065,104 @@ VALUES (3011, 25555, 112, 86, 90), (3011, 28632, 112, 91, 95), (3011, 34662, 112, 96, 254); +)" + }, + ManifestEntry{ + .version = 9051, + .description = "2024_11_26_remove_sk_icb.sql", + .check = "SELECT * FROM `bot_spells_entries` where `type` = 55", + .condition = "empty", + .match = "", + .sql = R"( +DELETE +FROM bot_spells_entries +WHERE `npc_spells_id` = 3005 +AND `type` = 10; + +INSERT INTO `bot_spells_entries` (`npc_spells_id`, `spell_id`, `type`, `minlevel`, `maxlevel`) +VALUES +(3003, 10175, 55, 72, 76), +(3003, 10173, 55, 72, 76), +(3003, 10174, 55, 72, 76), +(3003, 14956, 55, 77, 81), +(3003, 14954, 55, 77, 81), +(3003, 14955, 55, 77, 81), +(3003, 19070, 55, 82, 86), +(3003, 19068, 55, 82, 86), +(3003, 19069, 55, 82, 86), +(3003, 25298, 55, 87, 91), +(3003, 25299, 55, 87, 91), +(3003, 25297, 55, 87, 91), +(3003, 28348, 55, 92, 96), +(3003, 28349, 55, 92, 96), +(3003, 28347, 55, 92, 96), +(3003, 34351, 55, 97, 254), +(3003, 34352, 55, 97, 254), +(3003, 34350, 55, 97, 254), +(3003, 40078, 55, 98, 254), +(3003, 40079, 55, 98, 254), +(3003, 40080, 55, 98, 254), +(3005, 1221, 55, 33, 41), +(3005, 1222, 55, 42, 52), +(3005, 1223, 55, 53, 58), +(3005, 1224, 55, 59, 62), +(3005, 3405, 55, 63, 66), +(3005, 5329, 55, 67, 70), +(3005, 5336, 55, 69, 73), +(3005, 10258, 55, 71, 71), +(3005, 10259, 55, 71, 71), +(3005, 10257, 55, 71, 71), +(3005, 10261, 55, 72, 76), +(3005, 10262, 55, 72, 76), +(3005, 10260, 55, 72, 76), +(3005, 10291, 55, 74, 78), +(3005, 10292, 55, 74, 78), +(3005, 10293, 55, 74, 78), +(3005, 15160, 55, 76, 76), +(3005, 15161, 55, 76, 76), +(3005, 15162, 55, 76, 76), +(3005, 15165, 55, 77, 81), +(3005, 15163, 55, 77, 81), +(3005, 15164, 55, 77, 81), +(3005, 15186, 55, 79, 83), +(3005, 15184, 55, 79, 83), +(3005, 15185, 55, 79, 83), +(3005, 19315, 55, 81, 81), +(3005, 19313, 55, 81, 81), +(3005, 19314, 55, 81, 81), +(3005, 19317, 55, 82, 86), +(3005, 19318, 55, 82, 86), +(3005, 19316, 55, 82, 86), +(3005, 19338, 55, 84, 88), +(3005, 19339, 55, 84, 88), +(3005, 19337, 55, 84, 88), +(3005, 25581, 55, 86, 86), +(3005, 25582, 55, 86, 86), +(3005, 25580, 55, 86, 86), +(3005, 25586, 55, 87, 91), +(3005, 25587, 55, 87, 91), +(3005, 25588, 55, 87, 91), +(3005, 25641, 55, 89, 93), +(3005, 25642, 55, 89, 93), +(3005, 25643, 55, 89, 93), +(3005, 28659, 55, 91, 91), +(3005, 28657, 55, 91, 91), +(3005, 28658, 55, 91, 91), +(3005, 28665, 55, 92, 96), +(3005, 28663, 55, 92, 96), +(3005, 28664, 55, 92, 96), +(3005, 28735, 55, 94, 98), +(3005, 28733, 55, 94, 98), +(3005, 28734, 55, 94, 98), +(3005, 34688, 55, 96, 96), +(3005, 34689, 55, 96, 96), +(3005, 34687, 55, 96, 96), +(3005, 34694, 55, 97, 254), +(3005, 34695, 55, 97, 254), +(3005, 34693, 55, 97, 254), +(3005, 34752, 55, 99, 254), +(3005, 34753, 55, 99, 254), +(3005, 34751, 55, 99, 254); )" } // -- template; copy/paste this when you need to create a new entry diff --git a/common/ruletypes.h b/common/ruletypes.h index 32c8378fb..2dcdd3e97 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -800,6 +800,7 @@ RULE_INT(Bots, PercentChanceToCastSnare, 75, "The chance for a bot to attempt to RULE_INT(Bots, PercentChanceToCastDOT, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") RULE_INT(Bots, PercentChanceToCastDispel, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") RULE_INT(Bots, PercentChanceToCastInCombatBuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastHateLine, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") RULE_INT(Bots, PercentChanceToCastMez, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") RULE_INT(Bots, PercentChanceToCastSlow, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") RULE_INT(Bots, PercentChanceToCastDebuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") diff --git a/common/spdat.cpp b/common/spdat.cpp index a691d5197..c4086c0ac 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -2843,13 +2843,8 @@ bool BOT_SPELL_TYPES_DETRIMENTAL(uint16 spellType, uint8 cls) { case BotSpellTypes::AELifetap: case BotSpellTypes::PBAENuke: case BotSpellTypes::Lull: + case BotSpellTypes::HateLine: return true; - case BotSpellTypes::InCombatBuff: - if (cls == Class::ShadowKnight) { - return true; - } - - return false; default: return false; } @@ -2898,12 +2893,6 @@ bool BOT_SPELL_TYPES_BENEFICIAL(uint16 spellType, uint8 cls) { case BotSpellTypes::MovementSpeed: case BotSpellTypes::SendHome: case BotSpellTypes::SummonCorpse: - return true; - case BotSpellTypes::InCombatBuff: - if (cls == Class::ShadowKnight) { - return false; - } - return true; default: return false; @@ -3134,12 +3123,7 @@ bool SpellTypeRequiresLoS(uint16 spellType, uint16 cls) { case BotSpellTypes::PetFastHeals: case BotSpellTypes::PetVeryFastHeals: case BotSpellTypes::PetHoTHeals: - return false; case BotSpellTypes::InCombatBuff: - if (cls && cls == Class::ShadowKnight) { - return true; - } - return false; default: return true; @@ -3150,13 +3134,44 @@ bool SpellTypeRequiresLoS(uint16 spellType, uint16 cls) { bool SpellTypeRequiresTarget(uint16 spellType, uint16 cls) { switch (spellType) { - case BotSpellTypes::Escape: - if (cls == Class::ShadowKnight) { - return false; - } - - return true; case BotSpellTypes::Pet: + case BotSpellTypes::Succor: + return false; + default: + return true; + } + + return true; +} + +bool SpellTypeRequiresCastChecks(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::AEDebuff: + case BotSpellTypes::AEDispel: + case BotSpellTypes::AEDoT: + case BotSpellTypes::AEFear: + case BotSpellTypes::AELifetap: + case BotSpellTypes::AEMez: + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::AERoot: + case BotSpellTypes::AESlow: + case BotSpellTypes::AESnare: + case BotSpellTypes::AEStun: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Mez: + case BotSpellTypes::SummonCorpse: + return false; + default: + return true; + } + + return true; +} + +bool SpellTypeRequiresAEChecks(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::AEMez: return false; default: return true; @@ -3263,6 +3278,10 @@ bool IsDamageShieldOnlySpell(uint16 spell_id) { bool IsCommandedSpellType(uint16 spellType) { switch (spellType) { + case BotSpellTypes::Charm: + case BotSpellTypes::AEFear: + case BotSpellTypes::Fear: + case BotSpellTypes::Resurrect: case BotSpellTypes::Lull: case BotSpellTypes::Teleport: case BotSpellTypes::Succor: @@ -3276,8 +3295,6 @@ bool IsCommandedSpellType(uint16 spellType) { case BotSpellTypes::MovementSpeed: case BotSpellTypes::SendHome: case BotSpellTypes::SummonCorpse: - //case BotSpellTypes::Charm: - //case BotSpellTypes::Resurrect: //case BotSpellTypes::Cure: //case BotSpellTypes::GroupCures: //case BotSpellTypes::DamageShields: diff --git a/common/spdat.h b/common/spdat.h index cefac6784..1c3bcf52c 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -708,6 +708,7 @@ namespace BotSpellTypes constexpr uint16 ResistBuffs = 52; constexpr uint16 PetDamageShields = 53; constexpr uint16 PetResistBuffs = 54; + constexpr uint16 HateLine = 55; // Command Spell Types constexpr uint16 Teleport = 100; // this is handled by ^depart so uses other logic @@ -725,7 +726,7 @@ namespace BotSpellTypes constexpr uint16 SummonCorpse = 112; constexpr uint16 START = BotSpellTypes::Nuke; // Do not remove or change this - constexpr uint16 END = BotSpellTypes::PetResistBuffs; // Do not remove this, increment as needed + constexpr uint16 END = BotSpellTypes::HateLine; // Do not remove this, increment as needed constexpr uint16 COMMANDED_START = BotSpellTypes::Lull; // Do not remove or change this constexpr uint16 COMMANDED_END = BotSpellTypes::SummonCorpse; // Do not remove this, increment as needed } @@ -747,6 +748,8 @@ bool IsClientBotSpellType(uint16 spellType); bool IsHealBotSpellType(uint16 spellType); bool SpellTypeRequiresLoS(uint16 spellType, uint16 cls = 0); bool SpellTypeRequiresTarget(uint16 spellType, uint16 cls = 0); +bool SpellTypeRequiresCastChecks(uint16 spellType); +bool SpellTypeRequiresAEChecks(uint16 spellType); bool IsCommandedSpellType(uint16 spellType); // These should not be used to determine spell category.. diff --git a/common/version.h b/common/version.h index d9d57e7ca..800ee365d 100644 --- a/common/version.h +++ b/common/version.h @@ -43,7 +43,7 @@ */ #define CURRENT_BINARY_DATABASE_VERSION 9284 -#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9050 //TODO update as needed +#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9051 //TODO update as needed #endif diff --git a/zone/bot.cpp b/zone/bot.cpp index 56736942e..fedaa3264 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -5675,9 +5675,7 @@ bool Bot::CastSpell( casting_spell_id || delaytimer || spellend_timer.Enabled() || - IsStunned() || - IsFeared() || - IsMezzed() || + ((IsStunned() || IsMezzed() || DivineAura()) && !IsCastNotStandingSpell(spell_id)) || (IsSilenced() && !IsDiscipline(spell_id)) || (IsAmnesiad() && IsDiscipline(spell_id)) ) { @@ -9414,7 +9412,12 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec if (doPrechecks) { if (spells[spell_id].target_type == ST_Self && tar != this) { - tar = this; + if (IsEffectInSpell(spell_id, SE_SummonCorpse) && RuleB(Bots, AllowCommandedSummonCorpse)) { + //tar = this; + } + else { + tar = this; + } } if (!PrecastChecks(tar, spellType)) { @@ -9430,11 +9433,30 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec return false; } - if (spells[spell_id].target_type == ST_Self && tar != this) { + if (IsFeared() || IsSilenced() || IsAmnesiad()) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Incapacitated.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + + if ((IsStunned() || IsMezzed() || DivineAura()) && !IsCastNotStandingSpell(spell_id)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to !IsCastNotStandingSpell.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + + if ( + spells[spell_id].target_type == ST_Self + && tar != this && + (spellType != BotSpellTypes::SummonCorpse || RuleB(Bots, AllowCommandedSummonCorpse)) + ) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to ST_Self.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } + if (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanDoCombat.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + if (!CheckSpellRecastTimer(spell_id)) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} due to !CheckSpellRecastTimer.'", GetCleanName(), GetSpellName(spell_id)); //deleteme return false; @@ -9450,6 +9472,42 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec return false; } + if (this == tar && IsSacrificeSpell(spell_id)) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to IsSacrificeSpell.'", GetCleanName(), GetSpellName(spell_id)); //deleteme + return false; + } + + if (spells[spell_id].caster_requirement_id && !PassCastRestriction(spells[spell_id].caster_requirement_id)) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to !PassCastRestriction.'", GetCleanName(), GetSpellName(spell_id)); //deleteme + return false; + } + + if (!spells[spell_id].can_cast_in_combat && spells[spell_id].can_cast_out_of_combat) { + if (IsBeneficialSpell(spell_id)) { + if (IsEngaged()) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to !can_cast_in_combat.'", GetCleanName(), GetSpellName(spell_id)); //deleteme + return false; + } + } + } + else if (spells[spell_id].can_cast_in_combat && !spells[spell_id].can_cast_out_of_combat) { + if (IsBeneficialSpell(spell_id)) { + if (!IsEngaged()) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to !can_cast_out_of_combat.'", GetCleanName(), GetSpellName(spell_id)); //deleteme + return false; + } + } + } + + if (!IsDiscipline(spell_id)) { + int chance = GetFocusEffect(focusFcMute, spell_id); + + if (chance && zone->random.Roll(chance)) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to focusFcMute.'", GetCleanName(), GetSpellName(spell_id)); //deleteme + return(false); + } + } + if (!zone->CanLevitate() && IsEffectInSpell(spell_id, SE_Levitate)) { LogBotPreChecks("{} says, 'Cancelling cast of {} due to !CanLevitate.'", GetCleanName(), GetSpellName(spell_id)); //deleteme return false; @@ -9534,6 +9592,7 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spell_id, Mob* tar) { case BotSpellTypes::Buff: case BotSpellTypes::PetBuffs: case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::InCombatBuff: case BotSpellTypes::DamageShields: case BotSpellTypes::PetDamageShields: case BotSpellTypes::ResistBuffs: @@ -9567,6 +9626,11 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spell_id, Mob* tar) { return false; } + if (!tar->CheckSpellLevelRestriction(this, spell_id)) { + LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to CheckSpellLevelRestriction.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + if ((spellType != BotSpellTypes::Teleport && spellType != BotSpellTypes::Succor) && (IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Succor))) { LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to Teleport.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; @@ -9679,6 +9743,13 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spell_id, Mob* tar) { } } + break; + case BotSpellTypes::Lull: + if (IsHarmonySpell(spell_id) && !HarmonySpellLevelCheck(spell_id, tar)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to HarmonySpellLevelCheck.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + break; default: break; @@ -10505,50 +10576,48 @@ uint16 Bot::GetDefaultSpellTypeEngagedPriority(uint16 spellType, uint8 botClass, return 20; case BotSpellTypes::Mez: return 21; - case BotSpellTypes::AEDispel: + case BotSpellTypes::HateLine: return 22; - case BotSpellTypes::Dispel: + case BotSpellTypes::AEDispel: return 23; - case BotSpellTypes::AEDebuff: + case BotSpellTypes::Dispel: return 24; - case BotSpellTypes::Debuff: + case BotSpellTypes::AEDebuff: return 25; - case BotSpellTypes::AESnare: + case BotSpellTypes::Debuff: return 26; - case BotSpellTypes::Snare: + case BotSpellTypes::AESnare: return 27; - case BotSpellTypes::AEFear: + case BotSpellTypes::Snare: return 28; - case BotSpellTypes::Fear: - return 29; case BotSpellTypes::AESlow: - return 30; + return 29; case BotSpellTypes::Slow: - return 31; + return 30; case BotSpellTypes::AERoot: - return 32; + return 31; case BotSpellTypes::Root: - return 33; + return 32; case BotSpellTypes::AEDoT: - return 34; + return 33; case BotSpellTypes::DOT: - return 35; + return 34; case BotSpellTypes::AEStun: - return 36; + return 35; case BotSpellTypes::PBAENuke: - return 37; + return 36; case BotSpellTypes::AENukes: - return 38; + return 37; case BotSpellTypes::AERains: - return 39; + return 38; case BotSpellTypes::Stun: - return 40; + return 39; case BotSpellTypes::Nuke: - return 41; + return 40; case BotSpellTypes::InCombatBuff: - return 42; + return 41; case BotSpellTypes::InCombatBuffSong: - return 43; + return 42; default: return 0; } @@ -10915,8 +10984,6 @@ uint16 Bot::GetSpellListSpellType(uint16 spellType) { case BotSpellTypes::PetDamageShields: case BotSpellTypes::ResistBuffs: case BotSpellTypes::PetResistBuffs: - case BotSpellTypes::Teleport: - case BotSpellTypes::Succor: case BotSpellTypes::BindAffinity: case BotSpellTypes::Identify: case BotSpellTypes::Levitate: @@ -10925,8 +10992,6 @@ uint16 Bot::GetSpellListSpellType(uint16 spellType) { case BotSpellTypes::Size: case BotSpellTypes::Invisibility: case BotSpellTypes::MovementSpeed: - case BotSpellTypes::SendHome: - case BotSpellTypes::SummonCorpse: return BotSpellTypes::Buff; case BotSpellTypes::AEMez: case BotSpellTypes::Mez: @@ -10961,6 +11026,7 @@ uint16 Bot::GetSpellListSpellType(uint16 spellType) { case BotSpellTypes::Charm: case BotSpellTypes::Escape: case BotSpellTypes::HateRedux: + case BotSpellTypes::HateLine: case BotSpellTypes::InCombatBuff: case BotSpellTypes::InCombatBuffSong: case BotSpellTypes::OutOfCombatBuffSong: @@ -11032,84 +11098,84 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spellType, uint16 spell_id) { } return false; - case BotSpellTypes::Lull: - if (!IsHarmonySpell(spell_id)) { - return true; - } - - return false; - case BotSpellTypes::Teleport: - if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))) { - return true; - } - - return false; - case BotSpellTypes::Succor: - if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Succor)) { - return true; - } - - return false; - case BotSpellTypes::BindAffinity: - if (IsEffectInSpell(spell_id, SE_BindAffinity)) { - return true; - } - - return false; - case BotSpellTypes::Identify: - if (IsEffectInSpell(spell_id, SE_Identify)) { - return true; - } - - return false; - case BotSpellTypes::Levitate: - if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Levitate))) { - return true; - } - - return false; - case BotSpellTypes::Rune: - if (IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune)) { - return true; - } - - return false; - case BotSpellTypes::WaterBreathing: - if (IsEffectInSpell(spell_id, SE_WaterBreathing)) { - return true; - } - - return false; - case BotSpellTypes::Size: - if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))) { - return true; - } - - return false; - case BotSpellTypes::Invisibility: - if (IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id)) { - return true; - } - - return false; - case BotSpellTypes::MovementSpeed: - if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) { - return true; - } - - return false; - case BotSpellTypes::SendHome: - if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_GateToHomeCity)) { - return true; - } - - return false; - case BotSpellTypes::SummonCorpse: - if (IsEffectInSpell(spell_id, SE_SummonCorpse)) { - return true; - } - - return false; + //case BotSpellTypes::Lull: + // if (IsHarmonySpell(spell_id)) { + // return true; + // } + // + // return false; + //case BotSpellTypes::Teleport: + // if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))) { + // return true; + // } + // + // return false; + //case BotSpellTypes::Succor: + // if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Succor)) { + // return true; + // } + // + // return false; + //case BotSpellTypes::BindAffinity: + // if (IsEffectInSpell(spell_id, SE_BindAffinity)) { + // return true; + // } + // + // return false; + //case BotSpellTypes::Identify: + // if (IsEffectInSpell(spell_id, SE_Identify)) { + // return true; + // } + // + // return false; + //case BotSpellTypes::Levitate: + // if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Levitate))) { + // return true; + // } + // + // return false; + //case BotSpellTypes::Rune: + // if (IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune)) { + // return true; + // } + // + // return false; + //case BotSpellTypes::WaterBreathing: + // if (IsEffectInSpell(spell_id, SE_WaterBreathing)) { + // return true; + // } + // + // return false; + //case BotSpellTypes::Size: + // if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))) { + // return true; + // } + // + // return false; + //case BotSpellTypes::Invisibility: + // if (IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id)) { + // return true; + // } + // + // return false; + //case BotSpellTypes::MovementSpeed: + // if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) { + // return true; + // } + // + // return false; + //case BotSpellTypes::SendHome: + // if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_GateToHomeCity)) { + // return true; + // } + // + // return false; + //case BotSpellTypes::SummonCorpse: + // if (IsEffectInSpell(spell_id, SE_SummonCorpse)) { + // return true; + // } + // + // return false; default: return true; } @@ -11342,7 +11408,7 @@ bool Bot::HasValidAETarget(Bot* botCaster, uint16 spell_id, uint16 spellType, Mo break; } - if (!m->IsNPC() || !m->CastToNPC()->IsOnHatelist(botCaster->GetOwner())) { + if (!m->IsNPC() || (!IsCommandedSpell() && !m->CastToNPC()->IsOnHatelist(botCaster->GetOwner()))) { continue; } diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index 49112b869..10238aac2 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -308,7 +308,7 @@ void bot_command_cast(Client* c, const Seperator* sep) } Mob* tar = c->GetTarget(); - LogTestDebug("{}: 'Attempting {} [{}] on {}'", __LINE__, c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (tar ? tar->GetCleanName() : "NOBODY")); //deleteme + //LogTestDebug("{}: 'Attempting {} [{}-{}] on {}'", __LINE__, c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (subTargetType != UINT16_MAX ? c->GetSubTypeNameByID(subTargetType) : "Standard"), (tar ? tar->GetCleanName() : "NOBODY")); //deleteme if (!tar) { if (spellType != BotSpellTypes::Escape && spellType != BotSpellTypes::Pet) { @@ -338,7 +338,17 @@ void bot_command_cast(Client* c, const Seperator* sep) break; default: - if (BOT_SPELL_TYPES_DETRIMENTAL(spellType) && !c->IsAttackAllowed(tar)) { + if ( + (BOT_SPELL_TYPES_DETRIMENTAL(spellType) && !c->IsAttackAllowed(tar)) || + ( + spellType == BotSpellTypes::Charm && + ( + tar->IsClient() || + tar->IsCorpse() || + tar->GetOwner() + ) + ) + ) { c->Message(Chat::Yellow, "You cannot attack [%s].", tar->GetCleanName()); return; @@ -395,18 +405,16 @@ void bot_command_cast(Client* c, const Seperator* sep) /* TODO bot rewrite - - FIX: Depart, SummonCorpse, Lull, - Group Cures, Precombat, Fear/AE Fear - ICB (SK) casting hate on friendly but not hostile? + FIX: Depart + Group Cures, Precombat NEED TO CHECK: precombat, AE Dispel, AE Lifetap - DO I NEED A PBAE CHECK??? */ - if (bot_iter->GetBotStance() == Stance::Passive || bot_iter->GetHoldFlag() || bot_iter->GetAppearance() == eaDead || bot_iter->IsFeared() || bot_iter->IsStunned() || bot_iter->IsMezzed() || bot_iter->DivineAura() || bot_iter->GetHP() < 0) { + if (bot_iter->GetBotStance() == Stance::Passive || bot_iter->GetHoldFlag() || bot_iter->GetAppearance() == eaDead || bot_iter->IsFeared() || bot_iter->IsSilenced() || bot_iter->IsAmnesiad() || bot_iter->GetHP() < 0) { continue; } Mob* newTar = tar; - LogTestDebug("{}: {} says, 'Attempting {} [{}] on {}'", __LINE__, bot_iter->GetCleanName(), c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme + //LogTestDebug("{}: {} says, 'Attempting {} [{}-{}] on {}'", __LINE__, bot_iter->GetCleanName(), c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (subTargetType != UINT16_MAX ? c->GetSubTypeNameByID(subTargetType) : "Standard"), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme if (!SpellTypeRequiresTarget(spellType, bot_iter->GetClass())) { newTar = bot_iter; } @@ -435,7 +443,7 @@ void bot_command_cast(Client* c, const Seperator* sep) continue; } - LogTestDebug("{}: {} says, 'Attempting {} [{}] on {}'", __LINE__, bot_iter->GetCleanName(), c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme + LogTestDebug("{}: {} says, 'Attempting {} [{}-{}] on {}'", __LINE__, bot_iter->GetCleanName(), c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (subTargetType != UINT16_MAX ? c->GetSubTypeNameByID(subTargetType) : "Standard"), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme bot_iter->SetCommandedSpell(true); diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 241854dfa..d3123bf86 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -116,11 +116,13 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType, uint16 subTarge break; case BotSpellTypes::InCombatBuff: - if (GetClass() == Class::ShadowKnight && (tar->IsOfClientBot() || (tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot()))) { + if (!IsCommandedSpell() && GetClass() != Class::Shaman && spellType == BotSpellTypes::InCombatBuff && IsCasterClass(GetClass()) && GetLevel() >= GetStopMeleeLevel()) { return false; } - if (!IsCommandedSpell() && GetClass() != Class::Shaman && spellType == BotSpellTypes::InCombatBuff && IsCasterClass(GetClass()) && GetLevel() >= GetStopMeleeLevel()) { + break; + case BotSpellTypes::HateLine: + if (!tar->IsNPC()) { return false; } @@ -198,7 +200,6 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType, uint16 subTarge return BotCastPet(tar, botClass, botSpell, spellType); case BotSpellTypes::Resurrect: - case BotSpellTypes::SummonCorpse: if (!tar->IsCorpse() || !tar->CastToCorpse()->IsPlayerCorpse()) { return false; } @@ -267,6 +268,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType, uint16 subTarge return true; } + else { LogTestDebug("{} says, '{} [#{}] - [{}] FAILED AIDoSpellCast on {}.'", GetCleanName(), spells[s.SpellId].name, s.SpellId, GetSpellTypeNameByID(spellType), tar->GetCleanName()); } //deleteme } return false; @@ -631,7 +633,7 @@ bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain } bool Bot::AI_PursueCastCheck() { - if (GetAppearance() == eaDead || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0) { + if (GetAppearance() == eaDead || IsFeared() || IsSilenced() || IsAmnesiad() || GetHP() < 0) { return false; } @@ -656,11 +658,11 @@ bool Bot::AI_PursueCastCheck() { continue; } - if (RuleB(Bots, AllowAIMez) && (currentCast.spellType == BotSpellTypes::AEMez || currentCast.spellType == BotSpellTypes::Mez)) { + if (!RuleB(Bots, AllowAIMez) && (currentCast.spellType == BotSpellTypes::AEMez || currentCast.spellType == BotSpellTypes::Mez)) { continue; } - if (IsCommandedSpellType(currentCast.spellType) || currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. + if (IsCommandedSpellType(currentCast.spellType)) { // Unsupported by AI currently. continue; } @@ -680,7 +682,7 @@ bool Bot::AI_PursueCastCheck() { } bool Bot::AI_IdleCastCheck() { - if (GetAppearance() == eaDead || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0) { + if (GetAppearance() == eaDead || IsFeared() || IsSilenced() || IsAmnesiad() || GetHP() < 0) { return false; } @@ -719,11 +721,11 @@ bool Bot::AI_IdleCastCheck() { continue; } - if (RuleB(Bots, AllowAIMez) && (currentCast.spellType == BotSpellTypes::AEMez || currentCast.spellType == BotSpellTypes::Mez)) { + if (!RuleB(Bots, AllowAIMez) && (currentCast.spellType == BotSpellTypes::AEMez || currentCast.spellType == BotSpellTypes::Mez)) { continue; } - if (IsCommandedSpellType(currentCast.spellType) || currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. + if (IsCommandedSpellType(currentCast.spellType)) { // Unsupported by AI currently. continue; } @@ -743,7 +745,7 @@ bool Bot::AI_IdleCastCheck() { } bool Bot::AI_EngagedCastCheck() { - if (GetAppearance() == eaDead || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0) { + if (GetAppearance() == eaDead || IsFeared() || IsSilenced() || IsAmnesiad() || GetHP() < 0) { return false; } @@ -769,11 +771,11 @@ bool Bot::AI_EngagedCastCheck() { continue; } - if (RuleB(Bots, AllowAIMez) && (currentCast.spellType == BotSpellTypes::AEMez || currentCast.spellType == BotSpellTypes::Mez)) { + if (!RuleB(Bots, AllowAIMez) && (currentCast.spellType == BotSpellTypes::AEMez || currentCast.spellType == BotSpellTypes::Mez)) { continue; } - if (IsCommandedSpellType(currentCast.spellType) || currentCast.spellType == BotSpellTypes::Resurrect || currentCast.spellType == BotSpellTypes::Charm) { // Unsupported by AI currently. + if (IsCommandedSpellType(currentCast.spellType)) { // Unsupported by AI currently. continue; } @@ -1045,28 +1047,19 @@ std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa } if ( - ( - !botCaster->IsCommandedSpell() || - ( - botCaster->IsCommandedSpell() && - (spellType != BotSpellTypes::Mez && spellType != BotSpellTypes::AEMez) - ) - ) - && - ( - !IsPBAESpell(botSpellList[i].spellid) && - !botCaster->CastChecks(botSpellList[i].spellid, tar, spellType, false, IsAEBotSpellType(spellType)) - ) + (!botCaster->IsCommandedSpell() || (botCaster->IsCommandedSpell() && SpellTypeRequiresCastChecks(spellType))) && + (!IsPBAESpell(botSpellList[i].spellid) && !botCaster->CastChecks(botSpellList[i].spellid, tar, spellType, false, IsAEBotSpellType(spellType))) ) { continue; } if ( - botCaster->IsCommandedSpell() || + botCaster->IsCommandedSpell() || !AE || - (spellType == BotSpellTypes::GroupCures) || - (spellType == BotSpellTypes::AEMez) || - (AE && botCaster->HasValidAETarget(botCaster, botSpellList[i].spellid, spellType, tar)) + ( + SpellTypeRequiresAEChecks(spellType) && + botCaster->HasValidAETarget(botCaster, botSpellList[i].spellid, spellType, tar) + ) ) { BotSpell_wPriority botSpell; botSpell.SpellId = botSpellList[i].spellid; @@ -2099,18 +2092,6 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spellType) case BotSpellTypes::PetResistBuffs: case BotSpellTypes::DamageShields: case BotSpellTypes::PetDamageShields: - case BotSpellTypes::Teleport: - case BotSpellTypes::Succor: - case BotSpellTypes::BindAffinity: - case BotSpellTypes::Identify: - case BotSpellTypes::Levitate: - case BotSpellTypes::Rune: - case BotSpellTypes::WaterBreathing: - case BotSpellTypes::Size: - case BotSpellTypes::Invisibility: - case BotSpellTypes::MovementSpeed: - case BotSpellTypes::SendHome: - case BotSpellTypes::SummonCorpse: return RuleI(Bots, PercentChanceToCastBuff); case BotSpellTypes::Escape: return RuleI(Bots, PercentChanceToCastEscape); @@ -2124,6 +2105,8 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spellType) return RuleI(Bots, PercentChanceToCastDispel); case BotSpellTypes::InCombatBuff: return RuleI(Bots, PercentChanceToCastInCombatBuff); + case BotSpellTypes::HateLine: + return RuleI(Bots, PercentChanceToCastHateLine); case BotSpellTypes::Mez: return RuleI(Bots, PercentChanceToCastMez); case BotSpellTypes::Slow: @@ -2842,65 +2825,71 @@ void Bot::CheckBotSpells() { switch (s.type) { - case BotSpellTypes::Nuke: //DONE + case BotSpellTypes::Nuke: if (IsAnyNukeOrStunSpell(spell_id) && !IsEffectInSpell(spell_id, SE_Root) && !IsDebuffSpell(spell_id)) { valid = true; break; } break; - case BotSpellTypes::RegularHeal: //DONE - //if (IsAnyHealSpell(spell_id) && !IsEscapeSpell(spell_id) && (IsRegularPetHealSpell(spell_id) || !IsCureSpell(spell_id))) { + case BotSpellTypes::RegularHeal: if (IsAnyHealSpell(spell_id) && !IsEscapeSpell(spell_id)) { valid = true; break; } break; - case BotSpellTypes::Root: //DONE + case BotSpellTypes::Root: if (IsEffectInSpell(spell_id, SE_Root)) { valid = true; break; } break; - case BotSpellTypes::Buff: //DONE + case BotSpellTypes::Buff: if (IsAnyBuffSpell(spell_id)) { valid = true; break; } break; - case BotSpellTypes::Pet: //DONE + case BotSpellTypes::Pet: if (IsSummonPetSpell(spell_id) || IsEffectInSpell(spell_id, SE_TemporaryPets)) { valid = true; break; } break; - case BotSpellTypes::Lifetap: //DONE + case BotSpellTypes::Lifetap: if (IsLifetapSpell(spell_id)) { valid = true; break; } break; - case BotSpellTypes::Snare: //DONE + case BotSpellTypes::Snare: if (IsEffectInSpell(spell_id, SE_MovementSpeed) && IsDetrimentalSpell(spell_id)) { valid = true; break; } break; - case BotSpellTypes::DOT: //DONE + case BotSpellTypes::DOT: if (IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id)) { valid = true; break; } break; - case BotSpellTypes::Dispel: //DONE + case BotSpellTypes::Dispel: if (IsDispelSpell(spell_id)) { valid = true; break; } break; - case BotSpellTypes::InCombatBuff: //DONE + case BotSpellTypes::InCombatBuff: if ( IsSelfConversionSpell(spell_id) || - IsAnyBuffSpell(spell_id) || + IsAnyBuffSpell(spell_id) + ) { + valid = true; + break; + } + break; + case BotSpellTypes::HateLine: + if ( (IsEffectInSpell(spell_id, SE_Hate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_Hate)] > 0) || (IsEffectInSpell(spell_id, SE_InstantHate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_InstantHate)] > 0) ) { @@ -2908,37 +2897,37 @@ void Bot::CheckBotSpells() { break; } break; - case BotSpellTypes::Mez: //DONE + case BotSpellTypes::Mez: if (IsMesmerizeSpell(spell_id)) { valid = true; break; } break; - case BotSpellTypes::Charm: //DONE + case BotSpellTypes::Charm: if (IsCharmSpell(spell_id)) { valid = true; break; } break; - case BotSpellTypes::Slow: //DONE + case BotSpellTypes::Slow: if (IsSlowSpell(spell_id)) { valid = true; break; } break; - case BotSpellTypes::Debuff: //DONE + case BotSpellTypes::Debuff: if (IsDebuffSpell(spell_id) && !IsEscapeSpell(spell_id) && !IsHateReduxSpell(spell_id)) { valid = true; break; } break; - case BotSpellTypes::Cure: //DONE + case BotSpellTypes::Cure: if (IsCureSpell(spell_id)) { valid = true; break; } break; - case BotSpellTypes::PreCombatBuff: //DONE + case BotSpellTypes::PreCombatBuff: if ( IsBuffSpell(spell_id) && IsBeneficialSpell(spell_id) && @@ -2950,9 +2939,9 @@ void Bot::CheckBotSpells() { break; } break; - case BotSpellTypes::InCombatBuffSong: //DONE - case BotSpellTypes::OutOfCombatBuffSong: //DONE - case BotSpellTypes::PreCombatBuffSong: //DONE + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::OutOfCombatBuffSong: + case BotSpellTypes::PreCombatBuffSong: if ( IsBuffSpell(spell_id) && IsBeneficialSpell(spell_id) && @@ -2964,7 +2953,7 @@ void Bot::CheckBotSpells() { break; } break; - case BotSpellTypes::Fear: //DONE + case BotSpellTypes::Fear: if (IsFearSpell(spell_id)) { valid = true; break; @@ -2988,7 +2977,84 @@ void Bot::CheckBotSpells() { break; } break; - //TODO bot rewrite - add commanded types + case BotSpellTypes::Lull: + if (IsHarmonySpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::Teleport: + if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))) { + valid = true; + break; + } + break; + case BotSpellTypes::Succor: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Succor)) { + valid = true; + break; + } + break; + case BotSpellTypes::BindAffinity: + if (IsEffectInSpell(spell_id, SE_BindAffinity)) { + valid = true; + break; + } + break; + case BotSpellTypes::Identify: + if (IsEffectInSpell(spell_id, SE_Identify)) { + valid = true; + break; + } + break; + case BotSpellTypes::Levitate: + if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Levitate))) { + valid = true; + break; + } + break; + case BotSpellTypes::Rune: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune)) { + valid = true; + break; + } + break; + case BotSpellTypes::WaterBreathing: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_WaterBreathing)) { + valid = true; + break; + } + break; + case BotSpellTypes::Size: + if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))) { + valid = true; + break; + } + break; + case BotSpellTypes::Invisibility: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id)) { + valid = true; + break; + } + break; + case BotSpellTypes::MovementSpeed: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) { + valid = true; + break; + } + break; + case BotSpellTypes::SendHome: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_GateToHomeCity)) { + valid = true; + break; + } + break; + case BotSpellTypes::SummonCorpse: + if (IsEffectInSpell(spell_id, SE_SummonCorpse)) { + valid = true; + break; + } + break; default: break; @@ -2997,7 +3063,6 @@ void Bot::CheckBotSpells() { if (IsAnyNukeOrStunSpell(spell_id) && !IsEffectInSpell(spell_id, SE_Root) && !IsDebuffSpell(spell_id)) { correctType = BotSpellTypes::Nuke; } - //else if (IsAnyHealSpell(spell_id) && !IsEscapeSpell(spell_id) && (IsRegularPetHealSpell(spell_id) || !IsCureSpell(spell_id))) { else if (IsAnyHealSpell(spell_id) && !IsEscapeSpell(spell_id)) { correctType = BotSpellTypes::RegularHeal; } @@ -3024,11 +3089,15 @@ void Bot::CheckBotSpells() { } else if ( IsSelfConversionSpell(spell_id) || - IsAnyBuffSpell(spell_id) || + IsAnyBuffSpell(spell_id) + ) { + correctType = BotSpellTypes::InCombatBuff; + } + else if ( (IsEffectInSpell(spell_id, SE_Hate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_Hate)] > 0) || (IsEffectInSpell(spell_id, SE_InstantHate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_InstantHate)] > 0) ) { - correctType = BotSpellTypes::InCombatBuff; + correctType = BotSpellTypes::HateLine; } else if (IsMesmerizeSpell(spell_id)) { correctType = BotSpellTypes::Mez; @@ -3084,7 +3153,45 @@ void Bot::CheckBotSpells() { else if (IsEffectInSpell(spell_id, SE_Revive)) { correctType = BotSpellTypes::Resurrect; } - //TODO bot rewrite - add commanded types + else if (IsHarmonySpell(spell_id)) { + correctType = BotSpellTypes::Lull; + } + else if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))) { + correctType = BotSpellTypes::Teleport; + } + else if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Succor)) { + correctType = BotSpellTypes::Succor; + } + else if (IsEffectInSpell(spell_id, SE_BindAffinity)) { + correctType = BotSpellTypes::BindAffinity; + } + else if (IsEffectInSpell(spell_id, SE_Identify)) { + correctType = BotSpellTypes::Identify; + } + else if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Levitate))) { + correctType = BotSpellTypes::Levitate; + } + else if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune)) { + correctType = BotSpellTypes::Rune; + } + else if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_WaterBreathing)) { + correctType = BotSpellTypes::WaterBreathing; + } + else if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))) { + correctType = BotSpellTypes::Size; + } + else if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id)) { + correctType = BotSpellTypes::Invisibility; + } + else if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) { + correctType = BotSpellTypes::MovementSpeed; + } + else if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_GateToHomeCity)) { + correctType = BotSpellTypes::SendHome; + } + else if (IsEffectInSpell(spell_id, SE_SummonCorpse)) { + correctType = BotSpellTypes::SummonCorpse; + } if (!valid || (correctType == UINT16_MAX) || (s.type != correctType)) { LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]" diff --git a/zone/mob.cpp b/zone/mob.cpp index 4c1635655..6d04c9984 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -8898,6 +8898,9 @@ std::string Mob::GetSpellTypeNameByID(uint16 spellType) { case BotSpellTypes::PetResistBuffs: spellTypeName = "Pet Resist Buff"; break; + case BotSpellTypes::HateLine: + spellTypeName = "Hate Line"; + break; case BotSpellTypes::Lull: spellTypeName = "Lull"; break; @@ -9113,6 +9116,9 @@ std::string Mob::GetSpellTypeShortNameByID(uint16 spellType) { case BotSpellTypes::PetResistBuffs: spellTypeName = "petresistbuffs"; break; + case BotSpellTypes::HateLine: + spellTypeName = "hateline"; + break; case BotSpellTypes::Lull: spellTypeName = "lull"; break; @@ -9231,8 +9237,11 @@ bool Mob::GetDefaultSpellHold(uint16 spellType, uint8 stance) { case BotSpellTypes::AEFear: case BotSpellTypes::Fear: return true; + case BotSpellTypes::Mez: case BotSpellTypes::AEMez: + case BotSpellTypes::Debuff: case BotSpellTypes::AEDebuff: + case BotSpellTypes::Slow: case BotSpellTypes::AESlow: case BotSpellTypes::HateRedux: switch (stance) { @@ -9249,12 +9258,7 @@ bool Mob::GetDefaultSpellHold(uint16 spellType, uint8 stance) { case Stance::Assist: return true; default: - if (GetClass() == Class::Wizard) { - return true; - } - else { - return false; - } + return false; } case BotSpellTypes::InCombatBuffSong: case BotSpellTypes::OutOfCombatBuffSong: @@ -9265,6 +9269,55 @@ bool Mob::GetDefaultSpellHold(uint16 spellType, uint8 stance) { else { return true; } + case BotSpellTypes::HateLine: + if (GetClass() == Class::ShadowKnight || GetClass() == Class::Paladin) { + switch (stance) { + case Stance::Aggressive: + return false; + default: + return true; + } + } + else { + return true; + } + case BotSpellTypes::Cure: + case BotSpellTypes::GroupCures: + switch (stance) { + case Stance::Aggressive: + case Stance::AEBurn: + case Stance::Burn: + return true; + default: + return false; + } + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetHoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::RegularHeal: + switch (stance) { + case Stance::Aggressive: + case Stance::AEBurn: + case Stance::Burn: + return true; + default: + return false; + } + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::Pet: + case BotSpellTypes::Escape: + case BotSpellTypes::Lifetap: + case BotSpellTypes::Buff: + case BotSpellTypes::InCombatBuff: + case BotSpellTypes::PreCombatBuff: default: return false; } @@ -9487,6 +9540,7 @@ uint8 Mob::GetDefaultSpellMaxThreshold(uint16 spellType, uint8 stance) { case BotSpellTypes::PetResistBuffs: case BotSpellTypes::ResistBuffs: case BotSpellTypes::Resurrect: + case BotSpellTypes::HateLine: return 100; case BotSpellTypes::GroupHoTHeals: case BotSpellTypes::HoTHeals: diff --git a/zone/spells.cpp b/zone/spells.cpp index 1f80e3021..d24b5b7e4 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -803,7 +803,12 @@ bool Mob::DoCastingChecksOnTarget(bool check_on_casting, int32 spell_id, Mob *sp if (IsGroupSpell(spell_id)) { return true; } else if (spells[spell_id].target_type == ST_Self) { - spell_target = this; + if (IsBot() && (IsEffectInSpell(spell_id, SE_SummonCorpse) && RuleB(Bots, AllowCommandedSummonCorpse))) { + //spell_target = this; + } + else { + spell_target = this; + } } } else { if (IsGroupSpell(spell_id) && spell_target != this) { @@ -1951,7 +1956,13 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce // single target spells case ST_Self: { - spell_target = this; + if (IsBot() && (IsEffectInSpell(spell_id, SE_SummonCorpse) && RuleB(Bots, AllowCommandedSummonCorpse))) { + //spell_target = this; + } + else { + spell_target = this; + } + CastAction = SingleTarget; break; } From f6cb63a89bf67c1dee3bef14b3efb8cc591a6275 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 27 Nov 2024 19:02:37 -0600 Subject: [PATCH 39/97] more command cleanup --- zone/bot_command.cpp | 60 ++---- zone/bot_command.h | 6 +- zone/bot_commands/cast.cpp | 2 +- zone/bot_commands/copy_settings.cpp | 2 +- zone/bot_commands/default_settings.cpp | 2 +- zone/bot_commands/lull.cpp | 43 ---- zone/bot_commands/spell_aggro_checks.cpp | 2 +- zone/bot_commands/spell_delays.cpp | 2 +- zone/bot_commands/spell_engaged_priority.cpp | 2 +- zone/bot_commands/spell_holds.cpp | 2 +- zone/bot_commands/spell_idle_priority.cpp | 2 +- zone/bot_commands/spell_max_hp_pct.cpp | 2 +- zone/bot_commands/spell_max_mana_pct.cpp | 2 +- zone/bot_commands/spell_max_thresholds.cpp | 2 +- zone/bot_commands/spell_min_hp_pct.cpp | 2 +- zone/bot_commands/spell_min_mana_pct.cpp | 2 +- zone/bot_commands/spell_min_thresholds.cpp | 2 +- zone/bot_commands/spell_pursue_priority.cpp | 2 +- zone/bot_commands/spell_target_count.cpp | 2 +- zone/bot_commands/summon_corpse.cpp | 47 ----- zone/client.cpp | 60 ++++++ zone/client.h | 1 + zone/command.cpp | 2 +- zone/command.h | 2 +- zone/gm_commands/spell_delays.cpp | 197 ++++++++---------- zone/gm_commands/spell_holds.cpp | 198 ++++++++----------- zone/gm_commands/spell_max_thresholds.cpp | 198 ++++++++----------- zone/gm_commands/spell_min_thresholds.cpp | 198 ++++++++----------- 28 files changed, 413 insertions(+), 631 deletions(-) delete mode 100644 zone/bot_commands/lull.cpp delete mode 100644 zone/bot_commands/summon_corpse.cpp diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 7af4109f5..650f4a5e8 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1325,7 +1325,6 @@ int bot_command_init(void) bot_command_add("inventoryremove", "Removes an item from a bot's inventory", AccountStatus::Player, bot_command_inventory_remove) || bot_command_add("inventorywindow", "Displays all items in a bot's inventory in a pop-up window", AccountStatus::Player, bot_command_inventory_window) || bot_command_add("itemuse", "Elicits a report from spawned bots that can use the item on your cursor (option 'empty' yields only empty slots)", AccountStatus::Player, bot_command_item_use) || - bot_command_add("lull", "Orders a bot to cast a pacification spell", AccountStatus::Player, bot_command_lull) || //TODO bot rewrite - IMPLEMENT bot_command_add("maxmeleerange", "Toggles whether your bot is at max melee range or not. This will disable all special abilities, including taunt.", AccountStatus::Player, bot_command_max_melee_range) || bot_command_add("owneroption", "Sets options available to bot owners", AccountStatus::Player, bot_command_owner_option) || bot_command_add("pet", "Lists the available bot pet [subcommands]", AccountStatus::Player, bot_command_pet) || @@ -1362,7 +1361,6 @@ int bot_command_init(void) bot_command_add("spellsettingsupdate", "Update a bot spell setting entry", AccountStatus::Player, bot_command_spell_settings_update) || bot_command_add("spelltypeids", "Lists spelltypes by ID", AccountStatus::Player, bot_command_spelltype_ids) || bot_command_add("spelltypenames", "Lists spelltypes by shortname", AccountStatus::Player, bot_command_spelltype_names) || - bot_command_add("summoncorpse", "Orders a bot to summon a corpse to its feet", AccountStatus::Player, bot_command_summon_corpse) || //TODO bot rewrite - IMPLEMENT bot_command_add("suspend", "Suspends a bot's AI processing until released", AccountStatus::Player, bot_command_suspend) || bot_command_add("taunt", "Toggles taunt use by a bot", AccountStatus::Player, bot_command_taunt) || bot_command_add("timer", "Checks or clears timers of the chosen type.", AccountStatus::GMMgmt, bot_command_timer) || @@ -2102,56 +2100,13 @@ bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::Sp return false; } -void SendSpellTypePrompts(Client *c, bool commandedTypes) { - c->Message( - Chat::Yellow, - fmt::format( - "You can view spell types by ID or shortname: {}, {}, {} / {}, {}, {}", - Saylink::Silent( - fmt::format("^spelltypeids 0-19"), "ID 0-19" - ), - Saylink::Silent( - fmt::format("^spelltypeids 20-39"), "20-39" - ), - Saylink::Silent( - fmt::format("^spelltypeids 40+"), "40+" - ), - Saylink::Silent( - fmt::format("^spelltypenames 0-19"), "Shortname 0-19" - ), - Saylink::Silent( - fmt::format("^spelltypenames 20-39"), "20-39" - ), - Saylink::Silent( - fmt::format("^spelltypenames 40+"), "40+" - ) - ).c_str() - ); - - if (commandedTypes) { - c->Message( - Chat::Yellow, - fmt::format( - "You can view commanded spell types by ID or shortname: {} / {}", - Saylink::Silent( - fmt::format("^spelltypeids commanded"), "ID" - ), - Saylink::Silent( - fmt::format("^spelltypenames commanded"), "Shortname" - ) - ).c_str() - ); - } - - return; -} - -void SendSpellTypeWindow(Client *c, const Seperator* sep) { +void SendSpellTypeWindow(Client* c, const Seperator* sep) { std::string arg0 = sep->arg[0]; std::string arg1 = sep->arg[1]; uint8 minCount = 0; uint8 maxCount = 0; + bool clientOnly = false; if (BotSpellTypes::END <= 19) { minCount = BotSpellTypes::START; @@ -2173,6 +2128,11 @@ void SendSpellTypeWindow(Client *c, const Seperator* sep) { minCount = BotSpellTypes::COMMANDED_START; maxCount = BotSpellTypes::COMMANDED_END; } + else if (!arg1.compare("client")) { + minCount = BotSpellTypes::START; + maxCount = BotSpellTypes::END; + clientOnly = true; + } else { c->Message(Chat::Yellow, "You must choose a valid range option"); @@ -2222,6 +2182,10 @@ void SendSpellTypeWindow(Client *c, const Seperator* sep) { ); for (int i = minCount; i <= maxCount; ++i) { + if (clientOnly && !IsClientBotSpellType(i)) { + continue; + } + popup_text += DialogueWindow::TableRow( DialogueWindow::TableCell( fmt::format( @@ -2270,7 +2234,6 @@ void SendSpellTypeWindow(Client *c, const Seperator* sep) { #include "bot_commands/illusion_block.cpp" #include "bot_commands/inventory.cpp" #include "bot_commands/item_use.cpp" -#include "bot_commands/lull.cpp" #include "bot_commands/max_melee_range.cpp" #include "bot_commands/name.cpp" #include "bot_commands/owner_option.cpp" @@ -2299,7 +2262,6 @@ void SendSpellTypeWindow(Client *c, const Seperator* sep) { #include "bot_commands/spell_target_count.cpp" #include "bot_commands/spelltypes.cpp" #include "bot_commands/summon.cpp" -#include "bot_commands/summon_corpse.cpp" #include "bot_commands/suspend.cpp" #include "bot_commands/taunt.cpp" #include "bot_commands/timer.cpp" diff --git a/zone/bot_command.h b/zone/bot_command.h index 56be99680..ecce9485d 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -1688,7 +1688,6 @@ void bot_command_hold(Client *c, const Seperator *sep); void bot_command_illusion_block(Client* c, const Seperator* sep); void bot_command_inventory(Client *c, const Seperator *sep); void bot_command_item_use(Client *c, const Seperator *sep); -void bot_command_lull(Client *c, const Seperator *sep); void bot_command_max_melee_range(Client* c, const Seperator* sep); void bot_command_owner_option(Client *c, const Seperator *sep); void bot_command_pet(Client *c, const Seperator *sep); @@ -1723,7 +1722,6 @@ void bot_command_spelltype_ids(Client* c, const Seperator* sep); void bot_command_spelltype_names(Client* c, const Seperator* sep); void bot_spell_info_dialogue_window(Client* c, const Seperator *sep); void bot_command_enforce_spell_list(Client* c, const Seperator* sep); -void bot_command_summon_corpse(Client *c, const Seperator *sep); void bot_command_suspend(Client *c, const Seperator *sep); void bot_command_taunt(Client *c, const Seperator *sep); void bot_command_timer(Client* c, const Seperator* sep); @@ -1807,6 +1805,6 @@ void helper_send_available_subcommands(Client *bot_owner, const char* command_si void helper_send_usage_required_bots(Client *bot_owner, BCEnum::SpType spell_type, uint8 bot_class = Class::None); bool helper_spell_check_fail(STBaseEntry* local_entry); bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::SpType spell_type); -void SendSpellTypePrompts(Client *c, bool commandedTypes = false); -void SendSpellTypeWindow(Client *c, const Seperator* sep); +void SendSpellTypeWindow(Client* c, const Seperator* sep); + #endif diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index 10238aac2..eb1361d8d 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -94,7 +94,7 @@ void bot_command_cast(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c, true); + c->SendSpellTypePrompts(true); c->Message( Chat::Yellow, diff --git a/zone/bot_commands/copy_settings.cpp b/zone/bot_commands/copy_settings.cpp index 8e41f971a..359ac8c22 100644 --- a/zone/bot_commands/copy_settings.cpp +++ b/zone/bot_commands/copy_settings.cpp @@ -97,7 +97,7 @@ void bot_command_copy_settings(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/default_settings.cpp b/zone/bot_commands/default_settings.cpp index 8ed6733a6..b12e183b4 100644 --- a/zone/bot_commands/default_settings.cpp +++ b/zone/bot_commands/default_settings.cpp @@ -91,7 +91,7 @@ void bot_command_default_settings(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/lull.cpp b/zone/bot_commands/lull.cpp deleted file mode 100644 index d39f534d1..000000000 --- a/zone/bot_commands/lull.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "../bot_command.h" - -void bot_command_lull(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Lull]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Lull) || helper_command_alias_fail(c, "bot_command_lull", sep->arg[0], "lull")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Lull); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, ENEMY); - if (!target_mob) - continue; - - //if (spells[local_entry->spell_id].max[EFFECTIDTOINDEX(3)] && spells[local_entry->spell_id].max[EFFECTIDTOINDEX(3)] < target_mob->GetLevel()) - // continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - uint32 dont_root_before = 0; - if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before)) - target_mob->SetDontRootMeBefore(dont_root_before); - - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/spell_aggro_checks.cpp b/zone/bot_commands/spell_aggro_checks.cpp index cbcc4706b..0be650d69 100644 --- a/zone/bot_commands/spell_aggro_checks.cpp +++ b/zone/bot_commands/spell_aggro_checks.cpp @@ -92,7 +92,7 @@ void bot_command_spell_aggro_checks(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/spell_delays.cpp b/zone/bot_commands/spell_delays.cpp index 9fd37b70a..d1fb2d664 100644 --- a/zone/bot_commands/spell_delays.cpp +++ b/zone/bot_commands/spell_delays.cpp @@ -98,7 +98,7 @@ void bot_command_spell_delays(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/spell_engaged_priority.cpp b/zone/bot_commands/spell_engaged_priority.cpp index f27387b59..2c5208d14 100644 --- a/zone/bot_commands/spell_engaged_priority.cpp +++ b/zone/bot_commands/spell_engaged_priority.cpp @@ -96,7 +96,7 @@ void bot_command_spell_engaged_priority(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/spell_holds.cpp b/zone/bot_commands/spell_holds.cpp index e30911bd4..d83b5369b 100644 --- a/zone/bot_commands/spell_holds.cpp +++ b/zone/bot_commands/spell_holds.cpp @@ -82,7 +82,7 @@ void bot_command_spell_holds(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/spell_idle_priority.cpp b/zone/bot_commands/spell_idle_priority.cpp index cdb741d44..9576d4a8f 100644 --- a/zone/bot_commands/spell_idle_priority.cpp +++ b/zone/bot_commands/spell_idle_priority.cpp @@ -96,7 +96,7 @@ void bot_command_spell_idle_priority(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/spell_max_hp_pct.cpp b/zone/bot_commands/spell_max_hp_pct.cpp index bcd3614a5..42c552cdb 100644 --- a/zone/bot_commands/spell_max_hp_pct.cpp +++ b/zone/bot_commands/spell_max_hp_pct.cpp @@ -92,7 +92,7 @@ void bot_command_spell_max_hp_pct(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/spell_max_mana_pct.cpp b/zone/bot_commands/spell_max_mana_pct.cpp index 84cb9abd9..da8ad9d36 100644 --- a/zone/bot_commands/spell_max_mana_pct.cpp +++ b/zone/bot_commands/spell_max_mana_pct.cpp @@ -92,7 +92,7 @@ void bot_command_spell_max_mana_pct(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/spell_max_thresholds.cpp b/zone/bot_commands/spell_max_thresholds.cpp index 385258ddd..ed3316c01 100644 --- a/zone/bot_commands/spell_max_thresholds.cpp +++ b/zone/bot_commands/spell_max_thresholds.cpp @@ -98,7 +98,7 @@ void bot_command_spell_max_thresholds(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/spell_min_hp_pct.cpp b/zone/bot_commands/spell_min_hp_pct.cpp index be6fe1b9e..3c512ab3a 100644 --- a/zone/bot_commands/spell_min_hp_pct.cpp +++ b/zone/bot_commands/spell_min_hp_pct.cpp @@ -92,7 +92,7 @@ void bot_command_spell_min_hp_pct(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/spell_min_mana_pct.cpp b/zone/bot_commands/spell_min_mana_pct.cpp index f4ffe1bad..43c37ed8c 100644 --- a/zone/bot_commands/spell_min_mana_pct.cpp +++ b/zone/bot_commands/spell_min_mana_pct.cpp @@ -92,7 +92,7 @@ void bot_command_spell_min_mana_pct(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/spell_min_thresholds.cpp b/zone/bot_commands/spell_min_thresholds.cpp index ffcecf8b7..4c1b11a3a 100644 --- a/zone/bot_commands/spell_min_thresholds.cpp +++ b/zone/bot_commands/spell_min_thresholds.cpp @@ -100,7 +100,7 @@ void bot_command_spell_min_thresholds(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/spell_pursue_priority.cpp b/zone/bot_commands/spell_pursue_priority.cpp index 272135422..864bc2ba9 100644 --- a/zone/bot_commands/spell_pursue_priority.cpp +++ b/zone/bot_commands/spell_pursue_priority.cpp @@ -96,7 +96,7 @@ void bot_command_spell_pursue_priority(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/spell_target_count.cpp b/zone/bot_commands/spell_target_count.cpp index ad1b897b8..066fe95cc 100644 --- a/zone/bot_commands/spell_target_count.cpp +++ b/zone/bot_commands/spell_target_count.cpp @@ -92,7 +92,7 @@ void bot_command_spell_target_count(Client* c, const Seperator* sep) popup_text = DialogueWindow::Table(popup_text); c->SendPopupToClient(sep->arg[0], popup_text.c_str()); - SendSpellTypePrompts(c); + c->SendSpellTypePrompts(); if (RuleB(Bots, SendClassRaceOnHelp)) { c->Message( diff --git a/zone/bot_commands/summon_corpse.cpp b/zone/bot_commands/summon_corpse.cpp deleted file mode 100644 index 2a7a5b80f..000000000 --- a/zone/bot_commands/summon_corpse.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "../bot_command.h" - -void bot_command_summon_corpse(Client *c, const Seperator *sep) -{ - // Same methodology as old command..but, does not appear to work... (note: didn't work there, either...) - - // temp - c->Message(Chat::White, "This command is currently unavailable..."); - return; - - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_SummonCorpse]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_SummonCorpse) || helper_command_alias_fail(c, "bot_command_summon_corpse", sep->arg[0], "summoncorpse")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_SummonCorpse); - return; - } - - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = ActionableTarget::AsSingle_ByPlayer(c); - if (!target_mob) - continue; - - if (spells[local_entry->spell_id].base_value[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/client.cpp b/zone/client.cpp index 9d54f0b3d..569f4986c 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -13338,3 +13338,63 @@ std::string Client::SplitCommandHelpText(std::vector msg, std::stri return returnText; } + +void Client::SendSpellTypePrompts(bool commandedTypes, bool clientOnlyTypes) { + if (clientOnlyTypes) { + Message( + Chat::Yellow, + fmt::format( + "You can view spell types by {} or {}.", + Saylink::Silent( + fmt::format("^spelltypeids client"), "ID" + ), + Saylink::Silent( + fmt::format("^spelltypenames client"), "Shortname" + ) + ).c_str() + ); + } + else { + Message( + Chat::Yellow, + fmt::format( + "You can view spell types by {}, {}, {} or by {}, {}, {}.", + Saylink::Silent( + fmt::format("^spelltypeids 0-19"), "ID 0-19" + ), + Saylink::Silent( + fmt::format("^spelltypeids 20-39"), "20-39" + ), + Saylink::Silent( + fmt::format("^spelltypeids 40+"), "40+" + ), + Saylink::Silent( + fmt::format("^spelltypenames 0-19"), "Shortname 0-19" + ), + Saylink::Silent( + fmt::format("^spelltypenames 20-39"), "20-39" + ), + Saylink::Silent( + fmt::format("^spelltypenames 40+"), "40+" + ) + ).c_str() + ); + } + + if (commandedTypes) { + Message( + Chat::Yellow, + fmt::format( + "You can view commanded spell types by {} or {}.", + Saylink::Silent( + fmt::format("^spelltypeids commanded"), "ID" + ), + Saylink::Silent( + fmt::format("^spelltypenames commanded"), "Shortname" + ) + ).c_str() + ); + } + + return; +} diff --git a/zone/client.h b/zone/client.h index 77cf9a574..2a090eeac 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1263,6 +1263,7 @@ public: ); std::string GetCommandHelpHeader(std::string color, std::string header); std::string SplitCommandHelpText(std::vector msg, std::string color, uint16 maxLength, bool secondColor = false, std::string secondaryColor = ""); + void SendSpellTypePrompts(bool commandedTypes = false, bool clientOnlyTypes = false); // Task System Methods void LoadClientTaskState(); diff --git a/zone/command.cpp b/zone/command.cpp index 21f57a709..8fea9152b 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -219,7 +219,7 @@ int command_init(void) command_add("spawneditmass", "[Search Criteria] [Edit Option] [Edit Value] [Apply] Mass editing spawn command (Apply is optional, 0 = False, 1 = True, default is False)", AccountStatus::GMLeadAdmin, command_spawneditmass) || command_add("spawnfix", "Find targeted NPC in database based on its X/Y/heading and update the database to make it spawn at your current location/heading.", AccountStatus::GMAreas, command_spawnfix) || command_add("spelldelays", "Controls the delay between casts for a specific spell type", AccountStatus::Player, command_spell_delays) || - command_add("spellholds", "Controls whether a bot holds the specified spell type or not", AccountStatus::Player, command_spell_holds) || + //command_add("spellholds", "Controls whether a bot holds the specified spell type or not", AccountStatus::Player, command_spell_holds) || //currently unusued command_add("spellmaxthresholds", "Controls the minimum target HP threshold for a spell to be cast for a specific type", AccountStatus::Player, command_spell_max_thresholds) || command_add("spellminthresholds", "Controls the maximum target HP threshold for a spell to be cast for a specific type", AccountStatus::Player, command_spell_min_thresholds) || command_add("stun", "[duration] - Stuns you or your target for duration", AccountStatus::GMAdmin, command_stun) || diff --git a/zone/command.h b/zone/command.h index aa5771da9..39fc87e17 100644 --- a/zone/command.h +++ b/zone/command.h @@ -172,7 +172,7 @@ void command_spawn(Client *c, const Seperator *sep); void command_spawneditmass(Client *c, const Seperator *sep); void command_spawnfix(Client *c, const Seperator *sep); void command_spell_delays(Client* c, const Seperator* sep); -void command_spell_holds(Client* c, const Seperator* sep); +//void command_spell_holds(Client* c, const Seperator* sep); //currently unusued void command_spell_max_thresholds(Client* c, const Seperator* sep); void command_spell_min_thresholds(Client* c, const Seperator* sep); void command_stun(Client *c, const Seperator *sep); diff --git a/zone/gm_commands/spell_delays.cpp b/zone/gm_commands/spell_delays.cpp index 09b2b0c5d..f7fda40ee 100644 --- a/zone/gm_commands/spell_delays.cpp +++ b/zone/gm_commands/spell_delays.cpp @@ -7,101 +7,85 @@ void command_spell_delays(Client* c, const Seperator* sep) const bool is_help = !strcasecmp(sep->arg[1], "help"); if (is_help) { - c->Message(Chat::White, "usage: %s [spelltype ID | spelltype Shortname] [current | value: 0-1].", sep->arg[0]); - c->Message(Chat::White, "example: [%s 15 4000] or [%s cures 4000] would allow bots to cast cures on you every 4 seconds.", sep->arg[0], sep->arg[0]); - c->Message(Chat::White, "note: Use [current] to check your current setting."); - c->Message( - Chat::White, + std::vector description = + { + "Controls how often bots can cast certain spell types on you" + }; + + std::vector notes = + { + "- All pet types are control your how your pet will be affected" + }; + + std::vector example_format = + { fmt::format( - "note: Use {} for a list of spell types by ID or {} for a list of spell types by short name.", - Saylink::Silent( - fmt::format("{} listid", sep->arg[0]) - ), - Saylink::Silent( - fmt::format("{} listname", sep->arg[0]) - ) - ).c_str() + "{} [Type Shortname] [value]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set Very Fast Heals to be received every 1 second:", + fmt::format( + "{} {} 1000", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::VeryFastHeals) + ), + fmt::format( + "{} {} 1000", + sep->arg[0], + BotSpellTypes::VeryFastHeals + ) + }; + std::vector examples_two = + { + "To check your current Regular Heal delay:", + fmt::format( + "{} {} current", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::RegularHeal) + ), + fmt::format( + "{} {} current", + sep->arg[0], + BotSpellTypes::RegularHeal + ) + }; + std::vector examples_three = { }; + + std::vector actionables = { }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three ); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(false, true); + return; } } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - const std::string& color_red = "red_1"; - const std::string& color_blue = "royal_blue"; - const std::string& color_green = "forest_green"; - const std::string& bright_green = "green"; - const std::string& bright_red = "red"; - const std::string& heroic_color = "gold"; - - std::string fillerLine = "-----------"; - std::string spellTypeField = "Spell Type"; - std::string pluralS = "s"; - std::string idField = "ID"; - std::string shortnameField = "Short Name"; - - std::string popup_text = DialogueWindow::TableRow( - DialogueWindow::TableCell( - fmt::format( - "{}", - DialogueWindow::ColorMessage(bright_green, spellTypeField) - ) - ) + - DialogueWindow::TableCell( - fmt::format( - "{}", - (!arg1.compare("listid") ? DialogueWindow::ColorMessage(bright_green, idField) : DialogueWindow::ColorMessage(bright_green, shortnameField)) - ) - ) - ); - - popup_text += DialogueWindow::TableRow( - DialogueWindow::TableCell( - fmt::format( - "{}", - DialogueWindow::ColorMessage(heroic_color, fillerLine) - ) - ) + - DialogueWindow::TableCell( - fmt::format( - "{}", - DialogueWindow::ColorMessage(heroic_color, fillerLine) - ) - ) - ); - - for (int i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - if (!IsClientBotSpellType(i)) { - continue; - } - - popup_text += DialogueWindow::TableRow( - DialogueWindow::TableCell( - fmt::format( - "{}{}", - DialogueWindow::ColorMessage(color_green, c->GetSpellTypeNameByID(i)), - DialogueWindow::ColorMessage(color_green, pluralS) - ) - ) + - DialogueWindow::TableCell( - fmt::format( - "{}", - (!arg1.compare("listid") ? DialogueWindow::ColorMessage(color_blue, std::to_string(i)) : DialogueWindow::ColorMessage(color_blue, c->GetSpellTypeShortNameByID(i))) - ) - ) - ); - } - - popup_text = DialogueWindow::Table(popup_text); - - c->SendPopupToClient("Spell Types", popup_text.c_str()); - - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; @@ -112,18 +96,8 @@ void command_spell_delays(Client* c, const Seperator* sep) spellType = atoi(sep->arg[1]); if (!IsClientBotSpellType(spellType)) { - c->Message( - Chat::White, - fmt::format( - "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", - Saylink::Silent( - fmt::format("{} listid", sep->arg[0]) - ), - Saylink::Silent( - fmt::format("{} listname", sep->arg[0]) - ) - ).c_str() - ); + c->Message(Chat::Yellow, "Invalid spell type."); + c->SendSpellTypePrompts(false, true); return; } @@ -133,20 +107,8 @@ void command_spell_delays(Client* c, const Seperator* sep) spellType = c->GetSpellTypeIDByShortName(arg1); if (!IsClientBotSpellType(spellType)) { - c->Message( - Chat::White, - fmt::format( - "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", - Saylink::Silent( - fmt::format("{} listid", sep->arg[0]) - ), - Saylink::Silent( - fmt::format("{} listname", sep->arg[0]) - ) - ).c_str() - ); - - return; + c->Message(Chat::Yellow, "Invalid spell type."); + c->SendSpellTypePrompts(false, true); } } else { @@ -164,10 +126,11 @@ void command_spell_delays(Client* c, const Seperator* sep) } } + // Enable/Disable/Current checks if (sep->IsNumber(2)) { typeValue = atoi(sep->arg[2]); ++ab_arg; - if (typeValue < 1 || typeValue > 60000) { + if (typeValue < 0 || typeValue > 60000) { c->Message(Chat::Yellow, "You must enter a value between 1-60000 (1ms to 60s)."); return; @@ -195,18 +158,18 @@ void command_spell_delays(Client* c, const Seperator* sep) c->Message( Chat::Green, fmt::format( - "Your current {} delay is {} seconds.", + "Your [{}] delay is currently {} seconds.'", c->GetSpellTypeNameByID(spellType), c->GetSpellDelay(spellType) / 1000.00 ).c_str() ); } else { - c->SetSpellDelay(spellType, typeValue); + c->SetSpellHold(spellType, typeValue); c->Message( Chat::Green, fmt::format( - "Your {} delay was set to {} seconds.", + "Your [{}] delay was set to {} seconds.'", c->GetSpellTypeNameByID(spellType), c->GetSpellDelay(spellType) / 1000.00 ).c_str() diff --git a/zone/gm_commands/spell_holds.cpp b/zone/gm_commands/spell_holds.cpp index 90a725dfb..c9c3641d7 100644 --- a/zone/gm_commands/spell_holds.cpp +++ b/zone/gm_commands/spell_holds.cpp @@ -2,108 +2,94 @@ void command_spell_holds(Client *c, const Seperator *sep) { + //unused for clients + c->Message(Chat::Yellow, "Spell Holds for players is currently unused."); + return; + const int arguments = sep->argnum; if (arguments) { const bool is_help = !strcasecmp(sep->arg[1], "help"); if (is_help) { - c->Message(Chat::White, "usage: %s [spelltype ID | spelltype Shortname] [current | value: 0-1].", sep->arg[0]); - c->Message(Chat::White, "example: [%s 15 1] or [%s cures 1] would prevent bots from casting cures on you.", sep->arg[0], sep->arg[0]); - c->Message(Chat::White, "note: Use [current] to check your current setting."); - c->Message(Chat::White, "note: Set to 0 to unhold the given spell type."); - c->Message(Chat::White, "note: Set to 1 to hold the given spell type."); - c->Message( - Chat::White, + std::vector description = + { + "Toggles whether or not bots can cast certain spell types on you" + }; + + std::vector notes = + { + "- All pet types are control your how your pet will be affected" + }; + + std::vector example_format = + { fmt::format( - "note: Use {} for a list of spell types by ID or {} for a list of spell types by short name.", - Saylink::Silent( - fmt::format("{} listid", sep->arg[0]) - ), - Saylink::Silent( - fmt::format("{} listname", sep->arg[0]) - ) - ).c_str() + "{} [Type Shortname] [value]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set DoTs to be held:", + fmt::format( + "{} {} 1", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::DOT) + ), + fmt::format( + "{} {} 1", + sep->arg[0], + BotSpellTypes::DOT + ) + }; + std::vector examples_two = + { + "To check your current DoT settings:", + fmt::format( + "{} {} current", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::DOT) + ), + fmt::format( + "{} {} current", + sep->arg[0], + BotSpellTypes::DOT + ) + }; + std::vector examples_three = { }; + + std::vector actionables = { }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three ); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(false, true); + return; } } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - const std::string& color_red = "red_1"; - const std::string& color_blue = "royal_blue"; - const std::string& color_green = "forest_green"; - const std::string& bright_green = "green"; - const std::string& bright_red = "red"; - const std::string& heroic_color = "gold"; - - std::string fillerLine = "-----------"; - std::string spellTypeField = "Spell Type"; - std::string pluralS = "s"; - std::string idField = "ID"; - std::string shortnameField = "Short Name"; - - std::string popup_text = DialogueWindow::TableRow( - DialogueWindow::TableCell( - fmt::format( - "{}", - DialogueWindow::ColorMessage(bright_green, spellTypeField) - ) - ) + - DialogueWindow::TableCell( - fmt::format( - "{}", - (!arg1.compare("listid") ? DialogueWindow::ColorMessage(bright_green, idField) : DialogueWindow::ColorMessage(bright_green, shortnameField)) - ) - ) - ); - - popup_text += DialogueWindow::TableRow( - DialogueWindow::TableCell( - fmt::format( - "{}", - DialogueWindow::ColorMessage(heroic_color, fillerLine) - ) - ) + - DialogueWindow::TableCell( - fmt::format( - "{}", - DialogueWindow::ColorMessage(heroic_color, fillerLine) - ) - ) - ); - - for (int i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - if (!IsClientBotSpellType(i)) { - continue; - } - - popup_text += DialogueWindow::TableRow( - DialogueWindow::TableCell( - fmt::format( - "{}{}", - DialogueWindow::ColorMessage(color_green, c->GetSpellTypeNameByID(i)), - DialogueWindow::ColorMessage(color_green, pluralS) - ) - ) + - DialogueWindow::TableCell( - fmt::format( - "{}", - (!arg1.compare("listid") ? DialogueWindow::ColorMessage(color_blue, std::to_string(i)) : DialogueWindow::ColorMessage(color_blue, c->GetSpellTypeShortNameByID(i))) - ) - ) - ); - } - - popup_text = DialogueWindow::Table(popup_text); - - c->SendPopupToClient("Spell Types", popup_text.c_str()); - - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; @@ -114,18 +100,8 @@ void command_spell_holds(Client *c, const Seperator *sep) spellType = atoi(sep->arg[1]); if (!IsClientBotSpellType(spellType)) { - c->Message( - Chat::White, - fmt::format( - "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", - Saylink::Silent( - fmt::format("{} listid", sep->arg[0]) - ), - Saylink::Silent( - fmt::format("{} listname", sep->arg[0]) - ) - ).c_str() - ); + c->Message(Chat::Yellow, "Invalid spell type."); + c->SendSpellTypePrompts(false, true); return; } @@ -135,20 +111,8 @@ void command_spell_holds(Client *c, const Seperator *sep) spellType = c->GetSpellTypeIDByShortName(arg1); if (!IsClientBotSpellType(spellType)) { - c->Message( - Chat::White, - fmt::format( - "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", - Saylink::Silent( - fmt::format("{} listid", sep->arg[0]) - ), - Saylink::Silent( - fmt::format("{} listname", sep->arg[0]) - ) - ).c_str() - ); - - return; + c->Message(Chat::Yellow, "Invalid spell type."); + c->SendSpellTypePrompts(false, true); } } else { @@ -198,7 +162,7 @@ void command_spell_holds(Client *c, const Seperator *sep) c->Message( Chat::Green, fmt::format( - "Your current Hold {}s status is {}.", + "Your [{}] spell hold is currently [{}].'", c->GetSpellTypeNameByID(spellType), c->GetSpellHold(spellType) ? "enabled" : "disabled" ).c_str() @@ -209,7 +173,7 @@ void command_spell_holds(Client *c, const Seperator *sep) c->Message( Chat::Green, fmt::format( - "Your Hold {}s status was {}.", + "Your [{}] spell hold was [{}].'", c->GetSpellTypeNameByID(spellType), c->GetSpellHold(spellType) ? "enabled" : "disabled" ).c_str() diff --git a/zone/gm_commands/spell_max_thresholds.cpp b/zone/gm_commands/spell_max_thresholds.cpp index a67660d2e..3e0188baa 100644 --- a/zone/gm_commands/spell_max_thresholds.cpp +++ b/zone/gm_commands/spell_max_thresholds.cpp @@ -7,101 +7,85 @@ void command_spell_max_thresholds(Client* c, const Seperator* sep) const bool is_help = !strcasecmp(sep->arg[1], "help"); if (is_help) { - c->Message(Chat::White, "usage: %s [spelltype ID | spelltype Shortname] [current | value: 0-1].", sep->arg[0]); - c->Message(Chat::White, "example: [%s 15 95] or [%s cures 95] would allow bots to cast cures on you when you are under 95%% health.", sep->arg[0], sep->arg[0]); - c->Message(Chat::White, "note: Use [current] to check your current setting."); - c->Message( - Chat::White, + std::vector description = + { + "Threshold of your own health when bots will start casting the chosen spell type" + }; + + std::vector notes = + { + "- All pet types are control your how your pet will be affected" + }; + + std::vector example_format = + { fmt::format( - "note: Use {} for a list of spell types by ID or {} for a list of spell types by short name.", - Saylink::Silent( - fmt::format("{} listid", sep->arg[0]) - ), - Saylink::Silent( - fmt::format("{} listname", sep->arg[0]) - ) - ).c_str() + "{} [Type Shortname] [value]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set Complete Heals to start at 90% health:", + fmt::format( + "{} {} 90", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::CompleteHeal) + ), + fmt::format( + "{} {} 90", + sep->arg[0], + BotSpellTypes::CompleteHeal + ) + }; + std::vector examples_two = + { + "To check your current HoT Heal settings:", + fmt::format( + "{} {} current", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::HoTHeals) + ), + fmt::format( + "{} {} current", + sep->arg[0], + BotSpellTypes::HoTHeals + ) + }; + std::vector examples_three = { }; + + std::vector actionables = { }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three ); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(false, true); + return; } } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - const std::string& color_red = "red_1"; - const std::string& color_blue = "royal_blue"; - const std::string& color_green = "forest_green"; - const std::string& bright_green = "green"; - const std::string& bright_red = "red"; - const std::string& heroic_color = "gold"; - - std::string fillerLine = "-----------"; - std::string spellTypeField = "Spell Type"; - std::string pluralS = "s"; - std::string idField = "ID"; - std::string shortnameField = "Short Name"; - - std::string popup_text = DialogueWindow::TableRow( - DialogueWindow::TableCell( - fmt::format( - "{}", - DialogueWindow::ColorMessage(bright_green, spellTypeField) - ) - ) + - DialogueWindow::TableCell( - fmt::format( - "{}", - (!arg1.compare("listid") ? DialogueWindow::ColorMessage(bright_green, idField) : DialogueWindow::ColorMessage(bright_green, shortnameField)) - ) - ) - ); - - popup_text += DialogueWindow::TableRow( - DialogueWindow::TableCell( - fmt::format( - "{}", - DialogueWindow::ColorMessage(heroic_color, fillerLine) - ) - ) + - DialogueWindow::TableCell( - fmt::format( - "{}", - DialogueWindow::ColorMessage(heroic_color, fillerLine) - ) - ) - ); - - for (int i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - if (!IsClientBotSpellType(i)) { - continue; - } - - popup_text += DialogueWindow::TableRow( - DialogueWindow::TableCell( - fmt::format( - "{}{}", - DialogueWindow::ColorMessage(color_green, c->GetSpellTypeNameByID(i)), - DialogueWindow::ColorMessage(color_green, pluralS) - ) - ) + - DialogueWindow::TableCell( - fmt::format( - "{}", - (!arg1.compare("listid") ? DialogueWindow::ColorMessage(color_blue, std::to_string(i)) : DialogueWindow::ColorMessage(color_blue, c->GetSpellTypeShortNameByID(i))) - ) - ) - ); - } - - popup_text = DialogueWindow::Table(popup_text); - - c->SendPopupToClient("Spell Types", popup_text.c_str()); - - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; @@ -112,18 +96,8 @@ void command_spell_max_thresholds(Client* c, const Seperator* sep) spellType = atoi(sep->arg[1]); if (!IsClientBotSpellType(spellType)) { - c->Message( - Chat::White, - fmt::format( - "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", - Saylink::Silent( - fmt::format("{} listid", sep->arg[0]) - ), - Saylink::Silent( - fmt::format("{} listname", sep->arg[0]) - ) - ).c_str() - ); + c->Message(Chat::Yellow, "Invalid spell type."); + c->SendSpellTypePrompts(false, true); return; } @@ -133,20 +107,8 @@ void command_spell_max_thresholds(Client* c, const Seperator* sep) spellType = c->GetSpellTypeIDByShortName(arg1); if (!IsClientBotSpellType(spellType)) { - c->Message( - Chat::White, - fmt::format( - "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", - Saylink::Silent( - fmt::format("{} listid", sep->arg[0]) - ), - Saylink::Silent( - fmt::format("{} listname", sep->arg[0]) - ) - ).c_str() - ); - - return; + c->Message(Chat::Yellow, "Invalid spell type."); + c->SendSpellTypePrompts(false, true); } } else { @@ -168,8 +130,8 @@ void command_spell_max_thresholds(Client* c, const Seperator* sep) if (sep->IsNumber(2)) { typeValue = atoi(sep->arg[2]); ++ab_arg; - if (typeValue < 0 || typeValue > 150) { - c->Message(Chat::Yellow, "You must enter a value between 0-150 (0%% to 150%% of health)."); + if (typeValue < 0 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of your health)."); return; } @@ -196,18 +158,18 @@ void command_spell_max_thresholds(Client* c, const Seperator* sep) c->Message( Chat::Green, fmt::format( - "Your current max threshold for {}s is {}%%.", + "Your [{}] maximum hold is currently [{}]%%.'", c->GetSpellTypeNameByID(spellType), c->GetSpellMaxThreshold(spellType) ).c_str() ); } else { - c->SetSpellMaxThreshold(spellType, typeValue); + c->SetSpellHold(spellType, typeValue); c->Message( Chat::Green, fmt::format( - "Your max threshold for {}s was set to {}%%.", + "Your [{}] maximum hold was set to [{}]%%.'", c->GetSpellTypeNameByID(spellType), c->GetSpellMaxThreshold(spellType) ).c_str() diff --git a/zone/gm_commands/spell_min_thresholds.cpp b/zone/gm_commands/spell_min_thresholds.cpp index 1b0fcfdf2..c2b3893d7 100644 --- a/zone/gm_commands/spell_min_thresholds.cpp +++ b/zone/gm_commands/spell_min_thresholds.cpp @@ -7,101 +7,85 @@ void command_spell_min_thresholds(Client* c, const Seperator* sep) const bool is_help = !strcasecmp(sep->arg[1], "help"); if (is_help) { - c->Message(Chat::White, "usage: %s [spelltype ID | spelltype Shortname] [current | value: 0-1].", sep->arg[0]); - c->Message(Chat::White, "example: [%s 15 15] or [%s cures 15] would prevent bots from casting cures on you when you are under 15%% health.", sep->arg[0], sep->arg[0]); - c->Message(Chat::White, "note: Use [current] to check your current setting."); - c->Message( - Chat::White, + std::vector description = + { + "Threshold of your own health when bots will stop casting the chosen spell type" + }; + + std::vector notes = + { + "- All pet types are control your how your pet will be affected" + }; + + std::vector example_format = + { fmt::format( - "note: Use {} for a list of spell types by ID or {} for a list of spell types by short name.", - Saylink::Silent( - fmt::format("{} listid", sep->arg[0]) - ), - Saylink::Silent( - fmt::format("{} listname", sep->arg[0]) - ) - ).c_str() + "{} [Type Shortname] [value]" + , sep->arg[0] + ), + fmt::format( + "{} [Type ID] [value]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To set Fast Heals to be stopped at 65% health:", + fmt::format( + "{} {} 65", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::FastHeals) + ), + fmt::format( + "{} {} 65", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + std::vector examples_two = + { + "To check your current Cure settings:", + fmt::format( + "{} {} current", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Cure) + ), + fmt::format( + "{} {} current", + sep->arg[0], + BotSpellTypes::Cure + ) + }; + std::vector examples_three = { }; + + std::vector actionables = { }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three ); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(false, true); + return; } } std::string arg1 = sep->arg[1]; - - if (!arg1.compare("listid") || !arg1.compare("listname")) { - const std::string& color_red = "red_1"; - const std::string& color_blue = "royal_blue"; - const std::string& color_green = "forest_green"; - const std::string& bright_green = "green"; - const std::string& bright_red = "red"; - const std::string& heroic_color = "gold"; - - std::string fillerLine = "-----------"; - std::string spellTypeField = "Spell Type"; - std::string pluralS = "s"; - std::string idField = "ID"; - std::string shortnameField = "Short Name"; - - std::string popup_text = DialogueWindow::TableRow( - DialogueWindow::TableCell( - fmt::format( - "{}", - DialogueWindow::ColorMessage(bright_green, spellTypeField) - ) - ) + - DialogueWindow::TableCell( - fmt::format( - "{}", - (!arg1.compare("listid") ? DialogueWindow::ColorMessage(bright_green, idField) : DialogueWindow::ColorMessage(bright_green, shortnameField)) - ) - ) - ); - - popup_text += DialogueWindow::TableRow( - DialogueWindow::TableCell( - fmt::format( - "{}", - DialogueWindow::ColorMessage(heroic_color, fillerLine) - ) - ) + - DialogueWindow::TableCell( - fmt::format( - "{}", - DialogueWindow::ColorMessage(heroic_color, fillerLine) - ) - ) - ); - - for (int i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - if (!IsClientBotSpellType(i)) { - continue; - } - - popup_text += DialogueWindow::TableRow( - DialogueWindow::TableCell( - fmt::format( - "{}{}", - DialogueWindow::ColorMessage(color_green, c->GetSpellTypeNameByID(i)), - DialogueWindow::ColorMessage(color_green, pluralS) - ) - ) + - DialogueWindow::TableCell( - fmt::format( - "{}", - (!arg1.compare("listid") ? DialogueWindow::ColorMessage(color_blue, std::to_string(i)) : DialogueWindow::ColorMessage(color_blue, c->GetSpellTypeShortNameByID(i))) - ) - ) - ); - } - - popup_text = DialogueWindow::Table(popup_text); - - c->SendPopupToClient("Spell Types", popup_text.c_str()); - - return; - } - std::string arg2 = sep->arg[2]; int ab_arg = 2; bool current_check = false; @@ -112,18 +96,8 @@ void command_spell_min_thresholds(Client* c, const Seperator* sep) spellType = atoi(sep->arg[1]); if (!IsClientBotSpellType(spellType)) { - c->Message( - Chat::White, - fmt::format( - "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", - Saylink::Silent( - fmt::format("{} listid", sep->arg[0]) - ), - Saylink::Silent( - fmt::format("{} listname", sep->arg[0]) - ) - ).c_str() - ); + c->Message(Chat::Yellow, "Invalid spell type."); + c->SendSpellTypePrompts(false, true); return; } @@ -133,20 +107,8 @@ void command_spell_min_thresholds(Client* c, const Seperator* sep) spellType = c->GetSpellTypeIDByShortName(arg1); if (!IsClientBotSpellType(spellType)) { - c->Message( - Chat::White, - fmt::format( - "You must choose a valid spell type. Use {} for a list of spell types by ID or {} for a list of spell types by short name.", - Saylink::Silent( - fmt::format("{} listid", sep->arg[0]) - ), - Saylink::Silent( - fmt::format("{} listname", sep->arg[0]) - ) - ).c_str() - ); - - return; + c->Message(Chat::Yellow, "Invalid spell type."); + c->SendSpellTypePrompts(false, true); } } else { @@ -168,8 +130,8 @@ void command_spell_min_thresholds(Client* c, const Seperator* sep) if (sep->IsNumber(2)) { typeValue = atoi(sep->arg[2]); ++ab_arg; - if (typeValue < 0 || typeValue > 150) { - c->Message(Chat::Yellow, "You must enter a value between 0-150 (0%% to 150%% of health)."); + if (typeValue < 0 || typeValue > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of your health)."); return; } @@ -196,18 +158,18 @@ void command_spell_min_thresholds(Client* c, const Seperator* sep) c->Message( Chat::Green, fmt::format( - "Your current min threshold for {}s is {}%%.", + "Your [{}] minimum hold is currently [{}]%%.'", c->GetSpellTypeNameByID(spellType), c->GetSpellMinThreshold(spellType) ).c_str() ); } else { - c->SetSpellMinThreshold(spellType, typeValue); + c->SetSpellHold(spellType, typeValue); c->Message( Chat::Green, fmt::format( - "Your min threshold for {}s was set to {}%%.", + "Your [{}] minimum hold was set to [{}]%%.'", c->GetSpellTypeNameByID(spellType), c->GetSpellMinThreshold(spellType) ).c_str() From df6c6c3ea0ab3ecc3e3c94b7880a25b27b92fd36 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:50:21 -0600 Subject: [PATCH 40/97] add aehateline spell type --- common/spdat.cpp | 1 + common/spdat.h | 3 ++- zone/bot.cpp | 49 ++++++++++++++++++++++---------------- zone/bot_commands/cast.cpp | 2 +- zone/botspellsai.cpp | 1 + zone/mob.cpp | 8 +++++++ 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index c4086c0ac..bef0507df 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -2844,6 +2844,7 @@ bool BOT_SPELL_TYPES_DETRIMENTAL(uint16 spellType, uint8 cls) { case BotSpellTypes::PBAENuke: case BotSpellTypes::Lull: case BotSpellTypes::HateLine: + case BotSpellTypes::AEHateLine: return true; default: return false; diff --git a/common/spdat.h b/common/spdat.h index 1c3bcf52c..3f87672b4 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -709,6 +709,7 @@ namespace BotSpellTypes constexpr uint16 PetDamageShields = 53; constexpr uint16 PetResistBuffs = 54; constexpr uint16 HateLine = 55; + constexpr uint16 AEHateLine = 56; // Command Spell Types constexpr uint16 Teleport = 100; // this is handled by ^depart so uses other logic @@ -726,7 +727,7 @@ namespace BotSpellTypes constexpr uint16 SummonCorpse = 112; constexpr uint16 START = BotSpellTypes::Nuke; // Do not remove or change this - constexpr uint16 END = BotSpellTypes::HateLine; // Do not remove this, increment as needed + constexpr uint16 END = BotSpellTypes::AEHateLine; // Do not remove this, increment as needed constexpr uint16 COMMANDED_START = BotSpellTypes::Lull; // Do not remove or change this constexpr uint16 COMMANDED_END = BotSpellTypes::SummonCorpse; // Do not remove this, increment as needed } diff --git a/zone/bot.cpp b/zone/bot.cpp index fedaa3264..4b632b7be 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -10576,48 +10576,50 @@ uint16 Bot::GetDefaultSpellTypeEngagedPriority(uint16 spellType, uint8 botClass, return 20; case BotSpellTypes::Mez: return 21; - case BotSpellTypes::HateLine: + case BotSpellTypes::AEHateLine: return 22; - case BotSpellTypes::AEDispel: + case BotSpellTypes::HateLine: return 23; - case BotSpellTypes::Dispel: + case BotSpellTypes::AEDispel: return 24; - case BotSpellTypes::AEDebuff: + case BotSpellTypes::Dispel: return 25; - case BotSpellTypes::Debuff: + case BotSpellTypes::AEDebuff: return 26; - case BotSpellTypes::AESnare: + case BotSpellTypes::Debuff: return 27; - case BotSpellTypes::Snare: + case BotSpellTypes::AESnare: return 28; - case BotSpellTypes::AESlow: + case BotSpellTypes::Snare: return 29; - case BotSpellTypes::Slow: + case BotSpellTypes::AESlow: return 30; - case BotSpellTypes::AERoot: + case BotSpellTypes::Slow: return 31; - case BotSpellTypes::Root: + case BotSpellTypes::AERoot: return 32; - case BotSpellTypes::AEDoT: + case BotSpellTypes::Root: return 33; - case BotSpellTypes::DOT: + case BotSpellTypes::AEDoT: return 34; - case BotSpellTypes::AEStun: + case BotSpellTypes::DOT: return 35; - case BotSpellTypes::PBAENuke: + case BotSpellTypes::AEStun: return 36; - case BotSpellTypes::AENukes: + case BotSpellTypes::PBAENuke: return 37; - case BotSpellTypes::AERains: + case BotSpellTypes::AENukes: return 38; - case BotSpellTypes::Stun: + case BotSpellTypes::AERains: return 39; - case BotSpellTypes::Nuke: + case BotSpellTypes::Stun: return 40; - case BotSpellTypes::InCombatBuff: + case BotSpellTypes::Nuke: return 41; - case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::InCombatBuff: return 42; + case BotSpellTypes::InCombatBuffSong: + return 43; default: return 0; } @@ -11027,6 +11029,7 @@ uint16 Bot::GetSpellListSpellType(uint16 spellType) { case BotSpellTypes::Escape: case BotSpellTypes::HateRedux: case BotSpellTypes::HateLine: + case BotSpellTypes::AEHateLine: case BotSpellTypes::InCombatBuff: case BotSpellTypes::InCombatBuffSong: case BotSpellTypes::OutOfCombatBuffSong: @@ -11404,6 +11407,10 @@ bool Bot::HasValidAETarget(Bot* botCaster, uint16 spell_id, uint16 spellType, Mo } break; + case BotSpellTypes::AEHateLine: + if (!m->IsNPC()) { + continue; + } default: break; } diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index eb1361d8d..fb7f97209 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -407,7 +407,7 @@ void bot_command_cast(Client* c, const Seperator* sep) TODO bot rewrite - FIX: Depart Group Cures, Precombat - NEED TO CHECK: precombat, AE Dispel, AE Lifetap + NEED TO CHECK: precombat */ if (bot_iter->GetBotStance() == Stance::Passive || bot_iter->GetHoldFlag() || bot_iter->GetAppearance() == eaDead || bot_iter->IsFeared() || bot_iter->IsSilenced() || bot_iter->IsAmnesiad() || bot_iter->GetHP() < 0) { continue; diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index d3123bf86..35cec06d1 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -2076,6 +2076,7 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spellType) case BotSpellTypes::AELifetap: case BotSpellTypes::AERoot: case BotSpellTypes::PBAENuke: + case BotSpellTypes::AEHateLine: return RuleI(Bots, PercentChanceToCastAEs); case BotSpellTypes::GroupHeals: case BotSpellTypes::GroupCompleteHeals: diff --git a/zone/mob.cpp b/zone/mob.cpp index 6d04c9984..a455932e6 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -8901,6 +8901,9 @@ std::string Mob::GetSpellTypeNameByID(uint16 spellType) { case BotSpellTypes::HateLine: spellTypeName = "Hate Line"; break; + case BotSpellTypes::AEHateLine: + spellTypeName = "AE Hate Line"; + break; case BotSpellTypes::Lull: spellTypeName = "Lull"; break; @@ -9119,6 +9122,9 @@ std::string Mob::GetSpellTypeShortNameByID(uint16 spellType) { case BotSpellTypes::HateLine: spellTypeName = "hateline"; break; + case BotSpellTypes::AEHateLine: + spellTypeName = "aehateline"; + break; case BotSpellTypes::Lull: spellTypeName = "lull"; break; @@ -9236,6 +9242,7 @@ bool Mob::GetDefaultSpellHold(uint16 spellType, uint8 stance) { case BotSpellTypes::Dispel: case BotSpellTypes::AEFear: case BotSpellTypes::Fear: + case BotSpellTypes::AEHateLine: return true; case BotSpellTypes::Mez: case BotSpellTypes::AEMez: @@ -9541,6 +9548,7 @@ uint8 Mob::GetDefaultSpellMaxThreshold(uint16 spellType, uint8 stance) { case BotSpellTypes::ResistBuffs: case BotSpellTypes::Resurrect: case BotSpellTypes::HateLine: + case BotSpellTypes::AEHateLine: return 100; case BotSpellTypes::GroupHoTHeals: case BotSpellTypes::HoTHeals: From 156c9285216940cd5484b82c7dfa37105d096750 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:50:56 -0600 Subject: [PATCH 41/97] debug cleanup --- zone/bot.cpp | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 4b632b7be..d7ffd8b28 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2757,28 +2757,6 @@ void Bot::CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, bo if (!GetCombatRoundForAlerts()) { SetCombatRoundForAlerts(); - LogTestDebugDetail("{} says, 'I'm {} {}. I am currently {} away {} to be between [{} - {}] away. MMR is {}.'" - , GetCleanName() - , (tar_distance < melee_distance_min ? "too close to" : (tar_distance <= melee_distance ? "within range of" : "too far away from")) - , tar->GetCleanName() - , tar_distance - , (tar_distance <= melee_distance ? "but only needed" : "but need to be") - , melee_distance_min - , melee_distance - , melee_distance_max - ); //deleteme - LogTestDebugDetail("{} says, 'My stance is {} #{}, I am {} taunting. I am set to {} {}, {} at MMR, distanceranged {}, sml {} [{}]'" - , GetCleanName() - , Stance::GetName(GetBotStance()) - , GetBotStance() - , (IsTaunting() ? "currently" : "not") - , (BehindMob() ? "stay behind" : "not stay behind") - , tar->GetCleanName() - , (GetMaxMeleeRange() ? "I stay" : "I do not stay") - , GetBotDistanceRanged() - , GetStopMeleeLevel() - , ((GetLevel() <= GetStopMeleeLevel()) ? "disabled" : "enabled") - ); //deleteme } if (tar_distance <= melee_distance) { From 6846bdc56c9e9a813d6276b1190e0c841f5fb698 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 27 Nov 2024 21:09:51 -0600 Subject: [PATCH 42/97] commanded spell fixes. All should be working now minus depart --- zone/bot.cpp | 75 ++++++++++++++++++++++---------------- zone/bot_commands/cast.cpp | 6 --- zone/botspellsai.cpp | 9 ++--- 3 files changed, 46 insertions(+), 44 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index d7ffd8b28..0bc474139 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9383,12 +9383,12 @@ bool Bot::PrecastChecks(Mob* tar, uint16 spellType) { } bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechecks, bool AECheck) { - if (!tar) { - LogBotPreChecksDetail("{} says, 'Cancelling cast due to CastChecks !tar.'", GetCleanName()); //deleteme - return false; - } - if (doPrechecks) { + if (!tar) { + LogBotPreChecksDetail("{} says, 'Cancelling cast due to CastChecks !tar.'", GetCleanName()); //deleteme + return false; + } + if (spells[spell_id].target_type == ST_Self && tar != this) { if (IsEffectInSpell(spell_id, SE_SummonCorpse) && RuleB(Bots, AllowCommandedSummonCorpse)) { //tar = this; @@ -9404,7 +9404,7 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec } } - LogBotPreChecksDetail("{} says, 'Running [{}] CastChecks on [{}].'", GetCleanName(), GetSpellTypeNameByID(spellType), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, 'Running [{}] CastChecks on [{}].'", GetCleanName(), GetSpellTypeNameByID(spellType), (tar ? tar->GetCleanName() : "nobody")); //deleteme if (!IsValidSpell(spell_id)) { LogBotPreChecksDetail("{} says, 'Cancelling cast due to !IsValidSpell.'", GetCleanName()); //deleteme @@ -9412,26 +9412,17 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec } if (IsFeared() || IsSilenced() || IsAmnesiad()) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Incapacitated.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Incapacitated.'", GetCleanName(), GetSpellName(spell_id), (tar ? tar->GetCleanName() : "nobody")); //deleteme return false; } if ((IsStunned() || IsMezzed() || DivineAura()) && !IsCastNotStandingSpell(spell_id)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to !IsCastNotStandingSpell.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme - return false; - } - - if ( - spells[spell_id].target_type == ST_Self - && tar != this && - (spellType != BotSpellTypes::SummonCorpse || RuleB(Bots, AllowCommandedSummonCorpse)) - ) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to ST_Self.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to !IsCastNotStandingSpell.'", GetCleanName(), GetSpellName(spell_id), (tar ? tar->GetCleanName() : "nobody")); //deleteme return false; } if (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanDoCombat.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanDoCombat.'", GetCleanName(), GetSpellName(spell_id), (tar ? tar->GetCleanName() : "nobody")); //deleteme return false; } @@ -9450,11 +9441,6 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec return false; } - if (this == tar && IsSacrificeSpell(spell_id)) { - LogBotPreChecks("{} says, 'Cancelling cast of {} due to IsSacrificeSpell.'", GetCleanName(), GetSpellName(spell_id)); //deleteme - return false; - } - if (spells[spell_id].caster_requirement_id && !PassCastRestriction(spells[spell_id].caster_requirement_id)) { LogBotPreChecks("{} says, 'Cancelling cast of {} due to !PassCastRestriction.'", GetCleanName(), GetSpellName(spell_id)); //deleteme return false; @@ -9506,11 +9492,39 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec return false; } + if (SpellTypeRequiresTarget(spellType) && !tar) { + LogBotPreChecksDetail("{} says, 'Cancelling cast due to CastChecks !tar.'", GetCleanName()); //deleteme + return false; + } + + if ( + spells[spell_id].target_type == ST_Self + && tar != this && + (spellType != BotSpellTypes::SummonCorpse || RuleB(Bots, AllowCommandedSummonCorpse)) + ) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to ST_Self.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + + if (this == tar && IsSacrificeSpell(spell_id)) { + LogBotPreChecks("{} says, 'Cancelling cast of {} due to IsSacrificeSpell.'", GetCleanName(), GetSpellName(spell_id)); //deleteme + return false; + } + if (!AECheck && !IsValidSpellRange(spell_id, tar)) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsValidSpellRange.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } + LogBotPreChecksDetail("{} says, 'Doing CanCastSpellType checks of {} on {}.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + if (!CanCastSpellType(spellType, spell_id, tar)) { + return false; + } + + if (IsCommandedSpell()) { //stop checks here for commanded spells + return true; + } + if (!IsValidTargetType(spell_id, GetSpellTargetType(spell_id), tar->GetBodyType())) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsValidTargetType.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; @@ -9526,13 +9540,13 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec return false; } - if (!DoResistCheckBySpellType(tar, spell_id, spellType)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to DoResistCheckBySpellType.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + if (!IsCommandedSpell() && !IsTaunting() && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting(), spell_id) && !tar->IsFleeing()) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to HasOrMayGetAggro.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - if (!IsCommandedSpell() && !IsTaunting() && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting(), spell_id) && !tar->IsFleeing()) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to HasOrMayGetAggro.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + if (!DoResistCheckBySpellType(tar, spell_id, spellType)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to DoResistCheckBySpellType.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } @@ -9549,11 +9563,6 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec return false; } - LogBotPreChecksDetail("{} says, 'Doing CanCastSpellType checks of {} on {}.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme - if (!CanCastSpellType(spellType, spell_id, tar)) { - return false; - } - return true; } @@ -11389,6 +11398,8 @@ bool Bot::HasValidAETarget(Bot* botCaster, uint16 spell_id, uint16 spellType, Mo if (!m->IsNPC()) { continue; } + + break; default: break; } diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index fb7f97209..ef5074e89 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -403,12 +403,6 @@ void bot_command_cast(Client* c, const Seperator* sep) continue; } - /* - TODO bot rewrite - - FIX: Depart - Group Cures, Precombat - NEED TO CHECK: precombat - */ if (bot_iter->GetBotStance() == Stance::Passive || bot_iter->GetHoldFlag() || bot_iter->GetAppearance() == eaDead || bot_iter->IsFeared() || bot_iter->IsSilenced() || bot_iter->IsAmnesiad() || bot_iter->GetHP() < 0) { continue; } diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 35cec06d1..a86b8e6c8 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -1042,14 +1042,11 @@ std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa IsGroupSpell(botSpellList[i].spellid) && !IsTGBCompatibleSpell(botSpellList[i].spellid) && !botCaster->IsInGroupOrRaid(tar, true) - ) { + ) { continue; } - - if ( - (!botCaster->IsCommandedSpell() || (botCaster->IsCommandedSpell() && SpellTypeRequiresCastChecks(spellType))) && - (!IsPBAESpell(botSpellList[i].spellid) && !botCaster->CastChecks(botSpellList[i].spellid, tar, spellType, false, IsAEBotSpellType(spellType))) - ) { + if (spellType == debugSpellType) { LogTestDebugDetail("{} - #{}: [{} #{}] - {} says, '{} #{} - Passed TGB checks.'", __FILE__, __LINE__, botCaster->GetSpellTypeNameByID(spellType), spellType, botCaster->GetCleanName(), spells[botSpellList[i].spellid].name, botSpellList[i].spellid); }; //deleteme + if (!IsPBAESpell(botSpellList[i].spellid) && !botCaster->CastChecks(botSpellList[i].spellid, tar, spellType, false, IsAEBotSpellType(spellType))) { continue; } From d0033d47863d290a4ddb92b9e35ce1f286e3f345 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 28 Nov 2024 00:56:55 -0600 Subject: [PATCH 43/97] add check for self on isingrouporraid --- zone/mob.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zone/mob.cpp b/zone/mob.cpp index a455932e6..da438671d 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -9777,6 +9777,10 @@ bool Mob::IsInGroupOrRaid(Mob *other, bool sameRaidGroup) { return false; } + if (this == other) { + return true; + } + auto* r = GetRaid(); auto* rO = other->GetRaid(); From ceeb08386493555860e3da86cbde30d52ba2dcf4 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 28 Nov 2024 22:15:02 -0600 Subject: [PATCH 44/97] Allow bots to bypass los checks for positioning if no detrimental types allowed --- common/spdat.cpp | 39 +++++++++++++++++++++++++++++++++++++++ common/spdat.h | 1 + zone/bot.cpp | 20 +++++++------------- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index bef0507df..32782c3c2 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -3309,3 +3309,42 @@ bool IsCommandedSpellType(uint16 spellType) { return false; } + +bool BotSpellTypeRequiresLoS(uint16 spellType, uint8 cls) { + switch (spellType) { + case BotSpellTypes::Nuke: + case BotSpellTypes::Root: + case BotSpellTypes::Lifetap: + case BotSpellTypes::Snare: + case BotSpellTypes::DOT: + case BotSpellTypes::Dispel: + case BotSpellTypes::Mez: + //case BotSpellTypes::Charm: // commanded + case BotSpellTypes::Slow: + case BotSpellTypes::Debuff: + case BotSpellTypes::HateRedux: + //case BotSpellTypes::Fear: // commanded + case BotSpellTypes::Stun: + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::AEMez: + case BotSpellTypes::AEStun: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::AESlow: + case BotSpellTypes::AESnare: + //case BotSpellTypes::AEFear: // commanded + case BotSpellTypes::AEDispel: + case BotSpellTypes::AERoot: + case BotSpellTypes::AEDoT: + case BotSpellTypes::AELifetap: + case BotSpellTypes::PBAENuke: + // case BotSpellTypes::Lull: // commanded + case BotSpellTypes::HateLine: + case BotSpellTypes::AEHateLine: + return true; + default: + return false; + } + + return false; +} diff --git a/common/spdat.h b/common/spdat.h index 3f87672b4..9d7c0910a 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -752,6 +752,7 @@ bool SpellTypeRequiresTarget(uint16 spellType, uint16 cls = 0); bool SpellTypeRequiresCastChecks(uint16 spellType); bool SpellTypeRequiresAEChecks(uint16 spellType); bool IsCommandedSpellType(uint16 spellType); +bool BotSpellTypeRequiresLoS(uint16 spellType, uint8 cls); // These should not be used to determine spell category.. // They are a graphical affects (effects?) index only diff --git a/zone/bot.cpp b/zone/bot.cpp index 0bc474139..28f9cb549 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -11329,14 +11329,14 @@ bool Bot::RequiresLoSForPositioning() { if (GetLevel() < GetStopMeleeLevel()) { return true; } - else if (GetClass() == Class::Bard) { - return false; - } - else if (GetClass() == Class::Cleric) { //TODO bot rewrite - add check to see if spell requires los - return false; + + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + if (BOT_SPELL_TYPES_DETRIMENTAL(i) && !GetSpellHold(i)) { + return true; + } } - return true; + return false; } bool Bot::HasRequiredLoSForPositioning(Mob* tar) { @@ -11344,13 +11344,7 @@ bool Bot::HasRequiredLoSForPositioning(Mob* tar) { return true; } - if (GetClass() == Class::Cleric) { //add check to see if spell requires los - return true; - } - else if (GetClass() == Class::Bard && GetLevel() >= GetStopMeleeLevel()) { - return true; - } - if (!DoLosChecks(this, tar)) { + if (RequiresLoSForPositioning() && !DoLosChecks(this, tar)) { return false; } From 12f703678e709381bb81dfbac18d2788b79d9d21 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 28 Nov 2024 22:15:34 -0600 Subject: [PATCH 45/97] add invalid spell id cleanup to bot spell list inserts --- common/database/database_update_manifest_bots.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/common/database/database_update_manifest_bots.cpp b/common/database/database_update_manifest_bots.cpp index 72e26142c..235c160b3 100644 --- a/common/database/database_update_manifest_bots.cpp +++ b/common/database/database_update_manifest_bots.cpp @@ -1065,6 +1065,13 @@ VALUES (3011, 25555, 112, 86, 90), (3011, 28632, 112, 91, 95), (3011, 34662, 112, 96, 254); + +DELETE +FROM bot_spells_entries +WHERE NOT EXISTS +(SELECT * +FROM spells_new +WHERE bot_spells_entries.spell_id = spells_new.id); )" }, ManifestEntry{ @@ -1163,6 +1170,13 @@ VALUES (3005, 34752, 55, 99, 254), (3005, 34753, 55, 99, 254), (3005, 34751, 55, 99, 254); + +DELETE +FROM bot_spells_entries +WHERE NOT EXISTS +(SELECT * +FROM spells_new +WHERE bot_spells_entries.spell_id = spells_new.id); )" } // -- template; copy/paste this when you need to create a new entry From dc0819c2c60d52ce31decfdc55141046015934c9 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 28 Nov 2024 22:33:30 -0600 Subject: [PATCH 46/97] misc cleanup --- common/spdat.cpp | 23 ----------------------- common/version.h | 2 +- zone/bot.cpp | 8 ++++---- zone/bot.h | 1 - zone/entity.h | 2 +- 5 files changed, 6 insertions(+), 30 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index 32782c3c2..50b7651f4 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -678,29 +678,6 @@ bool IsAnyNukeOrStunSpell(uint16 spell_id) { } bool IsAnyAESpell(uint16 spell_id) { - //if ( - // spells[spell_id].target_type == ST_Target || - // spells[spell_id].target_type == ST_Self || - // spells[spell_id].target_type == ST_Animal || - // spells[spell_id].target_type == ST_Undead || - // spells[spell_id].target_type == ST_Summoned || - // spells[spell_id].target_type == ST_Tap || - // spells[spell_id].target_type == ST_Pet || - // spells[spell_id].target_type == ST_Corpse || - // spells[spell_id].target_type == ST_Plant || - // spells[spell_id].target_type == ST_Giant || - // spells[spell_id].target_type == ST_Dragon || - // spells[spell_id].target_type == ST_HateList || - // spells[spell_id].target_type == ST_LDoNChest_Cursed || - // spells[spell_id].target_type == ST_Muramite || - // spells[spell_id].target_type == ST_SummonedPet || - // spells[spell_id].target_type == ST_TargetsTarget || - // spells[spell_id].target_type == ST_PetMaster //|| - // //spells[spell_id].target_type == ST_AEBard //TODO needed? - //) { - // return false; - //} - if (IsAESpell(spell_id) || IsPBAENukeSpell(spell_id) || IsPBAESpell(spell_id) || IsAERainSpell(spell_id) || IsAERainNukeSpell(spell_id) || IsAEDurationSpell(spell_id)) { return true; } diff --git a/common/version.h b/common/version.h index 800ee365d..92982a1fa 100644 --- a/common/version.h +++ b/common/version.h @@ -43,7 +43,7 @@ */ #define CURRENT_BINARY_DATABASE_VERSION 9284 -#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9051 //TODO update as needed +#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9051 //TODO bot rewrite - update as needed #endif diff --git a/zone/bot.cpp b/zone/bot.cpp index 28f9cb549..ae915ed80 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -1613,7 +1613,7 @@ bool Bot::Process() } ScanCloseMobProcess(); - CheckScanCloseMobsMovingTimer(); // TODO bot rewrite -- necessary for bots? + CheckScanCloseMobsMovingTimer(); SpellProcess(); if (tic_timer.Check()) { @@ -1661,7 +1661,7 @@ bool Bot::Process() } } - if (viral_timer.Check()) { // TODO bot rewrite -- necessary for bots? + if (viral_timer.Check()) { VirusEffectProcess(); } @@ -7160,7 +7160,7 @@ bool Bot::CheckLoreConflict(const EQ::ItemData* item) { return (m_inv.HasItemByLoreGroup(item->LoreGroup, invWhereWorn) != INVALID_INDEX); } -bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint16 spellType) { +bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, uint16 spellType) { if (BOT_SPELL_TYPES_DETRIMENTAL(spellType, caster->GetClass())) { LogError("[EntityList::Bot_AICheckCloseBeneficialSpells] detrimental spells requested"); @@ -10927,7 +10927,7 @@ bool Bot::AttemptAICastSpell(uint16 spellType) { } if (!tar || !PrecastChecks(tar, spellType) || !AICastSpell(tar, GetChanceToCastBySpellType(spellType), spellType)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(spellType), BotAISpellRange, spellType)) { + if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(spellType), spellType)) { return result; } } diff --git a/zone/bot.h b/zone/bot.h index 2360d73b4..8d1a60047 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -46,7 +46,6 @@ constexpr uint32 MAG_EPIC_1_0 = 28034; extern WorldServer worldserver; -constexpr int BotAISpellRange = 100; // TODO: Write a method that calcs what the bot's spell range is based on spell, equipment, AA, whatever and replace this constexpr int NegativeItemReuse = -1; // Unlinked timer for items constexpr uint8 SumWater = 1; diff --git a/zone/entity.h b/zone/entity.h index 814a5feb5..b55c1c7a6 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -628,7 +628,7 @@ private: Client* GetBotOwnerByBotID(const uint32 bot_id); std::list GetBotsByBotOwnerCharacterID(uint32 botOwnerCharacterID); - bool Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint16 spellType); // TODO: Evaluate this closesly in hopes to eliminate + bool Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, uint16 spellType); // TODO: Evaluate this closesly in hopes to eliminate void ShowSpawnWindow(Client* client, int Distance, bool NamedOnly); // TODO: Implement ShowSpawnWindow in the bot class but it needs entity list stuff void ScanCloseClientMobs(std::unordered_map& close_mobs, Mob* scanning_mob); From 08387560b31afece3dc6fe2a66008cc6ffa5b2cd Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 28 Nov 2024 23:35:21 -0600 Subject: [PATCH 47/97] implement depart to use spell lists --- zone/bot_command.cpp | 2 +- zone/bot_commands/depart.cpp | 344 +++++++++++++++++++++++++++-------- 2 files changed, 270 insertions(+), 76 deletions(-) diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 650f4a5e8..1b00d701a 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1290,7 +1290,7 @@ int bot_command_init(void) bot_command_add("copysettings", "Copies settings from one bot to another", AccountStatus::Player, bot_command_copy_settings) || bot_command_add("defaultsettings", "Restores a bot back to default settings", AccountStatus::Player, bot_command_default_settings) || bot_command_add("defensive", "Orders a bot to use a defensive discipline", AccountStatus::Player, bot_command_defensive) || - bot_command_add("depart", "Orders a bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_depart) || //TODO bot rewrite - deleteme + bot_command_add("depart", "Orders a bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_depart) || bot_command_add("enforcespellsettings", "Toggles your Bot to cast only spells in their spell settings list.", AccountStatus::Player, bot_command_enforce_spell_list) || bot_command_add("findaliases", "Find available aliases for a bot command", AccountStatus::Player, bot_command_find_aliases) || bot_command_add("follow", "Orders bots to follow a designated target (option 'chain' auto-links eligible spawned bots)", AccountStatus::Player, bot_command_follow) || diff --git a/zone/bot_commands/depart.cpp b/zone/bot_commands/depart.cpp index d9b09d2d4..80c3c4e22 100644 --- a/zone/bot_commands/depart.cpp +++ b/zone/bot_commands/depart.cpp @@ -2,102 +2,296 @@ void bot_command_depart(Client* c, const Seperator* sep) { - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Depart]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_command_depart", sep->arg[0], "depart")) { + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Tells bots to list their port locations or port to a specific location" + }; + + std::vector notes = + { + "- This will interrupt any spell currently being cast by bots told to use the command.", + "- Bots will still check to see if they have the spell in their spell list, whether the target is immune, spell is allowed and all other sanity checks for spells" + }; + + std::vector example_format = + { + fmt::format( + "{} [list | zone shortname] [optional: single | group | ae] [actionable, default: spawned]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To tell everyone to list their portable locations:", + fmt::format( + "{} list spawned", + sep->arg[0] + ) + }; + std::vector examples_two = + { + "To tell all bots to port to Nexus:", + fmt::format( + "{} nexus spawned", + sep->arg[0] + ) + }; + std::vector examples_three = + { + "To tell Druidbot to single target port to Butcher:", + fmt::format( + "{} butcher single byname Druidbot", + sep->arg[0] + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + return; } - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [list | destination] ([option: single])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Depart); + bool single = false; + bool group = true; + bool ae = false; + std::string single_arg = sep->arg[2]; + bool list = false; + std::string destination = sep->arg[1]; + int ab_arg = 2; + + if (!single_arg.compare("single")) { + ++ab_arg; + single = true; + group = false; + } + else if (!single_arg.compare("group")) { + ++ab_arg; + single = false; + group = true; + } + else if (!single_arg.compare("ae")) { + ++ab_arg; + single = false; + group = false; + ae = true; + } - return; + if (!destination.compare("list") || destination.empty()) { + list = true; + + if (destination.empty()) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + } + } + + Mob* tar = c->GetTarget(); + + if (!tar) { + tar = c; + } + + std::string argString = sep->arg[ab_arg]; + + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionableArg = sep->arg[ab_arg]; + + if (actionableArg.empty()) { + actionableArg = "spawned"; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; } std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; bool single = false; - std::string single_arg = sep->arg[2]; - - if (!single_arg.compare("single")) { - single = true; - } - - std::string destination = sep->arg[1]; - - if (!destination.compare("list")) { - Bot* my_druid_bot = ActionableBots::Select_ByMinLevelAndClass(c, BCEnum::TT_None, sbl, 1, Class::Druid); - Bot* my_wizard_bot = ActionableBots::Select_ByMinLevelAndClass(c, BCEnum::TT_None, sbl, 1, Class::Wizard); - - if ( - (!my_druid_bot && !my_wizard_bot) || - (my_druid_bot && !my_druid_bot->IsInGroupOrRaid(c)) || - (my_wizard_bot && !my_wizard_bot->IsInGroupOrRaid(c)) - ) { - c->Message(Chat::Yellow, "No compatible bots found for %s.", sep->arg[0]); - return; - } - - helper_command_depart_list(c, my_druid_bot, my_wizard_bot, local_list, single); - - return; - } - else if (destination.empty()) { - c->Message(Chat::White, "A [destination] or [list] argument is required to use this command"); + if (ActionableBots::PopulateSBL(c, actionableArg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } - my_bot = nullptr; - sbl.clear(); - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - bool cast_success = false; + sbl.remove(nullptr); - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToDepart(); + BotSpell botSpell; + botSpell.SpellId = 0; + botSpell.SpellIndex = 0; + botSpell.ManaCost = 0; - if (helper_spell_check_fail(local_entry)) { + bool isSuccess = false; + std::map> listZones; + + for (auto bot_iter : sbl) { + if (!bot_iter->IsInGroupOrRaid(tar, !single)) { continue; } - if (local_entry->single != single) { + if (bot_iter->GetBotStance() == Stance::Passive || bot_iter->GetHoldFlag() || bot_iter->GetAppearance() == eaDead || bot_iter->IsFeared() || bot_iter->IsSilenced() || bot_iter->IsAmnesiad() || bot_iter->GetHP() < 0) { continue; } - if (destination.compare(spells[local_entry->spell_id].teleport_zone)) { - continue; + std::list botSpellListItr = bot_iter->GetPrioritizedBotSpellsBySpellType(bot_iter, BotSpellTypes::Teleport, tar); + + for (std::list::iterator itr = botSpellListItr.begin(); itr != botSpellListItr.end(); ++itr) { + if (!IsValidSpell(itr->SpellId)) { + continue; + } + + if ( + (single && spells[itr->SpellId].target_type != ST_Target) || + (group && !IsGroupSpell(itr->SpellId)) || + (ae && !IsAnyAESpell(itr->SpellId)) + ) { + continue; + } + + if (list) { + auto it = listZones.find(spells[itr->SpellId].teleport_zone); + + if (it != listZones.end()) { + const auto& [val1, val2] = it->second; + + if (val1 == spells[itr->SpellId].target_type && val2 == bot_iter->GetClass()) { + continue; + } + } + + Bot::BotGroupSay( + bot_iter, + fmt::format( + "I can port you to {}.", + Saylink::Silent( + fmt::format( + "{} {} {} byname {}", + sep->arg[0], + spells[itr->SpellId].teleport_zone, + (spells[itr->SpellId].target_type == ST_Target ? "single" : (IsGroupSpell(itr->SpellId) ? "group" : "ae")), + bot_iter->GetCleanName() + ).c_str() + , ZoneLongName(ZoneID(spells[itr->SpellId].teleport_zone))) + ).c_str() + ); + + listZones.insert({ spells[itr->SpellId].teleport_zone, {spells[itr->SpellId].target_type, bot_iter->GetClass()} }); + + continue; + } + + if (destination.compare(spells[itr->SpellId].teleport_zone)) { + continue; + } + + bot_iter->SetCommandedSpell(true); + + if (!IsValidSpellAndLoS(itr->SpellId, bot_iter->HasLoS())) { + continue; + } + + if (IsInvulnerabilitySpell(itr->SpellId)) { + tar = bot_iter; //target self for invul type spells + } + + if (bot_iter->IsCommandedSpell() && bot_iter->IsCasting()) { + Bot::BotGroupSay( + bot_iter, + fmt::format( + "Interrupting {}. I have been commanded to try to cast a [{}] spell, {} on {}.", + bot_iter->CastingSpellID() ? spells[bot_iter->CastingSpellID()].name : "my spell", + bot_iter->GetSpellTypeNameByID(BotSpellTypes::Teleport), + spells[itr->SpellId].name, + tar->GetCleanName() + ).c_str() + ); + + bot_iter->InterruptSpell(); + } + + if (bot_iter->CastSpell(itr->SpellId, tar->GetID(), EQ::spells::CastingSlot::Gem2, -1, -1)) { + if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(BotSpellTypes::Teleport)) { + bot_iter->SetCastedSpellType(UINT16_MAX); + } + else { + bot_iter->SetCastedSpellType(BotSpellTypes::Teleport); + } + + if (bot_iter->GetClass() != Class::Bard || RuleB(Bots, BardsAnnounceCasts)) { + Bot::BotGroupSay( + bot_iter, + fmt::format( + "Casting {} [{}] on {}.", + GetSpellName(itr->SpellId), + bot_iter->GetSpellTypeNameByID(BotSpellTypes::Teleport), + (tar == bot_iter ? "myself" : tar->GetCleanName()) + ).c_str() + ); + } + + isSuccess = true; + } + + bot_iter->SetCommandedSpell(false); + + if (isSuccess) { + return; + } + else { + continue; + } } - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - - if (!my_bot) { - continue; - } - - if (my_bot->BotPassiveCheck()) { - continue; - } - - if (!my_bot->IsInGroupOrRaid(c)) { - continue; - } - - if (local_entry->spell_id != 0 && my_bot->GetMana() < spells[local_entry->spell_id].mana) { - continue; - } - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - - break; } - if (!cast_success) { - helper_no_available_bots(c, my_bot); + if ( + (list && listZones.empty()) || + (!list && !isSuccess) + ) { + c->Message( + Chat::Yellow, + fmt::format( + "No bots are capable of that on {}. Be sure they are in the same group, raid or raid group if necessary.", + tar ? tar->GetCleanName() : "you" + ).c_str() + ); } } From 867771e76bd5259b550737675d229dc639ac66f2 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 28 Nov 2024 23:35:35 -0600 Subject: [PATCH 48/97] add default spawned note to ^cast --- zone/bot_commands/cast.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index ef5074e89..980821541 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -17,11 +17,11 @@ void bot_command_cast(Client* c, const Seperator* sep) std::vector example_format = { fmt::format( - "{} [Type Shortname] [actionable]" + "{} [Type Shortname] [actionable, default: spawned]" , sep->arg[0] ), fmt::format( - "{} [Type ID] [actionable]" + "{} [Type ID] [actionable, default: spawned]" , sep->arg[0] ) }; From 962e2d80b9165009c3994e0863570c072fc087f1 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 1 Dec 2024 20:36:27 -0600 Subject: [PATCH 49/97] fix AA loading and expansionbitmask saving/loading --- zone/bot.cpp | 7 +++---- zone/bot.h | 1 + zone/bot_database.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index ae915ed80..18e2fb048 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -248,12 +248,12 @@ Bot::Bot( database.botdb.LoadTimers(this); - LoadAAs(); - LoadDefaultBotSettings(); database.botdb.LoadBotSettings(this); + LoadAAs(); + if (database.botdb.LoadBuffs(this)) { //reapply some buffs uint32 buff_count = GetMaxBuffSlots(); @@ -1258,7 +1258,6 @@ int32 Bot::GenerateBaseHitPoints() { } void Bot::LoadAAs() { - aa_ranks.clear(); int id = 0; @@ -10213,7 +10212,7 @@ void Bot::LoadDefaultBotSettings() { uint8 botStance = GetBotStance(); - for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { + for (uint16 i = BotBaseSettings::START_ALL; i <= BotBaseSettings::END; ++i) { SetBotBaseSetting(i, GetDefaultSetting(BotSettingCategories::BaseSetting, i, botStance)); LogBotSettingsDetail("{} says, 'Setting default {} [{}] to [{}]'", GetCleanName(), GetBotSettingCategoryName(i), i, GetDefaultBotBaseSetting(i, botStance)); //deleteme } diff --git a/zone/bot.h b/zone/bot.h index 8d1a60047..8426a9b7f 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -142,6 +142,7 @@ namespace BotBaseSettings { constexpr uint16 HPWhenToMed = 12; constexpr uint16 ManaWhenToMed = 13; + constexpr uint16 START_ALL = ExpansionBitmask; constexpr uint16 START = BotBaseSettings::ShowHelm; // Everything above this cannot be copied, changed or viewed by players constexpr uint16 END = BotBaseSettings::ManaWhenToMed; // Increment as needed }; diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 60f389a9b..a4d4c8385 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -2294,7 +2294,7 @@ bool BotDatabase::SaveBotSettings(Mob* m) if (m->IsBot()) { uint8 botStance = m->CastToBot()->GetBotStance(); - for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { + for (uint16 i = BotBaseSettings::START_ALL; i <= BotBaseSettings::END; ++i) { if (m->CastToBot()->GetBotBaseSetting(i) != m->CastToBot()->GetDefaultBotBaseSetting(i, botStance)) { auto e = BotSettingsRepository::BotSettings{ .char_id = charID, From 338985634f0150d7d7c4bdc61402a828cc07786b Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 1 Dec 2024 20:46:17 -0600 Subject: [PATCH 50/97] Add taunting update on stance change when necessary --- zone/bot_commands/bot.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/zone/bot_commands/bot.cpp b/zone/bot_commands/bot.cpp index f9a3ecfe9..b91d5a0a7 100644 --- a/zone/bot_commands/bot.cpp +++ b/zone/bot_commands/bot.cpp @@ -1404,6 +1404,25 @@ void bot_command_stance(Client *c, const Seperator *sep) bot_iter->SetBotStance(value); bot_iter->LoadDefaultBotSettings(); database.botdb.LoadBotSettings(bot_iter); + + if ( + (bot_iter->GetClass() == Class::Warrior || bot_iter->GetClass() == Class::Paladin || bot_iter->GetClass() == Class::ShadowKnight) && + (bot_iter->GetBotStance() == Stance::Aggressive) + ) { + bot_iter->SetTaunting(true); + + if (bot_iter->HasPet() && bot_iter->GetPet()->GetSkill(EQ::skills::SkillTaunt)) { + bot_iter->GetPet()->CastToNPC()->SetTaunting(true); + } + } + else { + bot_iter->SetTaunting(false); + + if (bot_iter->HasPet() && bot_iter->GetPet()->GetSkill(EQ::skills::SkillTaunt)) { + bot_iter->GetPet()->CastToNPC()->SetTaunting(false); + } + } + bot_iter->Save(); ++success_count; } From ea84fd75da18e0bc594f099fdf0a35f3b0429316 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 1 Dec 2024 20:46:50 -0600 Subject: [PATCH 51/97] Clean up and fix any melee attacks to line up with clients --- zone/bot.cpp | 157 +++++++++++++++++++++++++++------------------------ zone/bot.h | 3 +- 2 files changed, 83 insertions(+), 77 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 18e2fb048..95067a89e 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -1779,29 +1779,43 @@ void Bot::BotMeditate(bool isSitting) { } } -void Bot::BotRangedAttack(Mob* other, bool CanDoubleAttack) { - if (!TargetValidation(other)) { return; } +bool Bot::BotRangedAttack(Mob* other, bool CanDoubleAttack) { + if (!other || !IsAttackAllowed(other) || IsCasting() || DivineAura() || IsStunned() || IsMezzed() || (GetAppearance() == eaDead)) { + return false; + } + + if ( + !GetPullingFlag() && + ( + (GetBotStance() != Stance::Aggressive && GetBotStance() != Stance::Burn && GetBotStance() != Stance::AEBurn) && + other->GetHPRatio() > 99.0f + ) + ) { + return false; + } if (!CanDoubleAttack && ((attack_timer.Enabled() && !attack_timer.Check(false)) || (ranged_timer.Enabled() && !ranged_timer.Check()))) { LogCombatDetail("Bot ranged attack canceled. Timer not up. Attack [{}] ranged [{}]", attack_timer.GetRemainingTime(), ranged_timer.GetRemainingTime()); - Message(0, "Error: Timer not up. Attack %d, ranged %d", attack_timer.GetRemainingTime(), ranged_timer.GetRemainingTime()); - return; + return false; } const auto rangedItem = GetBotItem(EQ::invslot::slotRange); const EQ::ItemData* RangeWeapon = nullptr; + if (rangedItem) { RangeWeapon = rangedItem->GetItem(); } if (!RangeWeapon) { - return; + return false; } const auto ammoItem = GetBotItem(EQ::invslot::slotAmmo); const EQ::ItemData* Ammo = nullptr; - if (ammoItem) + + if (ammoItem) { Ammo = ammoItem->GetItem(); + } // Bow requires arrows if ( @@ -1827,7 +1841,7 @@ void Bot::BotRangedAttack(Mob* other, bool CanDoubleAttack) { } } - return; + return false; } LogCombatDetail("Ranged attacking [{}] with {} [{}] ([{}]){}{}{}", @@ -1840,10 +1854,6 @@ void Bot::BotRangedAttack(Mob* other, bool CanDoubleAttack) { (Ammo && Ammo->ItemType == EQ::item::ItemTypeArrow ? std::to_string(Ammo->ID) : "") ); - if (!IsAttackAllowed(other) || IsCasting() || DivineAura() || IsStunned() || IsMezzed() || (GetAppearance() == eaDead)) { - return; - } - SendItemAnimation(other, Ammo, (RangeWeapon->ItemType == EQ::item::ItemTypeBow ? EQ::skills::SkillArchery : EQ::skills::SkillThrowing)); if (RangeWeapon->ItemType == EQ::item::ItemTypeBow) { DoArcheryAttackDmg(other, rangedItem, ammoItem); // watch @@ -1861,7 +1871,7 @@ void Bot::BotRangedAttack(Mob* other, bool CanDoubleAttack) { ) ) { ammoItem->SetCharges((ammoItem->GetCharges() - 1)); - LogCombat("Consumed Archery Ammo from slot {}.", EQ::invslot::slotAmmo); + LogCombatDetail("Consumed Archery Ammo from slot {}.", EQ::invslot::slotAmmo); if (ammoItem->GetCharges() < 1) { RemoveBotItemBySlot(EQ::invslot::slotAmmo); @@ -1869,10 +1879,10 @@ void Bot::BotRangedAttack(Mob* other, bool CanDoubleAttack) { } } else if (!consumes_ammo) { - LogCombat("Archery Ammo Consumption is disabled."); + LogCombatDetail("Archery Ammo Consumption is disabled."); } else { - LogCombat("Endless Quiver prevented Ammo Consumption."); + LogCombatDetail("Endless Quiver prevented Ammo Consumption."); } } else { @@ -1880,7 +1890,7 @@ void Bot::BotRangedAttack(Mob* other, bool CanDoubleAttack) { // Consume Ammo, unless Ammo Consumption is disabled if (RuleB(Bots, BotThrowingConsumesAmmo)) { ammoItem->SetCharges((ammoItem->GetCharges() - 1)); - LogCombat("Consumed Throwing Ammo from slot {}.", EQ::invslot::slotAmmo); + LogCombatDetail("Consumed Throwing Ammo from slot {}.", EQ::invslot::slotAmmo); if (ammoItem->GetCharges() < 1) { RemoveBotItemBySlot(EQ::invslot::slotAmmo); @@ -1888,45 +1898,51 @@ void Bot::BotRangedAttack(Mob* other, bool CanDoubleAttack) { } } else { - LogCombat("Throwing Ammo Consumption is disabled."); + LogCombatDetail("Throwing Ammo Consumption is disabled."); } } CommonBreakInvisibleFromCombat(); + + return true; } bool Bot::CheckBotDoubleAttack(bool tripleAttack) { //Check for bonuses that give you a double attack chance regardless of skill (ie Bestial Frenzy/Harmonious Attack AA) - uint32 bonusGiveDA = (aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack); - // If you don't have the double attack skill, return - if (!GetSkill(EQ::skills::SkillDoubleAttack) && !(GetClass() == Class::Bard || GetClass() == Class::Beastlord)) + uint32 bonus_give_double_attack = aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack; + + if (!GetSkill(EQ::skills::SkillDoubleAttack) && !bonus_give_double_attack) { return false; - - // You start with no chance of double attacking - float chance = 0.0f; - uint16 skill = GetSkill(EQ::skills::SkillDoubleAttack); - int32 bonusDA = (aabonuses.DoubleAttackChance + spellbonuses.DoubleAttackChance + itembonuses.DoubleAttackChance); - //Use skill calculations otherwise, if you only have AA applied GiveDoubleAttack chance then use that value as the base. - if (skill) - chance = ((float(skill + GetLevel()) * (float(100.0f + bonusDA + bonusGiveDA) / 100.0f)) / 500.0f); - else - chance = ((float(bonusGiveDA) * (float(100.0f + bonusDA) / 100.0f)) / 100.0f); - - //Live now uses a static Triple Attack skill (lv 46 = 2% lv 60 = 20%) - We do not have this skill on EMU ATM. - //A reasonable forumla would then be TA = 20% * chance - //AA's can also give triple attack skill over cap. (ie Burst of Power) NOTE: Skill ID in spell data is 76 (Triple Attack) - //Kayen: Need to decide if we can implement triple attack skill before working in over the cap effect. - if (tripleAttack) { - // Only some Double Attack classes get Triple Attack [This is already checked in client_processes.cpp] - int32 triple_bonus = (spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance); - chance *= 0.2f; //Baseline chance is 20% of your double attack chance. - chance *= (float(100.0f + triple_bonus) / 100.0f); //Apply modifiers. } - if (zone->random.Real(0, 1) < chance) - return true; + float chance = 0.0f; + uint16 skill = GetSkill(EQ::skills::SkillDoubleAttack); - return false; + int32 bonus_double_attack = 0; + if ((GetClass() == Class::Paladin || GetClass() == Class::ShadowKnight) && (!HasTwoHanderEquipped())) { + LogCombatDetail("Knight class without a 2 hand weapon equipped = No DA Bonus!"); + } + else { + bonus_double_attack = aabonuses.DoubleAttackChance + spellbonuses.DoubleAttackChance + itembonuses.DoubleAttackChance; + } + + //Use skill calculations otherwise, if you only have AA applied GiveDoubleAttack chance then use that value as the base. + if (skill) { + chance = (float(skill + GetLevel()) * (float(100.0f + bonus_double_attack + bonus_give_double_attack) / 100.0f)) / 500.0f; + } + else { + chance = (float(bonus_give_double_attack + bonus_double_attack) / 100.0f); + } + + LogCombatDetail( + "skill [{}] bonus_give_double_attack [{}] bonus_double_attack [{}] chance [{}]", + skill, + bonus_give_double_attack, + bonus_double_attack, + chance + ); + + return zone->random.Roll(chance); } bool Bot::CheckTripleAttack() @@ -2200,17 +2216,16 @@ void Bot::AI_Process() } } else { - if (atCombatRange && IsBotRanged()){ - StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); + //TODO bot rewrite - add casting AI to this + if (atCombatRange && IsBotRanged() && ranged_timer.Check(false)) { + StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); - TryRangedAttack(tar); - - if (!TargetValidation(tar)) { return; } - - if (CheckDoubleRangedAttack()) { + if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) { BotRangedAttack(tar, true); } + ranged_timer.Start(); + return; } } @@ -2255,13 +2270,11 @@ void Bot::AI_Process() } if (IsBotRanged() && ranged_timer.Check(false)) { - TryRangedAttack(tar); - - if (!TargetValidation(tar)) { return; } - - if (CheckDoubleRangedAttack()) { + if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) { BotRangedAttack(tar, true); } + + ranged_timer.Start(); } else if (!IsBotRanged() && GetLevel() < stopMeleeLevel) { if (!GetMaxMeleeRange() || !RuleB(Bots, DisableSpecialAbilitiesAtMaxMelee)) { @@ -2551,7 +2564,7 @@ void Bot::DoAttackRounds(Mob* target, int hand) { (aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack) > 0; if (candouble) { - if (CastToClient()->CheckDoubleAttack()) { + if (CheckBotDoubleAttack()) { Attack(target, hand, false, false); if (hand == EQ::invslot::slotPrimary) { @@ -2593,8 +2606,8 @@ void Bot::DoAttackRounds(Mob* target, int hand) { if (hand == EQ::invslot::slotPrimary && CanThisClassTripleAttack()) { if (CheckTripleAttack()) { Attack(target, hand, false, false); - int flurry_chance = aabonuses.FlurryChance + spellbonuses.FlurryChance + - itembonuses.FlurryChance; + + int flurry_chance = aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance; if (flurry_chance && zone->random.Roll(flurry_chance)) { Attack(target, hand, false, false); @@ -2602,7 +2615,15 @@ void Bot::DoAttackRounds(Mob* target, int hand) { if (zone->random.Roll(flurry_chance)) { Attack(target, hand, false, false); } - //MessageString(Chat::NPCFlurry, YOU_FLURRY); //TODO bot rewrite - add output to others hits with flurry message + + if (GetOwner()) { + GetOwner()->MessageString( + Chat::NPCFlurry, + NPC_FLURRY, + GetCleanName(), + target->GetCleanName() + ); //TODO bot rewrite - add output to others hits with flurry message + } } } } @@ -2610,22 +2631,6 @@ void Bot::DoAttackRounds(Mob* target, int hand) { } } -bool Bot::TryRangedAttack(Mob* tar) { - - if (IsBotRanged() && ranged_timer.Check(false)) { - - if (!TargetValidation(tar)) { return false; } - - if (GetPullingFlag() || GetTarget()->GetHPRatio() <= 99.0f) { - BotRangedAttack(tar); - } - - return true; - } - - return false; -} - bool Bot::TryFacingTarget(Mob* tar) { if (!IsSitting() && !IsFacingMob(tar)) { FaceTarget(tar); @@ -11318,8 +11323,10 @@ void Bot::DoCombatPositioning( bool Bot::CheckDoubleRangedAttack() { int32 chance = spellbonuses.DoubleRangedAttack + itembonuses.DoubleRangedAttack + aabonuses.DoubleRangedAttack; - if (chance && zone->random.Roll(chance)) + + if (chance && zone->random.Roll(chance)) { return true; + } return false; } diff --git a/zone/bot.h b/zone/bot.h index 8426a9b7f..c8f6e4a8e 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -979,14 +979,13 @@ public: // Try Combat Methods bool TryEvade(Mob* tar); bool TryFacingTarget(Mob* tar); - bool TryRangedAttack(Mob* tar); bool TryPursueTarget(float leash_distance, glm::vec3& Goal); bool TryMeditate(); bool TryAutoDefend(Client* bot_owner, float leash_distance); bool TryIdleChecks(float fm_distance); bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal); bool TryBardMovementCasts(); - void BotRangedAttack(Mob* other, bool CanDoubleAttack = false); + bool BotRangedAttack(Mob* other, bool CanDoubleAttack = false); bool CheckDoubleRangedAttack(); // Public "Refactor" Methods From af5dbbe932b5af26a8a3f16bf413a2bc0fb04743 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:05:50 -0600 Subject: [PATCH 52/97] remove unnecessary messages on silence /block for bots --- zone/bot.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 95067a89e..450315514 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -5664,13 +5664,13 @@ bool Bot::CastSpell( LogSpellsDetail("Spell casting canceled: not able to cast now. Valid? [{}] casting [{}] waiting? [{}] spellend? [{}] stunned? [{}] feared? [{}] mezed? [{}] silenced? [{}]", IsValidSpell(spell_id), casting_spell_id, delaytimer, spellend_timer.Enabled(), IsStunned(), IsFeared(), IsMezzed(), IsSilenced() ); - if (IsSilenced() && !IsDiscipline(spell_id)) { - MessageString(Chat::White, SILENCED_STRING); - } - - if (IsAmnesiad() && IsDiscipline(spell_id)) { - MessageString(Chat::White, MELEE_SILENCE); - } + //if (IsSilenced() && !IsDiscipline(spell_id)) { + // MessageString(Chat::White, SILENCED_STRING); + //} + // + //if (IsAmnesiad() && IsDiscipline(spell_id)) { + // MessageString(Chat::White, MELEE_SILENCE); + //} if (casting_spell_id) { AI_Bot_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); @@ -5681,7 +5681,7 @@ bool Bot::CastSpell( } if (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) { - MessageString(Chat::White, SPELL_WOULDNT_HOLD); + //MessageString(Chat::White, SPELL_WOULDNT_HOLD); if (casting_spell_id) { AI_Bot_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); } From 3034bd576218b7a9db9859a4fdd2ad6f4b694dff Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:31:39 -0600 Subject: [PATCH 53/97] fix changes made to mercs by mistake --- zone/merc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zone/merc.cpp b/zone/merc.cpp index d66501efa..c54585f9e 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -1500,7 +1500,7 @@ bool Merc::AI_IdleCastCheck() { bool EntityList::Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { - if (BOT_SPELL_TYPES_DETRIMENTAL(iSpellTypes)) { + if ((iSpellTypes & SPELL_TYPES_DETRIMENTAL) != 0) { //according to live, you can buff and heal through walls... //now with PCs, this only applies if you can TARGET the target, but // according to Rogean, Live NPCs will just cast through walls/floors, no problem.. @@ -1569,7 +1569,7 @@ bool Merc::AIDoSpellCast(uint16 spellid, Mob* tar, int32 mana_cost, uint32* oDon float dist2 = 0; - if (mercSpell.type == SpellType_Escape) { + if (mercSpell.type & SpellType_Escape) { dist2 = 0; } else dist2 = DistanceSquared(m_Position, tar->GetPosition()); From a22502a13dfff550b851a18bb77498d8a41a5f16 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:33:21 -0600 Subject: [PATCH 54/97] rename BOT_SPELL_TYPE functions --- common/spdat.cpp | 10 +++++----- common/spdat.h | 8 ++++---- zone/bot.cpp | 14 +++++++------- zone/bot_commands/cast.cpp | 8 ++++---- zone/bot_commands/depart.cpp | 2 +- zone/botspellsai.cpp | 6 +++--- zone/mob.cpp | 8 ++++---- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index 50b7651f4..5c8064f8c 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -2791,7 +2791,7 @@ bool IsLichSpell(uint16 spell_id) return false; } -bool BOT_SPELL_TYPES_DETRIMENTAL(uint16 spellType, uint8 cls) { +bool IsBotSpellTypeDetrimental(uint16 spellType, uint8 cls) { switch (spellType) { case BotSpellTypes::Nuke: case BotSpellTypes::Root: @@ -2830,7 +2830,7 @@ bool BOT_SPELL_TYPES_DETRIMENTAL(uint16 spellType, uint8 cls) { return false; } -bool BOT_SPELL_TYPES_BENEFICIAL(uint16 spellType, uint8 cls) { +bool IsBotSpellTypeBeneficial(uint16 spellType, uint8 cls) { switch (spellType) { case BotSpellTypes::RegularHeal: case BotSpellTypes::CompleteHeal: @@ -2879,7 +2879,7 @@ bool BOT_SPELL_TYPES_BENEFICIAL(uint16 spellType, uint8 cls) { return false; } -bool BOT_SPELL_TYPES_OTHER_BENEFICIAL(uint16 spellType) { +bool IsBotSpellTypeOtherBeneficial(uint16 spellType) { switch (spellType) { case BotSpellTypes::RegularHeal: case BotSpellTypes::CompleteHeal: @@ -2922,7 +2922,7 @@ bool BOT_SPELL_TYPES_OTHER_BENEFICIAL(uint16 spellType) { return false; } -bool BOT_SPELL_TYPES_INNATE(uint16 spellType) { +bool IsBotSpellTypeInnate(uint16 spellType) { switch (spellType) { case BotSpellTypes::AENukes: case BotSpellTypes::AERains: @@ -2955,7 +2955,7 @@ bool BOT_SPELL_TYPES_INNATE(uint16 spellType) { } bool IsBotSpellType(uint16 spellType) { - if (BOT_SPELL_TYPES_DETRIMENTAL(spellType) && BOT_SPELL_TYPES_BENEFICIAL(spellType) && BOT_SPELL_TYPES_INNATE(spellType)) { + if (IsBotSpellTypeDetrimental(spellType) && IsBotSpellTypeBeneficial(spellType) && IsBotSpellTypeInnate(spellType)) { return true; } diff --git a/common/spdat.h b/common/spdat.h index 9d7c0910a..adf9c05e4 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -736,10 +736,10 @@ const uint32 SPELL_TYPES_DETRIMENTAL = (SpellType_Nuke | SpellType_Root | SpellT const uint32 SPELL_TYPES_BENEFICIAL = (SpellType_Heal | SpellType_Buff | SpellType_Escape | SpellType_Pet | SpellType_InCombatBuff | SpellType_Cure | SpellType_HateRedux | SpellType_InCombatBuffSong | SpellType_OutOfCombatBuffSong | SpellType_PreCombatBuff | SpellType_PreCombatBuffSong); const uint32 SPELL_TYPES_INNATE = (SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root); -bool BOT_SPELL_TYPES_DETRIMENTAL (uint16 spellType, uint8 cls = 0); -bool BOT_SPELL_TYPES_BENEFICIAL (uint16 spellType, uint8 cls = 0); -bool BOT_SPELL_TYPES_OTHER_BENEFICIAL(uint16 spellType); -bool BOT_SPELL_TYPES_INNATE (uint16 spellType); +bool IsBotSpellTypeDetrimental (uint16 spellType, uint8 cls = 0); +bool IsBotSpellTypeBeneficial (uint16 spellType, uint8 cls = 0); +bool IsBotSpellTypeOtherBeneficial(uint16 spellType); +bool IsBotSpellTypeInnate (uint16 spellType); bool IsBotSpellType (uint16 spellType); bool IsAEBotSpellType(uint16 spellType); bool IsGroupBotSpellType(uint16 spellType); diff --git a/zone/bot.cpp b/zone/bot.cpp index 450315514..b6b74e74a 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -7166,7 +7166,7 @@ bool Bot::CheckLoreConflict(const EQ::ItemData* item) { bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, uint16 spellType) { - if (BOT_SPELL_TYPES_DETRIMENTAL(spellType, caster->GetClass())) { + if (IsBotSpellTypeDetrimental(spellType, caster->GetClass())) { LogError("[EntityList::Bot_AICheckCloseBeneficialSpells] detrimental spells requested"); return false; } @@ -10258,7 +10258,7 @@ void Bot::SetBotSpellRecastTimer(uint16 spellType, Mob* tar, bool preCast) { return; } - if (!preCast && BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType)) { + if (!preCast && IsBotSpellTypeOtherBeneficial(spellType)) { return; } @@ -10276,7 +10276,7 @@ void Bot::SetBotSpellRecastTimer(uint16 spellType, Mob* tar, bool preCast) { if (tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot()) { return tar->GetOwner()->SetSpellTypeRecastTimer(spellType, (GetUltimateSpellDelay(spellType, tar) + addedDelay)); } - else if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType)) { + else if (IsBotSpellTypeOtherBeneficial(spellType)) { tar->SetSpellTypeRecastTimer(spellType, (GetUltimateSpellDelay(spellType, tar) + addedDelay)); } else { @@ -10407,7 +10407,7 @@ uint16 Bot::GetDefaultSpellTypePriority(uint16 spellType, uint8 priorityType, ui } uint16 Bot::GetDefaultSpellTypeIdlePriority(uint16 spellType, uint8 botClass, uint8 stance) { - if (!BOT_SPELL_TYPES_BENEFICIAL(spellType, botClass)) { + if (!IsBotSpellTypeBeneficial(spellType, botClass)) { return 0; } @@ -10669,7 +10669,7 @@ uint16 Bot::GetDefaultSpellTypePursuePriority(uint16 spellType, uint8 botClass, uint16 Bot::GetDefaultSpellTypeResistLimit(uint16 spellType, uint8 stance) { - if (!BOT_SPELL_TYPES_BENEFICIAL(spellType, GetClass())) { + if (!IsBotSpellTypeBeneficial(spellType, GetClass())) { return RuleI(Bots, SpellResistLimit); } else { @@ -10924,7 +10924,7 @@ bool Bot::AttemptAICastSpell(uint16 spellType) { return result; } - if (BOT_SPELL_TYPES_BENEFICIAL(spellType, GetClass())) { + if (IsBotSpellTypeBeneficial(spellType, GetClass())) { if (!PrecastChecks(this, spellType) || !AICastSpell(this, GetChanceToCastBySpellType(spellType), spellType)) { if (GetClass() == Class::Bard) { return result; @@ -11337,7 +11337,7 @@ bool Bot::RequiresLoSForPositioning() { } for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { - if (BOT_SPELL_TYPES_DETRIMENTAL(i) && !GetSpellHold(i)) { + if (IsBotSpellTypeDetrimental(i) && !GetSpellHold(i)) { return true; } } diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index 980821541..9b9263670 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -339,7 +339,7 @@ void bot_command_cast(Client* c, const Seperator* sep) break; default: if ( - (BOT_SPELL_TYPES_DETRIMENTAL(spellType) && !c->IsAttackAllowed(tar)) || + (IsBotSpellTypeDetrimental(spellType) && !c->IsAttackAllowed(tar)) || ( spellType == BotSpellTypes::Charm && ( @@ -354,7 +354,7 @@ void bot_command_cast(Client* c, const Seperator* sep) return; } - if (BOT_SPELL_TYPES_BENEFICIAL(spellType)) { + if (IsBotSpellTypeBeneficial(spellType)) { if ( (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) || ((tar->IsOfClientBot() && !c->IsInGroupOrRaid(tar)) || (tar->GetOwner() && tar->GetOwner()->IsOfClientBot() && !c->IsInGroupOrRaid(tar->GetOwner()))) @@ -418,14 +418,14 @@ void bot_command_cast(Client* c, const Seperator* sep) } if ( - BOT_SPELL_TYPES_BENEFICIAL(spellType) && + IsBotSpellTypeBeneficial(spellType) && !RuleB(Bots, CrossRaidBuffingAndHealing) && !bot_iter->IsInGroupOrRaid(newTar, true) ) { continue; } - if (BOT_SPELL_TYPES_DETRIMENTAL(spellType, bot_iter->GetClass()) && !bot_iter->IsAttackAllowed(newTar)) { + if (IsBotSpellTypeDetrimental(spellType, bot_iter->GetClass()) && !bot_iter->IsAttackAllowed(newTar)) { bot_iter->BotGroupSay( bot_iter, fmt::format( diff --git a/zone/bot_commands/depart.cpp b/zone/bot_commands/depart.cpp index 80c3c4e22..1facaef1f 100644 --- a/zone/bot_commands/depart.cpp +++ b/zone/bot_commands/depart.cpp @@ -249,7 +249,7 @@ void bot_command_depart(Client* c, const Seperator* sep) } if (bot_iter->CastSpell(itr->SpellId, tar->GetID(), EQ::spells::CastingSlot::Gem2, -1, -1)) { - if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(BotSpellTypes::Teleport)) { + if (IsBotSpellTypeOtherBeneficial(BotSpellTypes::Teleport)) { bot_iter->SetCastedSpellType(UINT16_MAX); } else { diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index a86b8e6c8..a5718a2dc 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -243,7 +243,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType, uint16 subTarge } if (AIDoSpellCast(s.SpellIndex, tar, s.ManaCost)) { - if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType)) { + if (IsBotSpellTypeOtherBeneficial(spellType)) { SetCastedSpellType(UINT16_MAX); if (!IsCommandedSpell()) { @@ -293,7 +293,7 @@ bool Bot::BotCastMez(Mob* tar, uint8 botClass, BotSpell& botSpell, uint16 spellT } if (AIDoSpellCast(s.SpellIndex, tar, s.ManaCost)) { - if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType)) { + if (IsBotSpellTypeOtherBeneficial(spellType)) { SetCastedSpellType(UINT16_MAX); if (!IsCommandedSpell()) { @@ -2514,7 +2514,7 @@ DBbotspells_Struct* ZoneDatabase::GetBotSpells(uint32 bot_spell_id) entry.bucket_comparison = e.bucket_comparison; // some spell types don't make much since to be priority 0, so fix that - if (!BOT_SPELL_TYPES_INNATE(entry.type) && entry.priority == 0) { + if (!IsBotSpellTypeInnate(entry.type) && entry.priority == 0) { entry.priority = 1; } diff --git a/zone/mob.cpp b/zone/mob.cpp index da438671d..8e8cb8c01 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -9631,7 +9631,7 @@ uint16 Mob::GetUltimateSpellDelay(uint16 spellType, Mob* tar) { return tar->GetOwner()->GetSpellDelay(GetPetSpellType(spellType)); } - if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType) && tar->IsOfClientBot()) { + if (IsBotSpellTypeOtherBeneficial(spellType) && tar->IsOfClientBot()) { return tar->GetSpellDelay(spellType); } @@ -9647,7 +9647,7 @@ bool Mob::GetUltimateSpellDelayCheck(uint16 spellType, Mob* tar) { return tar->GetOwner()->SpellTypeRecastCheck(GetPetSpellType(spellType)); } - if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType) && tar->IsOfClientBot()) { + if (IsBotSpellTypeOtherBeneficial(spellType) && tar->IsOfClientBot()) { return tar->SpellTypeRecastCheck(spellType); } @@ -9663,7 +9663,7 @@ uint8 Mob::GetUltimateSpellMinThreshold(uint16 spellType, Mob* tar) { return tar->GetOwner()->GetSpellMinThreshold(GetPetSpellType(spellType)); } - if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType) && tar->IsOfClientBot()) { + if (IsBotSpellTypeOtherBeneficial(spellType) && tar->IsOfClientBot()) { return tar->GetSpellMinThreshold(spellType); } @@ -9679,7 +9679,7 @@ uint8 Mob::GetUltimateSpellMaxThreshold(uint16 spellType, Mob* tar) { return tar->GetOwner()->GetSpellMaxThreshold(GetPetSpellType(spellType)); } - if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(spellType) && tar->IsOfClientBot()) { + if (IsBotSpellTypeOtherBeneficial(spellType) && tar->IsOfClientBot()) { return tar->GetSpellMaxThreshold(spellType); } From 1d0ca78e2d09d2e29e7015a07070e310baa9b554 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:34:40 -0600 Subject: [PATCH 55/97] remove unused functions --- common/spdat.cpp | 8 -------- zone/bot.h | 2 -- 2 files changed, 10 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index 5c8064f8c..e9aec626c 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -2954,14 +2954,6 @@ bool IsBotSpellTypeInnate(uint16 spellType) { return false; } -bool IsBotSpellType(uint16 spellType) { - if (IsBotSpellTypeDetrimental(spellType) && IsBotSpellTypeBeneficial(spellType) && IsBotSpellTypeInnate(spellType)) { - return true; - } - - return false; -} - bool IsAEBotSpellType(uint16 spellType) { switch (spellType) { case BotSpellTypes::AEDebuff: diff --git a/zone/bot.h b/zone/bot.h index c8f6e4a8e..bf785cfc5 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -991,8 +991,6 @@ public: // Public "Refactor" Methods static bool CheckCampSpawnConditions(Client* c); - inline bool CommandedDoSpellCast(int32 i, Mob* tar, int32 mana_cost) { return AIDoSpellCast(i, tar, mana_cost); } - protected: void BotMeditate(bool isSitting); bool CheckBotDoubleAttack(bool Triple = false); From 51eb75523e68af475f17252c5d8b27858c5f0bd9 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:35:21 -0600 Subject: [PATCH 56/97] add !commandedspell() check to aggro checks on cast --- zone/bot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index b6b74e74a..c0896ad60 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -10919,7 +10919,7 @@ bool Bot::AttemptAICastSpell(uint16 spellType) { Mob* tar = GetTarget(); - if (!IsTaunting() && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting())) { + if (!IsTaunting() && !IsCommandedSpell() && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting())) { LogBotPreChecksDetail("{} says, 'Cancelling cast of [{}] due to GetSpellTypeAggroCheck and HasOrMayGetAggro.'", GetCleanName(), GetSpellTypeNameByID(spellType)); //deleteme return result; } From ead31d2cd40cf943230502071d5c47c8f90c751e Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:37:39 -0600 Subject: [PATCH 57/97] Update cast.cpp --- zone/bot_commands/cast.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index 9b9263670..a52406810 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -451,6 +451,7 @@ void bot_command_cast(Client* c, const Seperator* sep) } bot_iter->SetCommandedSpell(false); + continue; } From 1af85092f71d0333d436e169ee03a5b58596308d Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Mon, 2 Dec 2024 07:32:30 -0600 Subject: [PATCH 58/97] Implement spell AI pulling, fix throw stone --- common/ruletypes.h | 4 +++- common/spdat.cpp | 19 ++++++++++++++++++ common/spdat.h | 2 +- zone/bot.cpp | 49 ++++++++++++++++++++++++++++++++++------------ zone/bot.h | 3 +++ 5 files changed, 62 insertions(+), 15 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 2dcdd3e97..7710ecc50 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -828,8 +828,10 @@ RULE_BOOL(Bots, AllowMagicianEpicPet, false, "If enabled, magician bots can summ RULE_INT(Bots, AllowMagicianEpicPetLevel, 50, "If AllowMagicianEpicPet is enabled, bots can start using their epic pets at this level") RULE_INT(Bots, RequiredMagicianEpicPetItemID, 28034, "If AllowMagicianEpicPet is enabled and this is set, bots will be required to have this item ID equipped to cast their epic. Takes in to account AllowMagicianEpicPetLevel as well. Set to 0 to disable requirement") RULE_STRING(Bots, EpicPetSpellName, "", "'teleport_zone' in the spell to be cast for epic pets. This must be in their spell list to cast.") -RULE_BOOL(Bots, AllowSpellPulling, true, "If enabled bots will use a spell to pull when within range. Uses PullSpellID.") +RULE_BOOL(Bots, UseSpellPulling, true, "If enabled bots will use a spell to pull when within range. Uses PullSpellID.") RULE_INT(Bots, PullSpellID, 5225, "Default 5225 - Throw Stone. Spell that will be cast to pull by bots") +RULE_BOOL(Bots, AllowRangedPulling, true, "If enabled bots will pull with their ranged items if set to ranged.") +RULE_BOOL(Bots, AllowAISpellPulling, true, "If enabled bots will rely on their detrimental AI to pull when within range.") RULE_BOOL(Bots, AllowBotEquipAnyClassGear, false, "Allows Bots to wear Equipment even if their class is not valid") RULE_BOOL(Bots, BotArcheryConsumesAmmo, true, "Set to false to disable Archery Ammo Consumption") RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption") diff --git a/common/spdat.cpp b/common/spdat.cpp index e9aec626c..8a21e23b9 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -3279,6 +3279,25 @@ bool IsCommandedSpellType(uint16 spellType) { return false; } +bool IsPullingSpellType(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::Nuke: + case BotSpellTypes::Lifetap: + case BotSpellTypes::Snare: + case BotSpellTypes::DOT: + case BotSpellTypes::Dispel: + case BotSpellTypes::Slow: + case BotSpellTypes::Debuff: + case BotSpellTypes::Stun: + case BotSpellTypes::HateLine: + return true; + default: + return false; + } + + return false; +} + bool BotSpellTypeRequiresLoS(uint16 spellType, uint8 cls) { switch (spellType) { case BotSpellTypes::Nuke: diff --git a/common/spdat.h b/common/spdat.h index adf9c05e4..e7a266459 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -740,7 +740,6 @@ bool IsBotSpellTypeDetrimental (uint16 spellType, uint8 cls = 0); bool IsBotSpellTypeBeneficial (uint16 spellType, uint8 cls = 0); bool IsBotSpellTypeOtherBeneficial(uint16 spellType); bool IsBotSpellTypeInnate (uint16 spellType); -bool IsBotSpellType (uint16 spellType); bool IsAEBotSpellType(uint16 spellType); bool IsGroupBotSpellType(uint16 spellType); bool IsGroupTargetOnlyBotSpellType(uint16 spellType); @@ -752,6 +751,7 @@ bool SpellTypeRequiresTarget(uint16 spellType, uint16 cls = 0); bool SpellTypeRequiresCastChecks(uint16 spellType); bool SpellTypeRequiresAEChecks(uint16 spellType); bool IsCommandedSpellType(uint16 spellType); +bool IsPullingSpellType(uint16 spellType); bool BotSpellTypeRequiresLoS(uint16 spellType, uint8 cls); // These should not be used to determine spell category.. diff --git a/zone/bot.cpp b/zone/bot.cpp index c0896ad60..81c7de239 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -101,6 +101,7 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm LoadDefaultBotSettings(); SetCastedSpellType(UINT16_MAX); SetCommandedSpell(false); + SetPullingSpell(false); //DisableBotSpellTimers(); // Do this once and only in this constructor @@ -178,6 +179,7 @@ Bot::Bot( SetBotCharmer(false); SetCastedSpellType(UINT16_MAX); SetCommandedSpell(false); + SetPullingSpell(false); bool stance_flag = false; if (!database.botdb.LoadStance(this, stance_flag) && bot_owner) { @@ -2198,36 +2200,53 @@ void Bot::AI_Process() // PULLING FLAG (ACTIONABLE RANGE) if (GetPullingFlag()) { - if (!IsBotNonSpellFighter() && !HOLDING && AI_HasSpells() && AI_EngagedCastCheck()) { - return; + if (!IsBotNonSpellFighter() && !HOLDING && AI_HasSpells()) { + SetPullingSpell(true); + + if (AI_EngagedCastCheck()) { + SetPullingSpell(false); + return; + } + + SetPullingSpell(false); } - if (RuleB(Bots, AllowSpellPulling)) { + if (RuleB(Bots, UseSpellPulling)) { uint16 pullSpell = RuleI(Bots, PullSpellID); - if (tar_distance <= (spells[pullSpell].range * spells[pullSpell].range)) { + if (tar_distance <= spells[pullSpell].range) { StopMoving(); if (!TargetValidation(tar)) { return; } CastSpell(pullSpell, tar->GetID()); + SetPullingSpell(false); return; } } else { - //TODO bot rewrite - add casting AI to this - if (atCombatRange && IsBotRanged() && ranged_timer.Check(false)) { - StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); + if (atCombatRange) { + if (RuleB(Bots, AllowRangedPulling) && IsBotRanged() && ranged_timer.Check(false)) { + StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); - if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) { - BotRangedAttack(tar, true); + if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) { + BotRangedAttack(tar, true); + } + + ranged_timer.Start(); + SetPullingSpell(false); + + return; } + else if (RuleB(Bots, AllowAISpellPulling) && !IsBotNonSpellFighter() && !HOLDING && AI_HasSpells() && AI_EngagedCastCheck()) { + SetPullingSpell(false); - ranged_timer.Start(); - - return; + return; + } } + + return; } } @@ -2622,7 +2641,7 @@ void Bot::DoAttackRounds(Mob* target, int hand) { NPC_FLURRY, GetCleanName(), target->GetCleanName() - ); //TODO bot rewrite - add output to others hits with flurry message + ); } } } @@ -9357,6 +9376,10 @@ bool Bot::PrecastChecks(Mob* tar, uint16 spellType) { return false; } + if (IsPullingSpell() && IsPullingSpellType(spellType)) { //Skip remaining checks for commanded + return true; + } + if (GetManaRatio() < GetSpellTypeMinManaLimit(spellType) || GetManaRatio() > GetSpellTypeMaxManaLimit(spellType)) { LogBotPreChecksDetail("{} says, 'Cancelling cast of [{}] on [{}] due to GetSpellTypeMinManaLimit or GetSpellTypeMaxManaLimit.'", GetCleanName(), GetSpellTypeNameByID(spellType), tar->GetCleanName()); //deleteme return false; diff --git a/zone/bot.h b/zone/bot.h index bf785cfc5..5a9cbf7fb 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -410,6 +410,8 @@ public: void SetPauseAI(bool pause_flag) { _pauseAI = pause_flag; } bool IsCommandedSpell() const { return _commandedSpell; } void SetCommandedSpell(bool value) { _commandedSpell = value; } + bool IsPullingSpell() const { return _pullingSpell; } + void SetPullingSpell(bool value) { _pullingSpell = value; } void SetGuardMode(); void SetHoldMode(); @@ -1083,6 +1085,7 @@ private: uint16 _castedSpellType; bool _hasLoS; bool _commandedSpell; + bool _pullingSpell; // Private "base stats" Members int32 _baseMR; From f12e597ff3acfbbe6e7fca82c241781a2bbac391 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:16:11 -0600 Subject: [PATCH 59/97] more pull tweaks --- zone/bot.cpp | 50 +++++++++++++++----------------------- zone/bot_commands/pull.cpp | 8 +++++- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 81c7de239..bfedc1494 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2200,15 +2200,28 @@ void Bot::AI_Process() // PULLING FLAG (ACTIONABLE RANGE) if (GetPullingFlag()) { - if (!IsBotNonSpellFighter() && !HOLDING && AI_HasSpells()) { - SetPullingSpell(true); + if (!TargetValidation(tar)) { return; } + + if (atCombatRange) { + if (RuleB(Bots, AllowRangedPulling) && IsBotRanged() && ranged_timer.Check(false)) { + StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); + + if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) { + BotRangedAttack(tar, true); + } + + ranged_timer.Start(); - if (AI_EngagedCastCheck()) { - SetPullingSpell(false); return; } - SetPullingSpell(false); + if (RuleB(Bots, AllowAISpellPulling) && !IsBotNonSpellFighter() && AI_HasSpells()) { + SetPullingSpell(true); + AI_EngagedCastCheck(); + SetPullingSpell(false); + + return; + } } if (RuleB(Bots, UseSpellPulling)) { @@ -2216,38 +2229,15 @@ void Bot::AI_Process() if (tar_distance <= spells[pullSpell].range) { StopMoving(); - - if (!TargetValidation(tar)) { return; } - + SetPullingSpell(true); CastSpell(pullSpell, tar->GetID()); SetPullingSpell(false); return; } } - else { - if (atCombatRange) { - if (RuleB(Bots, AllowRangedPulling) && IsBotRanged() && ranged_timer.Check(false)) { - StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); - if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) { - BotRangedAttack(tar, true); - } - - ranged_timer.Start(); - SetPullingSpell(false); - - return; - } - else if (RuleB(Bots, AllowAISpellPulling) && !IsBotNonSpellFighter() && !HOLDING && AI_HasSpells() && AI_EngagedCastCheck()) { - SetPullingSpell(false); - - return; - } - } - - return; - } + return; } // ENGAGED AT COMBAT RANGE diff --git a/zone/bot_commands/pull.cpp b/zone/bot_commands/pull.cpp index fa222fcfa..535743712 100644 --- a/zone/bot_commands/pull.cpp +++ b/zone/bot_commands/pull.cpp @@ -15,6 +15,12 @@ void bot_command_pull(Client *c, const Seperator *sep) std::string arg1 = sep->arg[1]; int ab_arg = 1; + std::string actionableArg = sep->arg[ab_arg]; + + if (actionableArg.empty()) { + actionableArg = "spawned"; + } + std::string class_race_arg = sep->arg[ab_arg]; bool class_race_check = false; @@ -24,7 +30,7 @@ void bot_command_pull(Client *c, const Seperator *sep) std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + if (ActionableBots::PopulateSBL(c, actionableArg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } From b69ac7dc2108fd200fc12af00648439fa0618637 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Mon, 2 Dec 2024 21:25:06 -0600 Subject: [PATCH 60/97] holding check at start of ai process --- zone/bot.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index bfedc1494..89d0f2cc2 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2087,7 +2087,7 @@ void Bot::AI_Process() return; } - if (raid && r_group == RAID_GROUPLESS) { + if (HOLDING || (raid && r_group == RAID_GROUPLESS)) { glm::vec3 Goal(0, 0, 0); TryNonCombatMovementChecks(bot_owner, follow_mob, Goal); @@ -2269,7 +2269,7 @@ void Bot::AI_Process() } } - if (!IsBotNonSpellFighter() && !HOLDING && AI_HasSpells() && AI_EngagedCastCheck()) { + if (!IsBotNonSpellFighter() && AI_HasSpells() && AI_EngagedCastCheck()) { return; } @@ -2330,6 +2330,7 @@ void Bot::AI_Process() SetAttackFlag(false); SetCombatRoundForAlerts(false); SetAttackingFlag(false); + if (!bot_owner->GetBotPulling()) { SetPullingFlag(false); @@ -2345,7 +2346,6 @@ void Bot::AI_Process() SetTarget(nullptr); if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) { - GetPet()->WipeHateList(); GetPet()->SetTarget(nullptr); } @@ -2360,10 +2360,10 @@ void Bot::AI_Process() if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) { return; } - if (!HOLDING && AI_HasSpells() && TryIdleChecks(fm_distance)) { + if (AI_HasSpells() && TryIdleChecks(fm_distance)) { return; } - if (!HOLDING && AI_HasSpells() && TryBardMovementCasts()) { + if (AI_HasSpells() && TryBardMovementCasts()) { return; } } From 71bf497803a57d152f6a6696f2468cee387fdb46 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 4 Dec 2024 20:53:24 -0600 Subject: [PATCH 61/97] Update groups.cpp --- zone/groups.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/groups.cpp b/zone/groups.cpp index e157b0063..512cccfc3 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -2459,7 +2459,7 @@ bool Group::AmIMainAssist(const char *mob_name) if (!mob_name) return false; - return !((bool)MainTankName.compare(mob_name)); + return !((bool)MainAssistName.compare(mob_name)); } bool Group::AmIPuller(const char *mob_name) From d6df4aae3f65d28fe05b9ef665e8a95566852bcf Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 4 Dec 2024 20:56:09 -0600 Subject: [PATCH 62/97] fully implement ^pull logic to always return, can still be overidden by ^attack --- zone/bot.cpp | 50 +++++++++++++++++++++++++++++++++++++------------- zone/bot.h | 2 +- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 89d0f2cc2..70bc8fa79 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2117,6 +2117,7 @@ void Bot::AI_Process() //ALT COMBAT (ACQUIRE HATE) glm::vec3 Goal(0, 0, 0); + // We have aggro to choose from if (IsEngaged()) { if (rest_timer.Enabled()) { @@ -2133,16 +2134,18 @@ void Bot::AI_Process() // RETURNING FLAG - else if (GetReturningFlag()) { - if (!ReturningFlagChecks(bot_owner, fm_distance)) { - return; - } + if (GetReturningFlag()) { + LogTestDebugDetail("#{}: {} has ReturningFlag", __LINE__, GetCleanName()); //deleteme + ReturningFlagChecks(bot_owner, leash_owner, fm_distance); + + return; } // DEFAULT (ACQUIRE TARGET) // VERIFY TARGET AND STANCE auto tar = GetBotTarget(bot_owner); + if (!tar) { return; } @@ -2202,6 +2205,9 @@ void Bot::AI_Process() if (GetPullingFlag()) { if (!TargetValidation(tar)) { return; } + if (!DoLosChecks(this, tar)) { + return; + } if (atCombatRange) { if (RuleB(Bots, AllowRangedPulling) && IsBotRanged() && ranged_timer.Check(false)) { StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); @@ -2237,7 +2243,9 @@ void Bot::AI_Process() } } + TryPursueTarget(leash_distance, Goal); return; + //TODO bot rewrite - need pulling checks below to prevent assist } // ENGAGED AT COMBAT RANGE @@ -2332,7 +2340,6 @@ void Bot::AI_Process() SetAttackingFlag(false); if (!bot_owner->GetBotPulling()) { - SetPullingFlag(false); SetReturningFlag(false); } @@ -3011,24 +3018,41 @@ Mob* Bot::GetBotTarget(Client* bot_owner) return t; } -bool Bot::ReturningFlagChecks(Client* bot_owner, float fm_distance) {// Need to make it back to group before clearing return flag - if (fm_distance <= GetFollowDistance()) { - - // Once we're back, clear blocking flags so everyone else can join in +bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_distance) { + if ( + (NOT_GUARDING && fm_distance <= GetFollowDistance()) || + (GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) <= GetFollowDistance()) + ) { // Once we're back, clear blocking flags so everyone else can join in SetReturningFlag(false); bot_owner->SetBotPulling(false); if (GetPet()) { GetPet()->SetPetOrder(m_previous_pet_order); } + + return false; } // Need to keep puller out of combat until they reach their 'return to' destination - if (HasTargetReflection()) { + WipeHateList(); - SetTarget(nullptr); - WipeHateList(); - return false; + if (!IsMoving()) { + glm::vec3 Goal(0, 0, 0); + + if (GUARDING) { + Goal = GetGuardPoint(); + } + else { + Mob* follow_mob = entity_list.GetMob(GetFollowID()); + + if (!follow_mob) { + follow_mob = leash_owner; + SetFollowID(leash_owner->GetID()); + } + + Goal = follow_mob->GetPosition(); + } + RunTo(Goal.x, Goal.y, Goal.z); } return true; diff --git a/zone/bot.h b/zone/bot.h index 5a9cbf7fb..9e187d216 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -932,7 +932,7 @@ public: ); bool PullingFlagChecks(Client* bot_owner); - bool ReturningFlagChecks(Client* bot_owner, float fm_distance); + bool ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_distance); void BotPullerProcess(Client* bot_owner, Raid* raid); From 3d4474861c95d7400eab46b6fd43077de3cbb952 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 5 Dec 2024 07:21:34 -0600 Subject: [PATCH 63/97] Rewrite ^pull logic and handling. **MORE** Add ^setassistee command to set who your bots will assist. Bots will always assist you first before anyone else. If the rule Bots, AllowCrossGroupRaidAssist is enabled bots will assist the group or raid main assists. Rewrites logic in handling of pull and returning to ensure bots make it back to their location. --- common/ruletypes.h | 1 + zone/bot.cpp | 158 ++++++++++++++++++++++++----- zone/bot_command.cpp | 2 + zone/bot_command.h | 1 + zone/bot_commands/set_assistee.cpp | 59 +++++++++++ zone/client.h | 3 + 6 files changed, 199 insertions(+), 25 deletions(-) create mode 100644 zone/bot_commands/set_assistee.cpp diff --git a/common/ruletypes.h b/common/ruletypes.h index 7710ecc50..79daedca5 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -880,6 +880,7 @@ RULE_BOOL(Bots, AllowCommandedResurrect, true, "If enabled bots can be commanded RULE_BOOL(Bots, AllowCommandedSummonCorpse, true, "If enabled bots can be commanded to summon other's corpses.") RULE_INT(Bots, CampTimer, 25, "Number of seconds after /camp has begun before bots camp out.") RULE_BOOL(Bots, SendClassRaceOnHelp, true, "If enabled a reminder of how to check class/race IDs will be sent when using compatible commands.") +RULE_BOOL(Bots, AllowCrossGroupRaidAssist, true, "If enabled bots will autodefend group or raid members set as main assist.") RULE_CATEGORY_END() RULE_CATEGORY(Chat) diff --git a/zone/bot.cpp b/zone/bot.cpp index 70bc8fa79..6f3ab2e50 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2039,6 +2039,7 @@ void Bot::AI_Process() auto raid = entity_list.GetRaidByBotName(GetName()); uint32 r_group = RAID_GROUPLESS; + if (raid) { raid->VerifyRaid(); r_group = raid->GetGroup(GetName()); @@ -2135,7 +2136,6 @@ void Bot::AI_Process() // RETURNING FLAG if (GetReturningFlag()) { - LogTestDebugDetail("#{}: {} has ReturningFlag", __LINE__, GetCleanName()); //deleteme ReturningFlagChecks(bot_owner, leash_owner, fm_distance); return; @@ -2208,6 +2208,7 @@ void Bot::AI_Process() if (!DoLosChecks(this, tar)) { return; } + if (atCombatRange) { if (RuleB(Bots, AllowRangedPulling) && IsBotRanged() && ranged_timer.Check(false)) { StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); @@ -2244,8 +2245,8 @@ void Bot::AI_Process() } TryPursueTarget(leash_distance, Goal); + return; - //TODO bot rewrite - need pulling checks below to prevent assist } // ENGAGED AT COMBAT RANGE @@ -2457,42 +2458,150 @@ bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) { if ( m_auto_defend_timer.Check() && - bot_owner->GetAggroCount() && NOT_HOLDING && NOT_PASSIVE ) { - auto xhaters = bot_owner->GetXTargetAutoMgr(); + XTargetAutoHaters* tempHaters; + std::vector assisteeHaters; + std::vector assisteeMembers; + bool found = false; - if (xhaters && !xhaters->empty()) { - for (auto hater_iter : xhaters->get_list()) { - if (!hater_iter.spawn_id) { - continue; - } + if (bot_owner->GetAggroCount()) { + tempHaters = bot_owner->GetXTargetAutoMgr(); - if (bot_owner->GetBotPulling() && bot_owner->GetTarget() && hater_iter.spawn_id == bot_owner->GetTarget()->GetID()) { - continue; - } + if (tempHaters && !tempHaters->empty()) { + assisteeHaters.emplace_back(tempHaters); + assisteeMembers.emplace_back(bot_owner); + } + } - auto hater = entity_list.GetMob(hater_iter.spawn_id); - if (hater && hater->CastToNPC()->IsOnHatelist(bot_owner) && !hater->IsMezzed() && DistanceSquared(hater->GetPosition(), bot_owner->GetPosition()) <= leash_distance) { - // This is roughly equivilent to npc attacking a client pet owner - AddToHateList(hater, 1); - SetTarget(hater); - SetAttackingFlag(); + if ( + (!bot_owner->GetAssistee() || !entity_list.GetClientByCharID(bot_owner->GetAssistee())) && + RuleB(Bots, AllowCrossGroupRaidAssist) + ) { + XTargetAutoHaters* temp_xhaters = bot_owner->GetXTargetAutoMgr(); + bool assisteeFound = false; - if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { - GetPet()->AddToHateList(hater, 1); - GetPet()->SetTarget(hater); + if (IsRaidGrouped()) { + Raid* r = entity_list.GetRaidByBotName(GetName()); + if (r) { + for (const auto& m : r->members) { + if ( + m.member && + m.member->IsClient() && + m.member->GetAggroCount() && + r->IsAssister(m.member_name) + ) { + temp_xhaters = m.member->GetXTargetAutoMgr(); + + if (!temp_xhaters || temp_xhaters->empty()) { + continue; + } + + assisteeHaters.emplace_back(temp_xhaters); + assisteeMembers.emplace_back(m.member); + } } + } + } + else if (HasGroup()) { + Group* g = GetGroup(); + if (g) { + for (auto& m : g->members) { + if ( + m && + m->IsClient() && + m->CastToClient()->GetAggroCount() && + g->AmIMainAssist(m->GetName()) + ) { + temp_xhaters = m->CastToClient()->GetXTargetAutoMgr(); - m_auto_defend_timer.Disable(); + if (!temp_xhaters || temp_xhaters->empty()) { + continue; + } - return true; + assisteeHaters.emplace_back(temp_xhaters); + assisteeMembers.emplace_back(m->CastToClient()); + } + } + } + } + else { + return false; + } + } + else { + if (bot_owner->GetAssistee()) { + Client* c = entity_list.GetClientByCharID(bot_owner->GetAssistee()); + + if (bot_owner->IsInGroupOrRaid(c) && c->GetAggroCount()) { + tempHaters = bot_owner->GetXTargetAutoMgr(); + + if (tempHaters && !tempHaters->empty()) { + assisteeHaters.emplace_back(tempHaters); + assisteeMembers.emplace_back(c); + } } } } + + if (!assisteeHaters.empty()) { + for (XTargetAutoHaters* xHaters : assisteeHaters) { + if (!xHaters->empty()) { + for (auto hater_iter : xHaters->get_list()) { + if (!hater_iter.spawn_id) { + continue; + } + + Mob* hater = nullptr; + + for (Client* xMember : assisteeMembers) { + if ( + xMember && + xMember->GetBotPulling() && + xMember->GetTarget() && + (hater_iter.spawn_id == xMember->GetTarget()->GetID()) + ) { + continue; + } + + hater = entity_list.GetMob(hater_iter.spawn_id); + + if ( + hater && + !hater->IsMezzed() && + (DistanceSquared(hater->GetPosition(), bot_owner->GetPosition()) <= leash_distance) && + hater->CastToNPC()->IsOnHatelist(xMember) + ) { + break; + } + + hater = nullptr; + } + + if (hater) { + AddToHateList(hater, 1); + SetTarget(hater); + SetAttackingFlag(); + + if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { + GetPet()->AddToHateList(hater, 1); + GetPet()->SetTarget(hater); + } + + m_auto_defend_timer.Disable(); + + return true; + } + } + } + } + } + + return false; } } + return false; } @@ -3052,6 +3161,7 @@ bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_dist Goal = follow_mob->GetPosition(); } + RunTo(Goal.x, Goal.y, Goal.z); } @@ -11283,8 +11393,6 @@ void Bot::DoCombatPositioning( bool behindMob, bool frontMob ) { - //LogTestDebug("{} says, 'DoCombatPositioning. {} #{}", GetCleanName(), __FILE__, __LINE__); //deleteme - if (HasTargetReflection()) { if (!IsTaunting() && !tar->IsFeared() && !tar->IsStunned()) { if (TryEvade(tar)) { diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 1b00d701a..a2a25a71f 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1336,6 +1336,7 @@ int bot_command_init(void) bot_command_add("precombat", "Sets flag used to determine pre-combat behavior", AccountStatus::Player, bot_command_precombat) || bot_command_add("pull", "Orders a designated bot to 'pull' an enemy", AccountStatus::Player, bot_command_pull) || bot_command_add("release", "Releases a suspended bot's AI processing (with hate list wipe)", AccountStatus::Player, bot_command_release) || + bot_command_add("setassistee", "Sets your target to be the person your bots assist. Bots will always assist you before others", AccountStatus::Player, bot_command_set_assistee) || bot_command_add("sithppercent", "HP threshold for a bot to start sitting in combat if allowed", AccountStatus::Player, bot_command_sit_hp_percent) || bot_command_add("sitincombat", "Toggles whether or a not a bot will attempt to med or sit to heal in combat", AccountStatus::Player, bot_command_sit_in_combat) || bot_command_add("sitmanapercent", "Mana threshold for a bot to start sitting in combat if allowed", AccountStatus::Player, bot_command_sit_mana_percent) || @@ -2243,6 +2244,7 @@ void SendSpellTypeWindow(Client* c, const Seperator* sep) { #include "bot_commands/precombat.cpp" #include "bot_commands/pull.cpp" #include "bot_commands/release.cpp" +#include "bot_commands/set_assistee.cpp" #include "bot_commands/sit_hp_percent.cpp" #include "bot_commands/sit_in_combat.cpp" #include "bot_commands/sit_mana_percent.cpp" diff --git a/zone/bot_command.h b/zone/bot_command.h index ecce9485d..d6644bbf6 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -1747,6 +1747,7 @@ void bot_command_heritage(Client *c, const Seperator *sep); void bot_command_inspect_message(Client *c, const Seperator *sep); void bot_command_list_bots(Client *c, const Seperator *sep); void bot_command_report(Client *c, const Seperator *sep); +void bot_command_set_assistee(Client* c, const Seperator* sep); void bot_command_spawn(Client *c, const Seperator *sep); void bot_command_stance(Client *c, const Seperator *sep); void bot_command_stop_melee_level(Client *c, const Seperator *sep); diff --git a/zone/bot_commands/set_assistee.cpp b/zone/bot_commands/set_assistee.cpp new file mode 100644 index 000000000..d2be953e2 --- /dev/null +++ b/zone/bot_commands/set_assistee.cpp @@ -0,0 +1,59 @@ +#include "../bot_command.h" + +void bot_command_set_assistee(Client* c, const Seperator* sep) +{ + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Sets your bots to assist your target in addition to yourself" + }; + + std::vector notes = + { + "- Your target must be another player in your group or raid.", + "- This needs to be set on every zone/camp you do." + }; + + std::vector example_format = { }; + std::vector examples_one = { }; + std::vector examples_two = { }; + std::vector examples_three = { }; + + std::vector actionables = { }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; + } + + Mob* assistee = c->GetTarget(); + + if (assistee && assistee->IsClient() && c->IsInGroupOrRaid(assistee)) { + c->SetAssistee(assistee->CastToClient()->CharacterID()); + c->Message(Chat::Green, "Your bots will now assist %s.", assistee->GetCleanName()); + + return; + } + + c->Message(Chat::Yellow, "You can only set your bots to assist clients that are in your group or raid."); + + return; +} diff --git a/zone/client.h b/zone/client.h index 2a090eeac..c4ffd5460 100644 --- a/zone/client.h +++ b/zone/client.h @@ -2221,6 +2221,8 @@ public: bool GetBotPulling() { return m_bot_pulling; } void SetBotPulling(bool flag = true) { m_bot_pulling = flag; } + uint32 GetAssistee() { return _assistee; } + void SetAssistee(uint32 id = 0) { _assistee = id; } bool GetBotPrecombat() { return m_bot_precombat; } void SetBotPrecombat(bool flag = true) { m_bot_precombat = flag; } @@ -2257,6 +2259,7 @@ private: uint8 cure_min_threshold; uint8 cure_threshold; bool illusion_block; + uint32 _assistee; bool CanTradeFVNoDropItem(); void SendMobPositions(); From 69f724a2b9411936b567fe681a64ae0211a5f3a7 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 5 Dec 2024 23:46:37 -0600 Subject: [PATCH 64/97] Move HateLine to a better ID --- .../database_update_manifest_bots.cpp | 166 +++++++++--------- common/spdat.h | 68 +++---- zone/botspellsai.cpp | 2 +- 3 files changed, 118 insertions(+), 118 deletions(-) diff --git a/common/database/database_update_manifest_bots.cpp b/common/database/database_update_manifest_bots.cpp index 235c160b3..d93ec37bf 100644 --- a/common/database/database_update_manifest_bots.cpp +++ b/common/database/database_update_manifest_bots.cpp @@ -1077,7 +1077,7 @@ WHERE bot_spells_entries.spell_id = spells_new.id); ManifestEntry{ .version = 9051, .description = "2024_11_26_remove_sk_icb.sql", - .check = "SELECT * FROM `bot_spells_entries` where `type` = 55", + .check = "SELECT * FROM `bot_spells_entries` where `type` = 24", .condition = "empty", .match = "", .sql = R"( @@ -1088,88 +1088,88 @@ AND `type` = 10; INSERT INTO `bot_spells_entries` (`npc_spells_id`, `spell_id`, `type`, `minlevel`, `maxlevel`) VALUES -(3003, 10175, 55, 72, 76), -(3003, 10173, 55, 72, 76), -(3003, 10174, 55, 72, 76), -(3003, 14956, 55, 77, 81), -(3003, 14954, 55, 77, 81), -(3003, 14955, 55, 77, 81), -(3003, 19070, 55, 82, 86), -(3003, 19068, 55, 82, 86), -(3003, 19069, 55, 82, 86), -(3003, 25298, 55, 87, 91), -(3003, 25299, 55, 87, 91), -(3003, 25297, 55, 87, 91), -(3003, 28348, 55, 92, 96), -(3003, 28349, 55, 92, 96), -(3003, 28347, 55, 92, 96), -(3003, 34351, 55, 97, 254), -(3003, 34352, 55, 97, 254), -(3003, 34350, 55, 97, 254), -(3003, 40078, 55, 98, 254), -(3003, 40079, 55, 98, 254), -(3003, 40080, 55, 98, 254), -(3005, 1221, 55, 33, 41), -(3005, 1222, 55, 42, 52), -(3005, 1223, 55, 53, 58), -(3005, 1224, 55, 59, 62), -(3005, 3405, 55, 63, 66), -(3005, 5329, 55, 67, 70), -(3005, 5336, 55, 69, 73), -(3005, 10258, 55, 71, 71), -(3005, 10259, 55, 71, 71), -(3005, 10257, 55, 71, 71), -(3005, 10261, 55, 72, 76), -(3005, 10262, 55, 72, 76), -(3005, 10260, 55, 72, 76), -(3005, 10291, 55, 74, 78), -(3005, 10292, 55, 74, 78), -(3005, 10293, 55, 74, 78), -(3005, 15160, 55, 76, 76), -(3005, 15161, 55, 76, 76), -(3005, 15162, 55, 76, 76), -(3005, 15165, 55, 77, 81), -(3005, 15163, 55, 77, 81), -(3005, 15164, 55, 77, 81), -(3005, 15186, 55, 79, 83), -(3005, 15184, 55, 79, 83), -(3005, 15185, 55, 79, 83), -(3005, 19315, 55, 81, 81), -(3005, 19313, 55, 81, 81), -(3005, 19314, 55, 81, 81), -(3005, 19317, 55, 82, 86), -(3005, 19318, 55, 82, 86), -(3005, 19316, 55, 82, 86), -(3005, 19338, 55, 84, 88), -(3005, 19339, 55, 84, 88), -(3005, 19337, 55, 84, 88), -(3005, 25581, 55, 86, 86), -(3005, 25582, 55, 86, 86), -(3005, 25580, 55, 86, 86), -(3005, 25586, 55, 87, 91), -(3005, 25587, 55, 87, 91), -(3005, 25588, 55, 87, 91), -(3005, 25641, 55, 89, 93), -(3005, 25642, 55, 89, 93), -(3005, 25643, 55, 89, 93), -(3005, 28659, 55, 91, 91), -(3005, 28657, 55, 91, 91), -(3005, 28658, 55, 91, 91), -(3005, 28665, 55, 92, 96), -(3005, 28663, 55, 92, 96), -(3005, 28664, 55, 92, 96), -(3005, 28735, 55, 94, 98), -(3005, 28733, 55, 94, 98), -(3005, 28734, 55, 94, 98), -(3005, 34688, 55, 96, 96), -(3005, 34689, 55, 96, 96), -(3005, 34687, 55, 96, 96), -(3005, 34694, 55, 97, 254), -(3005, 34695, 55, 97, 254), -(3005, 34693, 55, 97, 254), -(3005, 34752, 55, 99, 254), -(3005, 34753, 55, 99, 254), -(3005, 34751, 55, 99, 254); +(3003, 10175, 24, 72, 76), +(3003, 10173, 24, 72, 76), +(3003, 10174, 24, 72, 76), +(3003, 14956, 24, 77, 81), +(3003, 14954, 24, 77, 81), +(3003, 14955, 24, 77, 81), +(3003, 19070, 24, 82, 86), +(3003, 19068, 24, 82, 86), +(3003, 19069, 24, 82, 86), +(3003, 25298, 24, 87, 91), +(3003, 25299, 24, 87, 91), +(3003, 25297, 24, 87, 91), +(3003, 28348, 24, 92, 96), +(3003, 28349, 24, 92, 96), +(3003, 28347, 24, 92, 96), +(3003, 34351, 24, 97, 254), +(3003, 34352, 24, 97, 254), +(3003, 34350, 24, 97, 254), +(3003, 40078, 24, 98, 254), +(3003, 40079, 24, 98, 254), +(3003, 40080, 24, 98, 254), +(3005, 1221, 24, 33, 41), +(3005, 1222, 24, 42, 52), +(3005, 1223, 24, 53, 58), +(3005, 1224, 24, 59, 62), +(3005, 3405, 24, 63, 66), +(3005, 5329, 24, 67, 70), +(3005, 5336, 24, 69, 73), +(3005, 10258, 24, 71, 71), +(3005, 10259, 24, 71, 71), +(3005, 10257, 24, 71, 71), +(3005, 10261, 24, 72, 76), +(3005, 10262, 24, 72, 76), +(3005, 10260, 24, 72, 76), +(3005, 10291, 24, 74, 78), +(3005, 10292, 24, 74, 78), +(3005, 10293, 24, 74, 78), +(3005, 15160, 24, 76, 76), +(3005, 15161, 24, 76, 76), +(3005, 15162, 24, 76, 76), +(3005, 15165, 24, 77, 81), +(3005, 15163, 24, 77, 81), +(3005, 15164, 24, 77, 81), +(3005, 15186, 24, 79, 83), +(3005, 15184, 24, 79, 83), +(3005, 15185, 24, 79, 83), +(3005, 19315, 24, 81, 81), +(3005, 19313, 24, 81, 81), +(3005, 19314, 24, 81, 81), +(3005, 19317, 24, 82, 86), +(3005, 19318, 24, 82, 86), +(3005, 19316, 24, 82, 86), +(3005, 19338, 24, 84, 88), +(3005, 19339, 24, 84, 88), +(3005, 19337, 24, 84, 88), +(3005, 25581, 24, 86, 86), +(3005, 25582, 24, 86, 86), +(3005, 25580, 24, 86, 86), +(3005, 25586, 24, 87, 91), +(3005, 25587, 24, 87, 91), +(3005, 25588, 24, 87, 91), +(3005, 25641, 24, 89, 93), +(3005, 25642, 24, 89, 93), +(3005, 25643, 24, 89, 93), +(3005, 28659, 24, 91, 91), +(3005, 28657, 24, 91, 91), +(3005, 28658, 24, 91, 91), +(3005, 28665, 24, 92, 96), +(3005, 28663, 24, 92, 96), +(3005, 28664, 24, 92, 96), +(3005, 28735, 24, 94, 98), +(3005, 28733, 24, 94, 98), +(3005, 28734, 24, 94, 98), +(3005, 34688, 24, 96, 96), +(3005, 34689, 24, 96, 96), +(3005, 34687, 24, 96, 96), +(3005, 34694, 24, 97, 254), +(3005, 34695, 24, 97, 254), +(3005, 34693, 24, 97, 254), +(3005, 34752, 24, 99, 254), +(3005, 34753, 24, 99, 254), +(3005, 34751, 24, 99, 254); DELETE FROM bot_spells_entries diff --git a/common/spdat.h b/common/spdat.h index e7a266459..fe94a82a4 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -677,39 +677,39 @@ namespace BotSpellTypes constexpr uint16 PreCombatBuffSong = 21; constexpr uint16 Fear = 22; constexpr uint16 Stun = 23; - constexpr uint16 GroupCures = 24; - constexpr uint16 CompleteHeal = 25; - constexpr uint16 FastHeals = 26; - constexpr uint16 VeryFastHeals = 27; - constexpr uint16 GroupHeals = 28; - constexpr uint16 GroupCompleteHeals = 29; - constexpr uint16 GroupHoTHeals = 30; - constexpr uint16 HoTHeals = 31; - constexpr uint16 AENukes = 32; - constexpr uint16 AERains = 33; - constexpr uint16 AEMez = 34; - constexpr uint16 AEStun = 35; - constexpr uint16 AEDebuff = 36; - constexpr uint16 AESlow = 37; - constexpr uint16 AESnare = 38; - constexpr uint16 AEFear = 39; - constexpr uint16 AEDispel = 40; - constexpr uint16 AERoot = 41; - constexpr uint16 AEDoT = 42; - constexpr uint16 AELifetap = 43; - constexpr uint16 PBAENuke = 44; - constexpr uint16 PetBuffs = 45; - constexpr uint16 PetRegularHeals = 46; - constexpr uint16 PetCompleteHeals = 47; - constexpr uint16 PetFastHeals = 48; - constexpr uint16 PetVeryFastHeals = 49; - constexpr uint16 PetHoTHeals = 50; - constexpr uint16 DamageShields = 51; - constexpr uint16 ResistBuffs = 52; - constexpr uint16 PetDamageShields = 53; - constexpr uint16 PetResistBuffs = 54; - constexpr uint16 HateLine = 55; - constexpr uint16 AEHateLine = 56; + constexpr uint16 HateLine = 24; + constexpr uint16 GroupCures = 25; + constexpr uint16 CompleteHeal = 26; + constexpr uint16 FastHeals = 27; + constexpr uint16 VeryFastHeals = 28; + constexpr uint16 GroupHeals = 29; + constexpr uint16 GroupCompleteHeals = 30; + constexpr uint16 GroupHoTHeals = 31; + constexpr uint16 HoTHeals = 32; + constexpr uint16 AENukes = 33; + constexpr uint16 AERains = 34; + constexpr uint16 AEMez = 35; + constexpr uint16 AEStun = 36; + constexpr uint16 AEDebuff = 37; + constexpr uint16 AESlow = 38; + constexpr uint16 AESnare = 39; + constexpr uint16 AEFear = 40; + constexpr uint16 AEDispel = 41; + constexpr uint16 AERoot = 42; + constexpr uint16 AEDoT = 43; + constexpr uint16 AELifetap = 44; + constexpr uint16 AEHateLine = 45; + constexpr uint16 PBAENuke = 46; + constexpr uint16 PetBuffs = 47; + constexpr uint16 PetRegularHeals = 48; + constexpr uint16 PetCompleteHeals = 49; + constexpr uint16 PetFastHeals = 50; + constexpr uint16 PetVeryFastHeals = 51; + constexpr uint16 PetHoTHeals = 52; + constexpr uint16 DamageShields = 53; + constexpr uint16 ResistBuffs = 54; + constexpr uint16 PetDamageShields = 55; + constexpr uint16 PetResistBuffs = 56; // Command Spell Types constexpr uint16 Teleport = 100; // this is handled by ^depart so uses other logic @@ -727,7 +727,7 @@ namespace BotSpellTypes constexpr uint16 SummonCorpse = 112; constexpr uint16 START = BotSpellTypes::Nuke; // Do not remove or change this - constexpr uint16 END = BotSpellTypes::AEHateLine; // Do not remove this, increment as needed + constexpr uint16 END = BotSpellTypes::PetResistBuffs; // Do not remove this, increment as needed constexpr uint16 COMMANDED_START = BotSpellTypes::Lull; // Do not remove or change this constexpr uint16 COMMANDED_END = BotSpellTypes::SummonCorpse; // Do not remove this, increment as needed } diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index a5718a2dc..f83455cc8 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -1045,7 +1045,7 @@ std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa ) { continue; } - if (spellType == debugSpellType) { LogTestDebugDetail("{} - #{}: [{} #{}] - {} says, '{} #{} - Passed TGB checks.'", __FILE__, __LINE__, botCaster->GetSpellTypeNameByID(spellType), spellType, botCaster->GetCleanName(), spells[botSpellList[i].spellid].name, botSpellList[i].spellid); }; //deleteme + if (!IsPBAESpell(botSpellList[i].spellid) && !botCaster->CastChecks(botSpellList[i].spellid, tar, spellType, false, IsAEBotSpellType(spellType))) { continue; } From 67ff004d617037ee30160aa8a34e09b5c1f61489 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:38:37 -0600 Subject: [PATCH 65/97] cleanup ST_Self logic in CastChecks --- zone/bot.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 6f3ab2e50..5b858ecda 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9541,10 +9541,10 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec } if (spells[spell_id].target_type == ST_Self && tar != this) { - if (IsEffectInSpell(spell_id, SE_SummonCorpse) && RuleB(Bots, AllowCommandedSummonCorpse)) { - //tar = this; - } - else { + if ( + !IsEffectInSpell(spell_id, SE_SummonCorpse) || + (IsEffectInSpell(spell_id, SE_SummonCorpse) && !RuleB(Bots, AllowCommandedSummonCorpse)) + ) { tar = this; } } From db26842194ffc9b3e4ec91aa5abdbce58bbc11d4 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:38:59 -0600 Subject: [PATCH 66/97] Removed unused BotSpellTypeRequiresLoS --- common/spdat.cpp | 39 --------------------------------------- common/spdat.h | 1 - 2 files changed, 40 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index 8a21e23b9..2d36d4916 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -3297,42 +3297,3 @@ bool IsPullingSpellType(uint16 spellType) { return false; } - -bool BotSpellTypeRequiresLoS(uint16 spellType, uint8 cls) { - switch (spellType) { - case BotSpellTypes::Nuke: - case BotSpellTypes::Root: - case BotSpellTypes::Lifetap: - case BotSpellTypes::Snare: - case BotSpellTypes::DOT: - case BotSpellTypes::Dispel: - case BotSpellTypes::Mez: - //case BotSpellTypes::Charm: // commanded - case BotSpellTypes::Slow: - case BotSpellTypes::Debuff: - case BotSpellTypes::HateRedux: - //case BotSpellTypes::Fear: // commanded - case BotSpellTypes::Stun: - case BotSpellTypes::AENukes: - case BotSpellTypes::AERains: - case BotSpellTypes::AEMez: - case BotSpellTypes::AEStun: - case BotSpellTypes::AEDebuff: - case BotSpellTypes::AESlow: - case BotSpellTypes::AESnare: - //case BotSpellTypes::AEFear: // commanded - case BotSpellTypes::AEDispel: - case BotSpellTypes::AERoot: - case BotSpellTypes::AEDoT: - case BotSpellTypes::AELifetap: - case BotSpellTypes::PBAENuke: - // case BotSpellTypes::Lull: // commanded - case BotSpellTypes::HateLine: - case BotSpellTypes::AEHateLine: - return true; - default: - return false; - } - - return false; -} diff --git a/common/spdat.h b/common/spdat.h index fe94a82a4..01cdf85af 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -752,7 +752,6 @@ bool SpellTypeRequiresCastChecks(uint16 spellType); bool SpellTypeRequiresAEChecks(uint16 spellType); bool IsCommandedSpellType(uint16 spellType); bool IsPullingSpellType(uint16 spellType); -bool BotSpellTypeRequiresLoS(uint16 spellType, uint8 cls); // These should not be used to determine spell category.. // They are a graphical affects (effects?) index only From 0f3c1129725b27c934543487aa09925d7ea15d03 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Fri, 6 Dec 2024 22:29:18 -0600 Subject: [PATCH 67/97] [Bots] Fix AA ranks to account for level Previously level requirement was only being checked on the initial rank of an AA. If passed, bots would gain all ranks for that AA regardless of level, this will now check for the level requirement for each rank before granting the AA --- zone/bot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 5b858ecda..a190d3315 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -1285,7 +1285,7 @@ void Bot::LoadAAs() { } while(current) { - if (!CanUseAlternateAdvancementRank(current)) { + if (current->level_req > GetLevel() || !CanUseAlternateAdvancementRank(current)) { current = nullptr; } else { current = current->next; From 347a916bbb1f6570429b04638eb67e5413150b41 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Fri, 6 Dec 2024 22:32:34 -0600 Subject: [PATCH 68/97] Move fizzle message to define --- zone/bot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index a190d3315..24394d5b6 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -5834,7 +5834,7 @@ bool Bot::CastSpell( if (DivineAura()) { LogSpellsDetail("Spell casting canceled: cannot cast while Divine Aura is in effect"); - InterruptSpell(173, 0x121, false); + InterruptSpell(SPELL_FIZZLE, 0x121, false); return false; } From 6d6fd9ee15e2b9f17772a7c3ad4a00078f57117c Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Fri, 6 Dec 2024 22:33:32 -0600 Subject: [PATCH 69/97] add timer checks to Idle/Engaged/Pursue CastCheck to early terminate --- zone/botspellsai.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index f83455cc8..410aa7e69 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -633,7 +633,7 @@ bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain } bool Bot::AI_PursueCastCheck() { - if (GetAppearance() == eaDead || IsFeared() || IsSilenced() || IsAmnesiad() || GetHP() < 0) { + if (GetAppearance() == eaDead || delaytimer || spellend_timer.Enabled() || IsFeared() || IsSilenced() || IsAmnesiad() || GetHP() < 0) { return false; } @@ -682,7 +682,7 @@ bool Bot::AI_PursueCastCheck() { } bool Bot::AI_IdleCastCheck() { - if (GetAppearance() == eaDead || IsFeared() || IsSilenced() || IsAmnesiad() || GetHP() < 0) { + if (GetAppearance() == eaDead || delaytimer || spellend_timer.Enabled() || IsFeared() || IsSilenced() || IsAmnesiad() || GetHP() < 0) { return false; } @@ -745,7 +745,7 @@ bool Bot::AI_IdleCastCheck() { } bool Bot::AI_EngagedCastCheck() { - if (GetAppearance() == eaDead || IsFeared() || IsSilenced() || IsAmnesiad() || GetHP() < 0) { + if (GetAppearance() == eaDead || delaytimer || spellend_timer.Enabled() || IsFeared() || IsSilenced() || IsAmnesiad() || GetHP() < 0) { return false; } From 221333a7bacd6d5c5abf1d33b862b1a1ffc89b23 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 8 Dec 2024 22:32:49 -0600 Subject: [PATCH 70/97] Add back !IsBotNonSpellFighter() check to the different CastCheck --- zone/bot.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 24394d5b6..bdd3a32e5 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2368,10 +2368,10 @@ void Bot::AI_Process() if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) { return; } - if (AI_HasSpells() && TryIdleChecks(fm_distance)) { + if (!IsBotNonSpellFighter() && AI_HasSpells() && TryIdleChecks(fm_distance)) { return; } - if (AI_HasSpells() && TryBardMovementCasts()) { + if (!IsBotNonSpellFighter() && AI_HasSpells() && TryBardMovementCasts()) { return; } } @@ -2663,7 +2663,7 @@ bool Bot::TryPursueTarget(float leash_distance, glm::vec3& Goal) { } // This is a mob that is fleeing either because it has been feared or is low on hitpoints - if (!HOLDING && AI_HasSpells()) { + if (!HOLDING && !IsBotNonSpellFighter() && AI_HasSpells()) { AI_PursueCastCheck(); } From 9917be3094c5684d50e4dd58d83bb3373977741f Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 8 Dec 2024 22:35:36 -0600 Subject: [PATCH 71/97] Correct IsValidSpellRange --- zone/bot.cpp | 128 +++++++++++++++++++++++++++++++++++++++---- zone/botspellsai.cpp | 47 +++++++++------- 2 files changed, 146 insertions(+), 29 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index bdd3a32e5..523f6c918 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9690,16 +9690,6 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsImmuneToBotSpell.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - - if (!IsCommandedSpell() && !IsTaunting() && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting(), spell_id) && !tar->IsFleeing()) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to HasOrMayGetAggro.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme - return false; - } - - if (!DoResistCheckBySpellType(tar, spell_id, spellType)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to DoResistCheckBySpellType.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme - return false; - } if ( (RequiresStackCheck(spellType) || (!RequiresStackCheck(spellType) && CalcBuffDuration(this, tar, spell_id) != 0)) @@ -9714,6 +9704,20 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec return false; } + if (spellType == UINT16_MAX) { //AA cast checks, return here + return true; + } + + if (!IsTaunting() && GetSpellTypeAggroCheck(spellType) && HasOrMayGetAggro(IsSitting(), spell_id) && !tar->IsFleeing()) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to HasOrMayGetAggro.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + + if (!DoResistCheckBySpellType(tar, spell_id, spellType)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to DoResistCheckBySpellType.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + return true; } @@ -11095,6 +11099,81 @@ bool Bot::AttemptAICastSpell(uint16 spellType) { return result; } +bool Bot::AttemptAACastSpell(Mob* tar, uint16 spell_id, AA::Rank* rank) { + if (!tar) { + tar = this; + } + + if (!DoLosChecks(this, tar)) { + return false; + } + + if (CheckSpellRecastTimer(spell_id)) { + if (IsBeneficialSpell(spell_id)) { + if ( + (tar->IsNPC() && !tar->GetOwner()) || + (tar->GetOwner() && tar->GetOwner()->IsOfClientBot() && !GetOwner()->IsInGroupOrRaid(tar->GetOwner())) || + (tar->IsOfClientBot() && !GetOwner()->IsInGroupOrRaid(tar)) + ) { + GetBotOwner()->Message(Chat::Yellow, "[%s] is an invalid target. Only players or their pet in your group or raid are eligible targets.", tar->GetCleanName()); + + return false; + } + } + + if (IsDetrimentalSpell(spell_id) && !IsAttackAllowed(tar)) { + GetBotOwner()->Message(Chat::Yellow, "%s says, 'I cannot attack [%s]'.", GetCleanName(), tar->GetCleanName()); + + return false; + } + + if (!CastChecks(spell_id, tar, UINT16_MAX)) { + GetBotOwner()->Message(Chat::Red, "%s says, 'Ability failed to cast. This could be due to this to any number of things: range, mana, immune, etc.'", GetCleanName()); + + return false; + } + + + if (CastSpell(spell_id, tar->GetID())) { + BotGroupSay( + this, + fmt::format( + "Casting {} on {}.", + GetSpellName(spell_id), + (tar == this ? "myself" : tar->GetCleanName()) + ).c_str() + ); + + int timer_duration = (rank->recast_time - GetAlternateAdvancementCooldownReduction(rank)) * 1000; + + if (timer_duration < 0) { + timer_duration = 0; + } + + SetSpellRecastTimer(spell_id, timer_duration); + } + else { + GetBotOwner()->Message(Chat::Red, "%s says, 'Ability failed to cast. This could be due to this to any number of things: range, mana, immune, etc.'", GetCleanName()); + + return false; + } + } + else { + GetBotOwner()->Message( + Chat::Yellow, + fmt::format( + "{} says, 'Ability recovery time not yet met. {} remaining.'", + GetCleanName(), + Strings::SecondsToTime(GetSpellRecastRemainingTime(spell_id), true) + ).c_str() + ); + + return false; + } + + return true; +} + uint16 Bot::GetSpellListSpellType(uint16 spellType) { switch (spellType) { case BotSpellTypes::AENukes: @@ -11890,3 +11969,32 @@ bool Bot::IsValidSpellTypeSubType(uint16 spellType, uint16 subType, uint16 spell return false; } + +uint16 Bot::GetSpellByAA(int id, AA::Rank*& rank) { + uint16 spell_id = 0; + std::pair aa_ability = std::make_pair(nullptr, nullptr); + AA::Ability* ability = zone->GetAlternateAdvancementAbility(id); + + if (!ability || !ability->first_rank_id) { + return spell_id; + } + + uint32 points = GetAA(ability->first_rank_id); + //if (points) { LogTestDebug("{}: {} says, '{} points for {} [#{} - {}] rank {}'", __LINE__, GetCleanName(), points, zone->GetAAName(aa_ability.first->id), aa_ability.first->id, aa_ability.second->id, points); } //deleteme + if (points > 0) { + aa_ability = zone->GetAlternateAdvancementAbilityAndRank(ability->id, points); + } + + rank = aa_ability.second; + + if (!points || !rank) { + LogTestDebug("{}: {} says, 'No {} found'", __LINE__, GetCleanName(), (!points ? "points" : "rank")); //deleteme + return spell_id; + } + + spell_id = rank->spell; + + LogTestDebug("{}: {} says, 'Found {} [#{}]'", __LINE__, GetCleanName(), spells[spell_id].name, spell_id); //deleteme + + return spell_id; +} diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 410aa7e69..b4588811d 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -2669,31 +2669,40 @@ bool Bot::HasBotSpellEntry(uint16 spell_id) { } bool Bot::IsValidSpellRange(uint16 spell_id, Mob* tar) { - if (!IsValidSpell(spell_id)) { + if (!IsValidSpell(spell_id) || !tar) { return false; } - if (tar) { - float range = spells[spell_id].range; - - if (tar != this) { - range += GetRangeDistTargetSizeMod(tar); - } + float range = spells[spell_id].range + GetRangeDistTargetSizeMod(tar); - range = GetActSpellRange(spell_id, range); - - if (range >= Distance(m_Position, tar->GetPosition())) { - return true; - } - - range = GetActSpellRange(spell_id, spells[spell_id].aoe_range); - - if (range >= Distance(m_Position, tar->GetPosition())) { - return true; - } + if (IsAnyAESpell(spell_id)) { + range = GetAOERange(spell_id); + } + + if (RuleB(Bots, EnableBotTGB) && IsTGBCompatibleSpell(spell_id) && IsGroupSpell(spell_id)) { + range = spells[spell_id].aoe_range; } - return false; + range = GetActSpellRange(spell_id, range); + + if (IsIllusionSpell(spell_id) && (HasProjectIllusion())) { + range = 100; + } + + float dist2 = DistanceSquared(m_Position, tar->GetPosition()); + float range2 = range * range; + float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range; + + if (dist2 > range2) { + //target is out of range. + return false; + } + else if (dist2 < min_range2) { + //target is too close range. + return false; + } + + return true; } BotSpell Bot::GetBestBotSpellForNukeByBodyType(Bot* botCaster, uint8 bodyType, uint16 spellType, bool AE, Mob* tar) { From ef36f2873c6b1e19cd6329f98f03c6c5ac8f0318 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 8 Dec 2024 22:36:56 -0600 Subject: [PATCH 72/97] Implement AAs and harmtouch/layonhands to ^cast --- fix IsValidSpellRange --- zone/bot.cpp | 4 +- zone/bot.h | 2 + zone/bot_commands/cast.cpp | 410 +++++++++++++++++++++---------------- zone/spells.cpp | 2 +- 4 files changed, 242 insertions(+), 176 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 523f6c918..347689f20 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9750,7 +9750,7 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spell_id, Mob* tar) { case BotSpellTypes::Invisibility: case BotSpellTypes::MovementSpeed: case BotSpellTypes::SendHome: - if ( + if ( // TODO bot rewrite - fix this, missing other target types (43 for example) !( spells[spell_id].target_type == ST_Target || spells[spell_id].target_type == ST_Pet || @@ -9759,7 +9759,7 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spell_id, Mob* tar) { spells[spell_id].target_type == ST_GroupTeleport ) ) { - LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to target_type checks.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to target_type checks. Using {}'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName(), GetSpellTargetType(spell_id)); //deleteme return false; } diff --git a/zone/bot.h b/zone/bot.h index 9e187d216..3371a6b99 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -402,6 +402,7 @@ public: // AI Methods bool AICastSpell(Mob* tar, uint8 iChance, uint16 spellType, uint16 subTargetType = UINT16_MAX, uint16 subType = UINT16_MAX); bool AttemptAICastSpell(uint16 spellType); + bool AttemptAACastSpell(Mob* tar, uint16 spell_id, AA::Rank* rank); bool AI_EngagedCastCheck() override; bool AI_PursueCastCheck() override; bool AI_IdleCastCheck() override; @@ -478,6 +479,7 @@ public: void LoadDefaultBotSettings(); void SetBotSpellRecastTimer(uint16 spellType, Mob* spelltar, bool preCast = false); BotSpell GetSpellByHealType(uint16 spellType, Mob* tar); + uint16 GetSpellByAA(int id, AA::Rank* &rank); std::string GetBotSpellCategoryName(uint8 setting_type); std::string GetBotSettingCategoryName(uint8 setting_type); diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index a52406810..7915a491c 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -11,7 +11,13 @@ void bot_command_cast(Client* c, const Seperator* sep) std::vector notes = { "- This will interrupt any spell currently being cast by bots told to use the command.", - "- Bots will still check to see if they have the spell in their spell list, whether the target is immune, spell is allowed and all other sanity checks for spells" + "- Bots will still check to see if they have the spell in their spell list, whether the target is immune, spell is allowed and all other sanity checks for spells", + fmt::format( + "- You can use {} aa # to cast any clickable AA or specifically {} harmtouch / {} layonhands" + , sep->arg[0] + , sep->arg[0] + , sep->arg[0] + ) }; std::vector example_format = @@ -29,12 +35,12 @@ void bot_command_cast(Client* c, const Seperator* sep) { "To tell everyone to Nuke the target:", fmt::format( - "{} {} spawned", + "{} {}", sep->arg[0], c->GetSpellTypeShortNameByID(BotSpellTypes::Nuke) ), fmt::format( - "{} {} spawned", + "{} {}", sep->arg[0], BotSpellTypes::Nuke ) @@ -57,16 +63,14 @@ void bot_command_cast(Client* c, const Seperator* sep) }; std::vector examples_three = { - "To tell Clrbot to resurrect the targeted corpse:", + "To tell Skbot to Harm Touch the target:", fmt::format( - "{} {} byname Clrbot", - sep->arg[0], - c->GetSpellTypeShortNameByID(BotSpellTypes::Resurrect) + "{} aa 6000 byname Skbot", + sep->arg[0] ), fmt::format( - "{} {} byname Clrbot", - sep->arg[0], - BotSpellTypes::Resurrect + "{} harmtouch byname Skbot", + sep->arg[0] ) }; @@ -120,6 +124,11 @@ void bot_command_cast(Client* c, const Seperator* sep) std::string arg1 = sep->arg[1]; std::string arg2 = sep->arg[2]; + //AA help + if (!arg1.compare("aa") && !arg2.compare("help")) { + c->Message(Chat::Yellow, "Enter the ID of an AA to attempt to cast.", sep->arg[0]); + } + //Commanded type help prompts if (!arg2.compare("help")) { c->Message(Chat::Yellow, "You can also use [single], [group], [ae]. Ex: ^cast movementspeed group.", sep->arg[0]); @@ -174,144 +183,168 @@ void bot_command_cast(Client* c, const Seperator* sep) } int ab_arg = 2; - uint16 spellType = 0; - - // String/Int type checks - if (sep->IsNumber(1)) { - spellType = atoi(sep->arg[1]); - - if (spellType < BotSpellTypes::START || (spellType > BotSpellTypes::END && spellType < BotSpellTypes::COMMANDED_START) || spellType > BotSpellTypes::COMMANDED_END) { - c->Message( - Chat::Yellow, - fmt::format( - "You must choose a valid spell type. Use {} for information regarding this command.", - Saylink::Silent( - fmt::format("{} help", sep->arg[0]) - ) - ).c_str() - ); - - return; - } - } - else { - if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { - spellType = c->GetSpellTypeIDByShortName(arg1); - } - else { - c->Message( - Chat::Yellow, - fmt::format( - "Incorrect argument, use {} for information regarding this command.", - Saylink::Silent( - fmt::format("{} help", sep->arg[0]) - ) - ).c_str() - ); - - return; - } - } - - switch (spellType) { //Allowed command checks - case BotSpellTypes::Charm: - if (!RuleB(Bots, AllowCommandedCharm)) { - c->Message(Chat::Yellow, "This commanded type is currently disabled."); - return; - } - - break; - case BotSpellTypes::AEMez: - case BotSpellTypes::Mez: - if (!RuleB(Bots, AllowCommandedMez)) { - c->Message(Chat::Yellow, "This commanded type is currently disabled."); - return; - } - - break; - case BotSpellTypes::Resurrect: - if (!RuleB(Bots, AllowCommandedResurrect)) { - c->Message(Chat::Yellow, "This commanded type is currently disabled."); - return; - } - - break; - case BotSpellTypes::SummonCorpse: - if (!RuleB(Bots, AllowCommandedSummonCorpse)) { - c->Message(Chat::Yellow, "This commanded type is currently disabled."); - return; - } - - break; - default: - break; - } - - std::string argString = sep->arg[ab_arg]; + uint16 spellType = UINT16_MAX; uint16 subType = UINT16_MAX; uint16 subTargetType = UINT16_MAX; - - if (!argString.compare("shrink")) { - subType = CommandedSubTypes::Shrink; - ++ab_arg; - } - else if (!argString.compare("grow")) { - subType = CommandedSubTypes::Grow; - ++ab_arg; - } - else if (!argString.compare("see")) { - subType = CommandedSubTypes::SeeInvis; - ++ab_arg; - } - else if (!argString.compare("invis")) { - subType = CommandedSubTypes::Invis; - ++ab_arg; - } - else if (!argString.compare("undead")) { - subType = CommandedSubTypes::InvisUndead; - ++ab_arg; - } - else if (!argString.compare("animals")) { - subType = CommandedSubTypes::InvisAnimals; - ++ab_arg; - } - else if (!argString.compare("selo")) { - subType = CommandedSubTypes::Selo; - ++ab_arg; + bool aaType = false; + int aaID = 0; + + if (!arg1.compare("aa") || !arg1.compare("harmtouch") || !arg1.compare("layonhands")) { + if (!arg1.compare("harmtouch")) { + aaID = zone->GetAlternateAdvancementAbilityByRank(aaHarmTouch)->id; + } + else if (!arg1.compare("layonhands")) { + aaID = zone->GetAlternateAdvancementAbilityByRank(aaLayonHands)->id; + } + else if (!sep->IsNumber(2) || !zone->GetAlternateAdvancementAbility(Strings::ToInt(arg2))) { + c->Message(Chat::Yellow, "You must enter an AA ID."); + return; + } + else { + ++ab_arg; + aaID = Strings::ToInt(arg2); + } + + aaType = true; } - argString = sep->arg[ab_arg]; + if (!aaType) { + // String/Int type checks + if (sep->IsNumber(1)) { + spellType = atoi(sep->arg[1]); - if (!argString.compare("single")) { - subTargetType = CommandedSubTypes::SingleTarget; - ++ab_arg; - } - else if (!argString.compare("group")) { - subTargetType = CommandedSubTypes::GroupTarget; - ++ab_arg; - } - else if (!argString.compare("ae")) { - subTargetType = CommandedSubTypes::AETarget; - ++ab_arg; - } + if (spellType < BotSpellTypes::START || (spellType > BotSpellTypes::END && spellType < BotSpellTypes::COMMANDED_START) || spellType > BotSpellTypes::COMMANDED_END) { + c->Message( + Chat::Yellow, + fmt::format( + "You must choose a valid spell type. Use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); - if ( - spellType == BotSpellTypes::PetBuffs || - spellType == BotSpellTypes::PetCompleteHeals || - spellType == BotSpellTypes::PetFastHeals || - spellType == BotSpellTypes::PetHoTHeals || - spellType == BotSpellTypes::PetRegularHeals || - spellType == BotSpellTypes::PetVeryFastHeals - ) { - c->Message(Chat::Yellow, "Pet type heals and buffs are not supported, use the regular spell type."); - return; + return; + } + } + else { + if (c->GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spellType = c->GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + switch (spellType) { //Allowed command checks + case BotSpellTypes::Charm: + if (!RuleB(Bots, AllowCommandedCharm)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + break; + case BotSpellTypes::AEMez: + case BotSpellTypes::Mez: + if (!RuleB(Bots, AllowCommandedMez)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + break; + case BotSpellTypes::Resurrect: + if (!RuleB(Bots, AllowCommandedResurrect)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + break; + case BotSpellTypes::SummonCorpse: + if (!RuleB(Bots, AllowCommandedSummonCorpse)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + break; + default: + break; + } + + std::string argString = sep->arg[ab_arg]; + + + if (!argString.compare("shrink")) { + subType = CommandedSubTypes::Shrink; + ++ab_arg; + } + else if (!argString.compare("grow")) { + subType = CommandedSubTypes::Grow; + ++ab_arg; + } + else if (!argString.compare("see")) { + subType = CommandedSubTypes::SeeInvis; + ++ab_arg; + } + else if (!argString.compare("invis")) { + subType = CommandedSubTypes::Invis; + ++ab_arg; + } + else if (!argString.compare("undead")) { + subType = CommandedSubTypes::InvisUndead; + ++ab_arg; + } + else if (!argString.compare("animals")) { + subType = CommandedSubTypes::InvisAnimals; + ++ab_arg; + } + else if (!argString.compare("selo")) { + subType = CommandedSubTypes::Selo; + ++ab_arg; + } + + argString = sep->arg[ab_arg]; + + if (!argString.compare("single")) { + subTargetType = CommandedSubTypes::SingleTarget; + ++ab_arg; + } + else if (!argString.compare("group")) { + subTargetType = CommandedSubTypes::GroupTarget; + ++ab_arg; + } + else if (!argString.compare("ae")) { + subTargetType = CommandedSubTypes::AETarget; + ++ab_arg; + } + + if ( + spellType == BotSpellTypes::PetBuffs || + spellType == BotSpellTypes::PetCompleteHeals || + spellType == BotSpellTypes::PetFastHeals || + spellType == BotSpellTypes::PetHoTHeals || + spellType == BotSpellTypes::PetRegularHeals || + spellType == BotSpellTypes::PetVeryFastHeals + ) { + c->Message(Chat::Yellow, "Pet type heals and buffs are not supported, use the regular spell type."); + return; + } } Mob* tar = c->GetTarget(); //LogTestDebug("{}: 'Attempting {} [{}-{}] on {}'", __LINE__, c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (subTargetType != UINT16_MAX ? c->GetSubTypeNameByID(subTargetType) : "Standard"), (tar ? tar->GetCleanName() : "NOBODY")); //deleteme if (!tar) { - if (spellType != BotSpellTypes::Escape && spellType != BotSpellTypes::Pet) { + if (!aaType && spellType != BotSpellTypes::Escape && spellType != BotSpellTypes::Pet) { c->Message(Chat::Yellow, "You need a target for that."); return; } @@ -356,10 +389,11 @@ void bot_command_cast(Client* c, const Seperator* sep) if (IsBotSpellTypeBeneficial(spellType)) { if ( - (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) || - ((tar->IsOfClientBot() && !c->IsInGroupOrRaid(tar)) || (tar->GetOwner() && tar->GetOwner()->IsOfClientBot() && !c->IsInGroupOrRaid(tar->GetOwner()))) + (tar->IsNPC() && !tar->GetOwner()) || + (tar->GetOwner() && tar->GetOwner()->IsOfClientBot() && !c->IsInGroupOrRaid(tar->GetOwner())) || + (tar->IsOfClientBot() && !c->IsInGroupOrRaid(tar)) ) { - c->Message(Chat::Yellow, "[%s] is an invalid target. Only players in your group or raid are eligible targets.", tar->GetCleanName()); + c->Message(Chat::Yellow, "[%s] is an invalid target. Only players or their pet in your group or raid are eligible targets.", tar->GetCleanName()); return; } @@ -408,49 +442,79 @@ void bot_command_cast(Client* c, const Seperator* sep) } Mob* newTar = tar; - //LogTestDebug("{}: {} says, 'Attempting {} [{}-{}] on {}'", __LINE__, bot_iter->GetCleanName(), c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (subTargetType != UINT16_MAX ? c->GetSubTypeNameByID(subTargetType) : "Standard"), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme - if (!SpellTypeRequiresTarget(spellType, bot_iter->GetClass())) { - newTar = bot_iter; + + if (!aaType) { + //LogTestDebug("{}: {} says, 'Attempting {} [{}-{}] on {}'", __LINE__, bot_iter->GetCleanName(), c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (subTargetType != UINT16_MAX ? c->GetSubTypeNameByID(subTargetType) : "Standard"), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme + if (!SpellTypeRequiresTarget(spellType, bot_iter->GetClass())) { + newTar = bot_iter; + } + + if (!newTar) { + continue; + } + + if ( + IsBotSpellTypeBeneficial(spellType) && + !RuleB(Bots, CrossRaidBuffingAndHealing) && + !bot_iter->IsInGroupOrRaid(newTar, true) + ) { + continue; + } + + if (IsBotSpellTypeDetrimental(spellType, bot_iter->GetClass()) && !bot_iter->IsAttackAllowed(newTar)) { + bot_iter->BotGroupSay( + bot_iter, + fmt::format( + "I cannot attack [{}].", + newTar->GetCleanName() + ).c_str() + ); + + continue; + } } - if (!newTar) { - continue; - } + if (aaType) { + if (!bot_iter->GetAA(zone->GetAlternateAdvancementAbility(aaID)->first_rank_id)) { + continue; + } - if ( - IsBotSpellTypeBeneficial(spellType) && - !RuleB(Bots, CrossRaidBuffingAndHealing) && - !bot_iter->IsInGroupOrRaid(newTar, true) - ) { - continue; - } + LogTestDebug("{}: {} says, 'aaID is {}'", __LINE__, bot_iter->GetCleanName(), aaID); //deleteme + AA::Rank* tempRank = nullptr; + AA::Rank*& rank = tempRank; + uint16 spell_id = bot_iter->GetSpellByAA(aaID, rank); - if (IsBotSpellTypeDetrimental(spellType, bot_iter->GetClass()) && !bot_iter->IsAttackAllowed(newTar)) { - bot_iter->BotGroupSay( - bot_iter, - fmt::format( - "I cannot attack [{}].", - newTar->GetCleanName() - ).c_str() - ); + if (!IsValidSpell(spell_id)) { + continue; + } - continue; - } - - LogTestDebug("{}: {} says, 'Attempting {} [{}-{}] on {}'", __LINE__, bot_iter->GetCleanName(), c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (subTargetType != UINT16_MAX ? c->GetSubTypeNameByID(subTargetType) : "Standard"), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme - - bot_iter->SetCommandedSpell(true); - - if (bot_iter->AICastSpell(newTar, 100, spellType, subTargetType, subType)) { - if (!firstFound) { - firstFound = bot_iter; + if (!bot_iter->AttemptAACastSpell(tar, spell_id, rank)) { + continue; } isSuccess = true; ++successCount; } + else { + LogTestDebug("{}: {} says, 'Attempting {} [{}-{}] on {}'", __LINE__, bot_iter->GetCleanName(), c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (subTargetType != UINT16_MAX ? c->GetSubTypeNameByID(subTargetType) : "Standard"), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme + bot_iter->SetCommandedSpell(true); + + if (bot_iter->AICastSpell(newTar, 100, spellType, subTargetType, subType)) { + if (!firstFound) { + firstFound = bot_iter; + } - bot_iter->SetCommandedSpell(false); + isSuccess = true; + ++successCount; + } + else { + bot_iter->GetBotOwner()->Message(Chat::Red, "%s says, 'Ability failed to cast. This could be due to this to any number of things: range, mana, immune, etc.'", bot_iter->GetCleanName()); + + continue; + } + + bot_iter->SetCommandedSpell(false); + } continue; } @@ -460,7 +524,7 @@ void bot_command_cast(Client* c, const Seperator* sep) Chat::Yellow, fmt::format( "No bots are capable of casting [{}] on {}.", - c->GetSpellTypeNameByID(spellType), + (!aaType ? c->GetSpellTypeNameByID(spellType) : zone->GetAAName(zone->GetAlternateAdvancementAbility(aaID)->first_rank_id)), tar ? tar->GetCleanName() : "your target" ).c_str() ); @@ -471,7 +535,7 @@ void bot_command_cast(Client* c, const Seperator* sep) "{} {} [{}]{}", ((successCount == 1 && firstFound) ? firstFound->GetCleanName() : (fmt::format("{}", successCount).c_str())), ((successCount == 1 && firstFound) ? "casted" : "of your bots casted"), - c->GetSpellTypeNameByID(spellType), + (!aaType ? c->GetSpellTypeNameByID(spellType) : zone->GetAAName(zone->GetAlternateAdvancementAbility(aaID)->first_rank_id)), tar ? (fmt::format(" on {}.", tar->GetCleanName()).c_str()) : "." ).c_str() ); diff --git a/zone/spells.cpp b/zone/spells.cpp index d24b5b7e4..7ca17d394 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -2580,7 +2580,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in } range = GetActSpellRange(spell_id, range); - if(IsClient() && IsIllusionSpell(spell_id) && (HasProjectIllusion())){ + if(IsOfClientBot() && IsIllusionSpell(spell_id) && (HasProjectIllusion())){ range = 100; } From 33386e1e66cc9b3a9197df9b12216eef3fb6a267 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 8 Dec 2024 23:10:49 -0600 Subject: [PATCH 73/97] Add PetDamageShields and PetResistBuffs to IsPetBotSpellType() --- common/spdat.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/spdat.cpp b/common/spdat.cpp index 2d36d4916..42e64116e 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -3012,6 +3012,8 @@ bool IsPetBotSpellType(uint16 spellType) { case BotSpellTypes::PetFastHeals: case BotSpellTypes::PetVeryFastHeals: case BotSpellTypes::PetHoTHeals: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::PetResistBuffs: return true; default: return false; From 51332cc0c7ed166b829f353531214c1be930005d Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:58:54 -0600 Subject: [PATCH 74/97] Add priorities to HateLine inserts for db update --- .../database_update_manifest_bots.cpp | 166 +++++++++--------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/common/database/database_update_manifest_bots.cpp b/common/database/database_update_manifest_bots.cpp index d93ec37bf..bd35d776f 100644 --- a/common/database/database_update_manifest_bots.cpp +++ b/common/database/database_update_manifest_bots.cpp @@ -1086,90 +1086,90 @@ FROM bot_spells_entries WHERE `npc_spells_id` = 3005 AND `type` = 10; -INSERT INTO `bot_spells_entries` (`npc_spells_id`, `spell_id`, `type`, `minlevel`, `maxlevel`) +INSERT INTO `bot_spells_entries` (`npc_spells_id`, `spell_id`, `type`, `minlevel`, `maxlevel`, `priority`) VALUES -(3003, 10175, 24, 72, 76), -(3003, 10173, 24, 72, 76), -(3003, 10174, 24, 72, 76), -(3003, 14956, 24, 77, 81), -(3003, 14954, 24, 77, 81), -(3003, 14955, 24, 77, 81), -(3003, 19070, 24, 82, 86), -(3003, 19068, 24, 82, 86), -(3003, 19069, 24, 82, 86), -(3003, 25298, 24, 87, 91), -(3003, 25299, 24, 87, 91), -(3003, 25297, 24, 87, 91), -(3003, 28348, 24, 92, 96), -(3003, 28349, 24, 92, 96), -(3003, 28347, 24, 92, 96), -(3003, 34351, 24, 97, 254), -(3003, 34352, 24, 97, 254), -(3003, 34350, 24, 97, 254), -(3003, 40078, 24, 98, 254), -(3003, 40079, 24, 98, 254), -(3003, 40080, 24, 98, 254), -(3005, 1221, 24, 33, 41), -(3005, 1222, 24, 42, 52), -(3005, 1223, 24, 53, 58), -(3005, 1224, 24, 59, 62), -(3005, 3405, 24, 63, 66), -(3005, 5329, 24, 67, 70), -(3005, 5336, 24, 69, 73), -(3005, 10258, 24, 71, 71), -(3005, 10259, 24, 71, 71), -(3005, 10257, 24, 71, 71), -(3005, 10261, 24, 72, 76), -(3005, 10262, 24, 72, 76), -(3005, 10260, 24, 72, 76), -(3005, 10291, 24, 74, 78), -(3005, 10292, 24, 74, 78), -(3005, 10293, 24, 74, 78), -(3005, 15160, 24, 76, 76), -(3005, 15161, 24, 76, 76), -(3005, 15162, 24, 76, 76), -(3005, 15165, 24, 77, 81), -(3005, 15163, 24, 77, 81), -(3005, 15164, 24, 77, 81), -(3005, 15186, 24, 79, 83), -(3005, 15184, 24, 79, 83), -(3005, 15185, 24, 79, 83), -(3005, 19315, 24, 81, 81), -(3005, 19313, 24, 81, 81), -(3005, 19314, 24, 81, 81), -(3005, 19317, 24, 82, 86), -(3005, 19318, 24, 82, 86), -(3005, 19316, 24, 82, 86), -(3005, 19338, 24, 84, 88), -(3005, 19339, 24, 84, 88), -(3005, 19337, 24, 84, 88), -(3005, 25581, 24, 86, 86), -(3005, 25582, 24, 86, 86), -(3005, 25580, 24, 86, 86), -(3005, 25586, 24, 87, 91), -(3005, 25587, 24, 87, 91), -(3005, 25588, 24, 87, 91), -(3005, 25641, 24, 89, 93), -(3005, 25642, 24, 89, 93), -(3005, 25643, 24, 89, 93), -(3005, 28659, 24, 91, 91), -(3005, 28657, 24, 91, 91), -(3005, 28658, 24, 91, 91), -(3005, 28665, 24, 92, 96), -(3005, 28663, 24, 92, 96), -(3005, 28664, 24, 92, 96), -(3005, 28735, 24, 94, 98), -(3005, 28733, 24, 94, 98), -(3005, 28734, 24, 94, 98), -(3005, 34688, 24, 96, 96), -(3005, 34689, 24, 96, 96), -(3005, 34687, 24, 96, 96), -(3005, 34694, 24, 97, 254), -(3005, 34695, 24, 97, 254), -(3005, 34693, 24, 97, 254), -(3005, 34752, 24, 99, 254), -(3005, 34753, 24, 99, 254), -(3005, 34751, 24, 99, 254); +(3003, 10173, 24, 72, 76, 3), +(3003, 10174, 24, 72, 76, 2), +(3003, 10175, 24, 72, 76, 1), +(3003, 14954, 24, 77, 81, 3), +(3003, 14955, 24, 77, 81, 2), +(3003, 14956, 24, 77, 81, 1), +(3003, 19068, 24, 82, 86, 3), +(3003, 19069, 24, 82, 86, 2), +(3003, 19070, 24, 82, 86, 1), +(3003, 25297, 24, 87, 91, 3), +(3003, 25298, 24, 87, 91, 2), +(3003, 25299, 24, 87, 91, 1), +(3003, 28347, 24, 92, 96, 3), +(3003, 28348, 24, 92, 96, 2), +(3003, 28349, 24, 92, 96, 1), +(3003, 34350, 24, 97, 254, 3), +(3003, 34351, 24, 97, 254, 2), +(3003, 34352, 24, 97, 254, 1), +(3003, 40078, 24, 98, 254, 3), +(3003, 40079, 24, 98, 254, 2), +(3003, 40080, 24, 98, 254, 1), +(3005, 1221, 24, 33, 41, 1), +(3005, 1222, 24, 42, 52, 1), +(3005, 1223, 24, 53, 58, 1), +(3005, 1224, 24, 59, 62, 1), +(3005, 3405, 24, 63, 66, 1), +(3005, 5329, 24, 67, 70, 5), +(3005, 5336, 24, 69, 73, 4), +(3005, 10257, 24, 71, 71, 3), +(3005, 10258, 24, 71, 71, 2), +(3005, 10259, 24, 71, 71, 1), +(3005, 10260, 24, 72, 76, 3), +(3005, 10261, 24, 72, 76, 2), +(3005, 10262, 24, 72, 76, 1), +(3005, 10291, 24, 74, 78, 3), +(3005, 10292, 24, 74, 78, 2), +(3005, 10293, 24, 74, 78, 1), +(3005, 15160, 24, 76, 76, 3), +(3005, 15161, 24, 76, 76, 2), +(3005, 15162, 24, 76, 76, 1), +(3005, 15163, 24, 77, 81, 3), +(3005, 15164, 24, 77, 81, 2), +(3005, 15165, 24, 77, 81, 1), +(3005, 15184, 24, 79, 83, 3), +(3005, 15185, 24, 79, 83, 2), +(3005, 15186, 24, 79, 83, 1), +(3005, 19313, 24, 81, 81, 3), +(3005, 19314, 24, 81, 81, 2), +(3005, 19315, 24, 81, 81, 1), +(3005, 19316, 24, 82, 86, 3), +(3005, 19317, 24, 82, 86, 2), +(3005, 19318, 24, 82, 86, 1), +(3005, 19337, 24, 84, 88, 3), +(3005, 19338, 24, 84, 88, 2), +(3005, 19339, 24, 84, 88, 1), +(3005, 25580, 24, 86, 86, 3), +(3005, 25581, 24, 86, 86, 2), +(3005, 25582, 24, 86, 86, 1), +(3005, 25586, 24, 87, 91, 3), +(3005, 25587, 24, 87, 91, 2), +(3005, 25588, 24, 87, 91, 1), +(3005, 25641, 24, 89, 93, 3), +(3005, 25642, 24, 89, 93, 2), +(3005, 25643, 24, 89, 93, 1), +(3005, 28657, 24, 91, 91, 3), +(3005, 28658, 24, 91, 91, 2), +(3005, 28659, 24, 91, 91, 1), +(3005, 28663, 24, 92, 96, 3), +(3005, 28664, 24, 92, 96, 2), +(3005, 28665, 24, 92, 96, 1), +(3005, 28733, 24, 94, 98, 3), +(3005, 28734, 24, 94, 98, 2), +(3005, 28735, 24, 94, 98, 1), +(3005, 34687, 24, 96, 96, 3), +(3005, 34688, 24, 96, 96, 2), +(3005, 34689, 24, 96, 96, 1), +(3005, 34693, 24, 97, 254, 3), +(3005, 34694, 24, 97, 254, 2), +(3005, 34695, 24, 97, 254, 1), +(3005, 34751, 24, 99, 254, 3), +(3005, 34752, 24, 99, 254, 2), +(3005, 34753, 24, 99, 254, 1); DELETE FROM bot_spells_entries From ef7af5c63f6b1409a35eecf8cc90c4c4660dc281 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:59:17 -0600 Subject: [PATCH 75/97] Remove SpellTypeRequiresCastChecks --- common/spdat.cpp | 25 ------------------------- common/spdat.h | 1 - 2 files changed, 26 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index 42e64116e..ff4a3006a 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -3116,31 +3116,6 @@ bool SpellTypeRequiresTarget(uint16 spellType, uint16 cls) { return true; } -bool SpellTypeRequiresCastChecks(uint16 spellType) { - switch (spellType) { - case BotSpellTypes::AEDebuff: - case BotSpellTypes::AEDispel: - case BotSpellTypes::AEDoT: - case BotSpellTypes::AEFear: - case BotSpellTypes::AELifetap: - case BotSpellTypes::AEMez: - case BotSpellTypes::AENukes: - case BotSpellTypes::AERains: - case BotSpellTypes::AERoot: - case BotSpellTypes::AESlow: - case BotSpellTypes::AESnare: - case BotSpellTypes::AEStun: - case BotSpellTypes::PBAENuke: - case BotSpellTypes::Mez: - case BotSpellTypes::SummonCorpse: - return false; - default: - return true; - } - - return true; -} - bool SpellTypeRequiresAEChecks(uint16 spellType) { switch (spellType) { case BotSpellTypes::AEMez: diff --git a/common/spdat.h b/common/spdat.h index 01cdf85af..7b533328b 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -748,7 +748,6 @@ bool IsClientBotSpellType(uint16 spellType); bool IsHealBotSpellType(uint16 spellType); bool SpellTypeRequiresLoS(uint16 spellType, uint16 cls = 0); bool SpellTypeRequiresTarget(uint16 spellType, uint16 cls = 0); -bool SpellTypeRequiresCastChecks(uint16 spellType); bool SpellTypeRequiresAEChecks(uint16 spellType); bool IsCommandedSpellType(uint16 spellType); bool IsPullingSpellType(uint16 spellType); From aa996e7b31b62c03e508b3566abb9e8d0b2b8f97 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:59:48 -0600 Subject: [PATCH 76/97] Add bot check to DetermineSpellTargets for IsIllusionSpell --- zone/spells.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/zone/spells.cpp b/zone/spells.cpp index 7ca17d394..be631b76f 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -1939,6 +1939,7 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce && spell_target != nullptr // null ptr crash safeguard && !spell_target->IsNPC() // still self only if NPC targetted && IsClient() + && IsOfClientBot() && (IsGrouped() // still self only if not grouped || IsRaidGrouped()) && (HasProjectIllusion())){ From 307b576da9e28af4b4ed8ef3215d20b52ec5228a Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:59:59 -0600 Subject: [PATCH 77/97] merge with previous --- zone/spells.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/zone/spells.cpp b/zone/spells.cpp index be631b76f..32903bfcf 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -1938,7 +1938,6 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce if(IsIllusionSpell(spell_id) && spell_target != nullptr // null ptr crash safeguard && !spell_target->IsNPC() // still self only if NPC targetted - && IsClient() && IsOfClientBot() && (IsGrouped() // still self only if not grouped || IsRaidGrouped()) From 389515d75c49dd834294347760256fb7897ab0a5 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:00:17 -0600 Subject: [PATCH 78/97] Correct bot checks for ST_GroupClientAndPet --- zone/spells.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/zone/spells.cpp b/zone/spells.cpp index 32903bfcf..62b0e886b 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -2321,6 +2321,19 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce if(GetOwner()) group_id_caster = (GetRaid()->GetGroup(GetOwner()->CastToClient()) == 0xFFFF) ? 0 : (GetRaid()->GetGroup(GetOwner()->CastToClient()) + 1); } + if (IsGrouped()) + { + if (Group* group = GetGroup()) { + group_id_caster = group->GetID(); + } + } + else if (IsRaidGrouped()) + { + if (Raid* raid = GetRaid()) { + uint32 group_id = raid->GetGroup(GetName()); + group_id_caster = (group_id == 0xFFFFFFFF) ? 0 : (group_id + 1); + } + } } if(spell_target->IsClient()) From 211908196e68f1bb1ec9b8c61055f1f25f70678f Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:01:29 -0600 Subject: [PATCH 79/97] Remove misc target_type checks --- zone/bot.cpp | 10 +--------- zone/botspellsai.cpp | 43 ++++++++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 347689f20..107f86304 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9750,15 +9750,7 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spell_id, Mob* tar) { case BotSpellTypes::Invisibility: case BotSpellTypes::MovementSpeed: case BotSpellTypes::SendHome: - if ( // TODO bot rewrite - fix this, missing other target types (43 for example) - !( - spells[spell_id].target_type == ST_Target || - spells[spell_id].target_type == ST_Pet || - (tar == this && spells[spell_id].target_type != ST_TargetsTarget) || - spells[spell_id].target_type == ST_Group || - spells[spell_id].target_type == ST_GroupTeleport - ) - ) { + if (tar == this && spells[spell_id].target_type == ST_TargetsTarget) { LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to target_type checks. Using {}'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName(), GetSpellTargetType(spell_id)); //deleteme return false; } diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index b4588811d..aa24443a3 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -595,27 +595,28 @@ bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain } else dist2 = DistanceSquared(m_Position, tar->GetPosition()); - if ( - ( - ( - ( - (spells[AIBot_spells[i].spellid].target_type==ST_GroupTeleport && AIBot_spells[i].type == BotSpellTypes::RegularHeal) || - spells[AIBot_spells[i].spellid].target_type ==ST_AECaster || - spells[AIBot_spells[i].spellid].target_type ==ST_Group || - spells[AIBot_spells[i].spellid].target_type ==ST_AEBard || - ( - tar == this && spells[AIBot_spells[i].spellid].target_type != ST_TargetsTarget - ) - ) && - dist2 <= spells[AIBot_spells[i].spellid].aoe_range*spells[AIBot_spells[i].spellid].aoe_range - ) || - dist2 <= GetActSpellRange(AIBot_spells[i].spellid, spells[AIBot_spells[i].spellid].range)*GetActSpellRange(AIBot_spells[i].spellid, spells[AIBot_spells[i].spellid].range) - ) && - ( - mana_cost <= GetMana() || - IsBotNonSpellFighter() - ) - ) { + //if ( + // ( + // ( + // ( + // (spells[AIBot_spells[i].spellid].target_type==ST_GroupTeleport && AIBot_spells[i].type == BotSpellTypes::RegularHeal) || + // spells[AIBot_spells[i].spellid].target_type ==ST_AECaster || + // spells[AIBot_spells[i].spellid].target_type ==ST_Group || + // spells[AIBot_spells[i].spellid].target_type ==ST_AEBard || + // ( + // tar == this && spells[AIBot_spells[i].spellid].target_type != ST_TargetsTarget + // ) + // ) && + // dist2 <= spells[AIBot_spells[i].spellid].aoe_range*spells[AIBot_spells[i].spellid].aoe_range + // ) || + // dist2 <= GetActSpellRange(AIBot_spells[i].spellid, spells[AIBot_spells[i].spellid].range)*GetActSpellRange(AIBot_spells[i].spellid, spells[AIBot_spells[i].spellid].range) + // ) && + // ( + // mana_cost <= GetMana() || + // IsBotNonSpellFighter() + // ) + //) { + if (IsValidSpellRange(AIBot_spells[i].spellid, tar) && (mana_cost <= GetMana() || IsBotNonSpellFighter())) { casting_spell_AIindex = i; LogAI("spellid [{}] tar [{}] mana [{}] Name [{}]", AIBot_spells[i].spellid, tar->GetName(), mana_cost, spells[AIBot_spells[i].spellid].name); result = Mob::CastSpell(AIBot_spells[i].spellid, tar->GetID(), EQ::spells::CastingSlot::Gem2, spells[AIBot_spells[i].spellid].cast_time, AIBot_spells[i].manacost == -2 ? 0 : mana_cost, oDontDoAgainBefore, -1, -1, 0, &(AIBot_spells[i].resist_adjust)); From f3e0fdae73c51dfaa5d14ccb7b420598f9163d72 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:03:33 -0600 Subject: [PATCH 80/97] Add lull/aelull to ^cast --- common/ruletypes.h | 1 + common/spdat.cpp | 7 +++++++ common/spdat.h | 3 ++- zone/bot.cpp | 7 +++++-- zone/bot_commands/cast.cpp | 8 ++++++++ zone/botspellsai.cpp | 1 + zone/mob.cpp | 6 ++++++ 7 files changed, 30 insertions(+), 3 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 79daedca5..9a7b3acbd 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -878,6 +878,7 @@ RULE_BOOL(Bots, AllowCommandedCharm, true, "If enabled bots can be commanded to RULE_BOOL(Bots, AllowCommandedMez, true, "If enabled bots can be commanded to mez NPCs.") RULE_BOOL(Bots, AllowCommandedResurrect, true, "If enabled bots can be commanded to resurrect players.") RULE_BOOL(Bots, AllowCommandedSummonCorpse, true, "If enabled bots can be commanded to summon other's corpses.") +RULE_BOOL(Bots, AllowCommandedLull, true, "If enabled bots can be commanded to lull targets.") RULE_INT(Bots, CampTimer, 25, "Number of seconds after /camp has begun before bots camp out.") RULE_BOOL(Bots, SendClassRaceOnHelp, true, "If enabled a reminder of how to check class/race IDs will be sent when using compatible commands.") RULE_BOOL(Bots, AllowCrossGroupRaidAssist, true, "If enabled bots will autodefend group or raid members set as main assist.") diff --git a/common/spdat.cpp b/common/spdat.cpp index ff4a3006a..d5ec84092 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -2820,6 +2820,7 @@ bool IsBotSpellTypeDetrimental(uint16 spellType, uint8 cls) { case BotSpellTypes::AELifetap: case BotSpellTypes::PBAENuke: case BotSpellTypes::Lull: + case BotSpellTypes::AELull: case BotSpellTypes::HateLine: case BotSpellTypes::AEHateLine: return true; @@ -2946,6 +2947,9 @@ bool IsBotSpellTypeInnate(uint16 spellType) { case BotSpellTypes::AEMez: case BotSpellTypes::Mez: case BotSpellTypes::Lull: + case BotSpellTypes::AELull: + case BotSpellTypes::HateLine: + case BotSpellTypes::AEHateLine: return true; default: return false; @@ -2969,6 +2973,8 @@ bool IsAEBotSpellType(uint16 spellType) { case BotSpellTypes::PBAENuke: case BotSpellTypes::AELifetap: case BotSpellTypes::AERoot: + case BotSpellTypes::AEHateLine: + case BotSpellTypes::AELull: return true; default: return false; @@ -3242,6 +3248,7 @@ bool IsCommandedSpellType(uint16 spellType) { case BotSpellTypes::MovementSpeed: case BotSpellTypes::SendHome: case BotSpellTypes::SummonCorpse: + case BotSpellTypes::AELull: //case BotSpellTypes::Cure: //case BotSpellTypes::GroupCures: //case BotSpellTypes::DamageShields: diff --git a/common/spdat.h b/common/spdat.h index 7b533328b..e030143c7 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -725,11 +725,12 @@ namespace BotSpellTypes constexpr uint16 MovementSpeed = 110; constexpr uint16 SendHome = 111; constexpr uint16 SummonCorpse = 112; + constexpr uint16 AELull = 113; constexpr uint16 START = BotSpellTypes::Nuke; // Do not remove or change this constexpr uint16 END = BotSpellTypes::PetResistBuffs; // Do not remove this, increment as needed constexpr uint16 COMMANDED_START = BotSpellTypes::Lull; // Do not remove or change this - constexpr uint16 COMMANDED_END = BotSpellTypes::SummonCorpse; // Do not remove this, increment as needed + constexpr uint16 COMMANDED_END = BotSpellTypes::AELull; // Do not remove this, increment as needed } const uint32 SPELL_TYPES_DETRIMENTAL = (SpellType_Nuke | SpellType_Root | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Charm | SpellType_Debuff | SpellType_Slow); diff --git a/zone/bot.cpp b/zone/bot.cpp index 107f86304..21d36b859 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9878,6 +9878,7 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spell_id, Mob* tar) { } break; + case BotSpellTypes::AELull: case BotSpellTypes::Lull: if (IsHarmonySpell(spell_id) && !HarmonySpellLevelCheck(spell_id, tar)) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to HarmonySpellLevelCheck.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme @@ -11234,6 +11235,9 @@ uint16 Bot::GetSpellListSpellType(uint16 spellType) { case BotSpellTypes::AELifetap: case BotSpellTypes::Lifetap: return BotSpellTypes::Lifetap; + case BotSpellTypes::AELull: + case BotSpellTypes::Lull: + return BotSpellTypes::Lull; case BotSpellTypes::Charm: case BotSpellTypes::Escape: case BotSpellTypes::HateRedux: @@ -11245,8 +11249,7 @@ uint16 Bot::GetSpellListSpellType(uint16 spellType) { case BotSpellTypes::Pet: case BotSpellTypes::PreCombatBuff: case BotSpellTypes::PreCombatBuffSong: - case BotSpellTypes::Resurrect: - case BotSpellTypes::Lull: + case BotSpellTypes::Resurrect: default: return spellType; } diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index 7915a491c..5866bacf4 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -268,6 +268,14 @@ void bot_command_cast(Client* c, const Seperator* sep) return; } + break; + case BotSpellTypes::AELull: + case BotSpellTypes::Lull: + if (!RuleB(Bots, AllowCommandedLull)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + break; case BotSpellTypes::SummonCorpse: if (!RuleB(Bots, AllowCommandedSummonCorpse)) { diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index aa24443a3..93c38ccf6 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -83,6 +83,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType, uint16 subTarge } break; + case BotSpellTypes::AELull: case BotSpellTypes::Lull: if (tar->GetSpecialAbility(SpecialAbility::PacifyImmunity)) { return false; diff --git a/zone/mob.cpp b/zone/mob.cpp index 8e8cb8c01..fdf1ed2a0 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -8943,6 +8943,9 @@ std::string Mob::GetSpellTypeNameByID(uint16 spellType) { case BotSpellTypes::SummonCorpse: spellTypeName = "Summon Corpse"; break; + case BotSpellTypes::AELull: + spellTypeName = "AE Lull"; + break; default: break; } @@ -9164,6 +9167,9 @@ std::string Mob::GetSpellTypeShortNameByID(uint16 spellType) { case BotSpellTypes::SummonCorpse: spellTypeName = "summoncorpse"; break; + case BotSpellTypes::AELull: + spellTypeName = "aelull"; + break; default: break; } From 57a81fc31067cb69a32754da71e6fe5ebf13a7ca Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:04:07 -0600 Subject: [PATCH 81/97] Add more checks for CommandedSubTypes::AETarget --- zone/bot.cpp | 2 +- zone/botspellsai.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 21d36b859..2163a1348 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -11905,7 +11905,7 @@ bool Bot::IsValidSpellTypeSubType(uint16 spellType, uint16 subType, uint16 spell break; case CommandedSubTypes::AETarget: - if (IsAnyAESpell(spell_id)) { + if (IsAnyAESpell(spell_id) && !IsGroupSpell(spell_id)) { return true; } diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 93c38ccf6..c6ac70397 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -216,7 +216,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType, uint16 subTarge break; } - std::list botSpellList = GetPrioritizedBotSpellsBySpellType(this, spellType, tar, IsAEBotSpellType(spellType), subTargetType, subType); + std::list botSpellList = GetPrioritizedBotSpellsBySpellType(this, spellType, tar, (IsAEBotSpellType(spellType) || subTargetType == CommandedSubTypes::AETarget), subTargetType, subType); for (const auto& s : botSpellList) { From 51711e799b82b364137714605cb8e80f712f042e Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:07:21 -0600 Subject: [PATCH 82/97] remove unneeded checks on IsValidSpellTypeBySpellID --- zone/bot.cpp | 82 ++-------------------------------------------------- 1 file changed, 2 insertions(+), 80 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 2163a1348..747a30d9c 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9761,12 +9761,12 @@ bool Bot::CanCastSpellType(uint16 spellType, uint16 spell_id, Mob* tar) { } if (!tar->CheckSpellLevelRestriction(this, spell_id)) { - LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to CheckSpellLevelRestriction.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to CheckSpellLevelRestriction.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } if ((spellType != BotSpellTypes::Teleport && spellType != BotSpellTypes::Succor) && (IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Succor))) { - LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to Teleport.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to Teleport.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } @@ -11313,84 +11313,6 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spellType, uint16 spell_id) { } return false; - //case BotSpellTypes::Lull: - // if (IsHarmonySpell(spell_id)) { - // return true; - // } - // - // return false; - //case BotSpellTypes::Teleport: - // if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))) { - // return true; - // } - // - // return false; - //case BotSpellTypes::Succor: - // if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Succor)) { - // return true; - // } - // - // return false; - //case BotSpellTypes::BindAffinity: - // if (IsEffectInSpell(spell_id, SE_BindAffinity)) { - // return true; - // } - // - // return false; - //case BotSpellTypes::Identify: - // if (IsEffectInSpell(spell_id, SE_Identify)) { - // return true; - // } - // - // return false; - //case BotSpellTypes::Levitate: - // if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Levitate))) { - // return true; - // } - // - // return false; - //case BotSpellTypes::Rune: - // if (IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune)) { - // return true; - // } - // - // return false; - //case BotSpellTypes::WaterBreathing: - // if (IsEffectInSpell(spell_id, SE_WaterBreathing)) { - // return true; - // } - // - // return false; - //case BotSpellTypes::Size: - // if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))) { - // return true; - // } - // - // return false; - //case BotSpellTypes::Invisibility: - // if (IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id)) { - // return true; - // } - // - // return false; - //case BotSpellTypes::MovementSpeed: - // if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) { - // return true; - // } - // - // return false; - //case BotSpellTypes::SendHome: - // if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_GateToHomeCity)) { - // return true; - // } - // - // return false; - //case BotSpellTypes::SummonCorpse: - // if (IsEffectInSpell(spell_id, SE_SummonCorpse)) { - // return true; - // } - // - // return false; default: return true; } From 229b8684ab84a1847ce2712e5c266e507b7e5900 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 11 Dec 2024 07:16:43 -0600 Subject: [PATCH 83/97] add to aelull --- common/spdat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index d5ec84092..c91255394 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -3235,6 +3235,7 @@ bool IsCommandedSpellType(uint16 spellType) { case BotSpellTypes::AEFear: case BotSpellTypes::Fear: case BotSpellTypes::Resurrect: + case BotSpellTypes::AELull: case BotSpellTypes::Lull: case BotSpellTypes::Teleport: case BotSpellTypes::Succor: @@ -3248,7 +3249,6 @@ bool IsCommandedSpellType(uint16 spellType) { case BotSpellTypes::MovementSpeed: case BotSpellTypes::SendHome: case BotSpellTypes::SummonCorpse: - case BotSpellTypes::AELull: //case BotSpellTypes::Cure: //case BotSpellTypes::GroupCures: //case BotSpellTypes::DamageShields: From 1536e26b310fc6d047100b93c16c46730be6f112 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 11 Dec 2024 07:18:12 -0600 Subject: [PATCH 84/97] rewrite GetCorrectSpellType --- common/ruletypes.h | 3 + common/spdat.cpp | 250 ++++++++++++++++++++++++- common/spdat.h | 3 + zone/bot.cpp | 2 +- zone/botspellsai.cpp | 423 ++++--------------------------------------- 5 files changed, 292 insertions(+), 389 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 9a7b3acbd..4d57b4f82 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -824,6 +824,9 @@ RULE_INT(Bots, MinTargetsForAESpell, 3, "Minimum number of targets in valid rang RULE_INT(Bots, MinTargetsForGroupSpell, 3, "Minimum number of targets in valid range that are required for an group spell to cast. Default 3.") RULE_BOOL(Bots, AllowBuffingHealingFamiliars, false, "Determines if bots are allowed to buff and heal familiars. Default false.") RULE_BOOL(Bots, RunSpellTypeChecksOnSpawn, false, "This will run a serious of checks on spell types and output errors to LogBotSpellTypeChecks") +RULE_BOOL(Bots, UseParentSpellTypeForChecks, true, "This will check only the parent instead of AE/Group/Pet types (ex: AENukes/AERains/PBAENukes fall under Nukes or PetBuffs fall under buffs) when RunSpellTypeChecksOnSpawn fires") +RULE_BOOL(Bots, AllowForcedCastsBySpellID, true, "If enabled, players can use ^cast spellid # to cast a specific spell by ID that is in their spell list") +RULE_BOOL(Bots, AllowCastAAs, true, "If enabled, players can use ^cast aa to cast a clickable AA") RULE_BOOL(Bots, AllowMagicianEpicPet, false, "If enabled, magician bots can summon their epic pets following the rules AllowMagicianEpicPetLevel") RULE_INT(Bots, AllowMagicianEpicPetLevel, 50, "If AllowMagicianEpicPet is enabled, bots can start using their epic pets at this level") RULE_INT(Bots, RequiredMagicianEpicPetItemID, 28034, "If AllowMagicianEpicPet is enabled and this is set, bots will be required to have this item ID equipped to cast their epic. Takes in to account AllowMagicianEpicPetLevel as well. Set to 0 to disable requirement") diff --git a/common/spdat.cpp b/common/spdat.cpp index c91255394..cbff31731 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -1582,11 +1582,11 @@ bool IsRegularPetHealSpell(uint16 spell_id) if (spell_id) { if ( - spells[spell_id].target_type == ST_Pet && + (spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_Undead) && !IsCompleteHealSpell(spell_id) && !IsHealOverTimeSpell(spell_id) && !IsGroupSpell(spell_id) - ) { + ) { for (int i = 0; i < EFFECT_COUNT; i++) { if ( spells[spell_id].base_value[i] > 0 && @@ -1594,8 +1594,8 @@ bool IsRegularPetHealSpell(uint16 spell_id) ( spells[spell_id].effect_id[i] == SE_CurrentHP || spells[spell_id].effect_id[i] == SE_CurrentHPOnce - ) - ) { + ) + ) { return true; } } @@ -3229,6 +3229,17 @@ bool IsDamageShieldOnlySpell(uint16 spell_id) { return true; } +bool IsHateSpell(uint16 spell_id) { + if ( + (IsEffectInSpell(spell_id, SE_Hate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_Hate)] > 0) || + (IsEffectInSpell(spell_id, SE_InstantHate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_InstantHate)] > 0) + ) { + return true; + } + + return false; +} + bool IsCommandedSpellType(uint16 spellType) { switch (spellType) { case BotSpellTypes::Charm: @@ -3281,3 +3292,234 @@ bool IsPullingSpellType(uint16 spellType) { return false; } + +uint16 GetCorrectSpellType(uint16 spellType, uint16 spell_id) { + uint16 correctType = UINT16_MAX; + SPDat_Spell_Struct spell = spells[spell_id]; + std::string teleportZone = spell.teleport_zone; + + if (IsCharmSpell(spell_id)) { + correctType = BotSpellTypes::Charm; + } + else if (IsFearSpell(spell_id)) { + correctType = BotSpellTypes::Fear; + } + else if (IsEffectInSpell(spell_id, SE_Revive)) { + correctType = BotSpellTypes::Resurrect; + } + else if (IsHarmonySpell(spell_id)) { + correctType = BotSpellTypes::Lull; + } + else if (teleportZone.compare("") && !IsEffectInSpell(spell_id, SE_GateToHomeCity) && IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))) { + correctType = BotSpellTypes::Teleport; + } + else if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Succor)) { + correctType = BotSpellTypes::Succor; + } + else if (IsEffectInSpell(spell_id, SE_BindAffinity)) { + correctType = BotSpellTypes::BindAffinity; + } + else if (IsEffectInSpell(spell_id, SE_Identify)) { + correctType = BotSpellTypes::Identify; + } + else if (spellType == BotSpellTypes::Levitate && IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Levitate))) { + correctType = BotSpellTypes::Levitate; + } + else if (spellType == BotSpellTypes::Rune && IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune))) { + correctType = BotSpellTypes::Rune; + } + else if (spellType == BotSpellTypes::WaterBreathing && IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_WaterBreathing)) { + correctType = BotSpellTypes::WaterBreathing; + } + else if (spellType == BotSpellTypes::Size && IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))) { + correctType = BotSpellTypes::Size; + } + else if (spellType == BotSpellTypes::Invisibility && IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id))) { + correctType = BotSpellTypes::Invisibility; + } + else if (spellType == BotSpellTypes::MovementSpeed && IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) { + correctType = BotSpellTypes::MovementSpeed; + } + else if (!teleportZone.compare("") && IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Translocate) || IsEffectInSpell(spell_id, SE_GateToHomeCity))) { + correctType = BotSpellTypes::SendHome; + } + else if (IsEffectInSpell(spell_id, SE_SummonCorpse)) { + correctType = BotSpellTypes::SummonCorpse; + } + + if (correctType == UINT16_MAX) { + if (IsSummonPetSpell(spell_id) || IsEffectInSpell(spell_id, SE_TemporaryPets)) { + correctType = BotSpellTypes::Pet; + } + else if (IsMesmerizeSpell(spell_id)) { + correctType = BotSpellTypes::Mez; + } + else if (IsEscapeSpell(spell_id)) { + correctType = BotSpellTypes::Escape; + } + else if (IsDetrimentalSpell(spell_id) && IsEffectInSpell(spell_id, SE_Root)) { + if (IsAnyAESpell(spell_id)) { + correctType = BotSpellTypes::AERoot; + } + else { + correctType = BotSpellTypes::Root; + } + } + else if (IsDetrimentalSpell(spell_id) && IsLifetapSpell(spell_id)) { + correctType = BotSpellTypes::Lifetap; + } + else if (IsDetrimentalSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) { + correctType = BotSpellTypes::Snare; + } + else if (IsDetrimentalSpell(spell_id) && (IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id))) { + correctType = BotSpellTypes::DOT; + } + else if (IsDispelSpell(spell_id)) { + correctType = BotSpellTypes::Dispel; + } + else if (IsDetrimentalSpell(spell_id) && IsSlowSpell(spell_id)) { + correctType = BotSpellTypes::Slow; + } + else if (IsDebuffSpell(spell_id) && !IsHateReduxSpell(spell_id) && !IsHateSpell(spell_id)) { + correctType = BotSpellTypes::Debuff; + } + else if (IsHateReduxSpell(spell_id)) { + correctType = BotSpellTypes::HateRedux; + } + else if (IsDetrimentalSpell(spell_id) && IsHateSpell(spell_id)) { + correctType = BotSpellTypes::HateLine; + } + else if ( + IsBuffSpell(spell_id) && + IsBeneficialSpell(spell_id) && + IsBardSong(spell_id) + ) { + if ( + spellType == BotSpellTypes::InCombatBuffSong || + spellType == BotSpellTypes::OutOfCombatBuffSong || + spellType == BotSpellTypes::PreCombatBuffSong + ) { + correctType = spellType; + } + else { + correctType = BotSpellTypes::OutOfCombatBuffSong; + } + } + else if ( + !IsBardSong(spell_id) && + ( + (IsSelfConversionSpell(spell_id) && spell.buff_duration < 1) || + (spellType == BotSpellTypes::InCombatBuff && IsAnyBuffSpell(spell_id)) + ) + ) { + correctType = BotSpellTypes::InCombatBuff; + } + else if ( + spellType == BotSpellTypes::PreCombatBuff && + IsAnyBuffSpell(spell_id) && + !IsBardSong(spell_id) + ) { + correctType = BotSpellTypes::PreCombatBuff; + } + else if ( + (IsCureSpell(spell_id) && spellType == BotSpellTypes::Cure) || + (IsCureSpell(spell_id) && !IsAnyHealSpell(spell_id)) + ) { + correctType = BotSpellTypes::Cure; + } + else if (IsAnyNukeOrStunSpell(spell_id)) { + if (IsAnyAESpell(spell_id)) { + if (IsAERainSpell(spell_id)) { + correctType = BotSpellTypes::AERains; + } + else if (IsPBAENukeSpell(spell_id)) { + correctType = BotSpellTypes::PBAENuke; + } + else if (IsStunSpell(spell_id)) { + correctType = BotSpellTypes::AEStun; + } + else { + correctType = BotSpellTypes::AENukes; + } + } + else if (IsStunSpell(spell_id)) { + correctType = BotSpellTypes::Stun; + } + else { + correctType = BotSpellTypes::Nuke; + } + } + else if (IsAnyHealSpell(spell_id)) { + if (IsGroupSpell(spell_id)) { + if (IsGroupCompleteHealSpell(spell_id)) { + correctType = BotSpellTypes::GroupCompleteHeals; + } + else if (IsGroupHealOverTimeSpell(spell_id)) { + correctType = BotSpellTypes::GroupHoTHeals; + } + else if (IsRegularGroupHealSpell(spell_id)) { + correctType = BotSpellTypes::GroupHeals; + } + } + else { + if (IsVeryFastHealSpell(spell_id)) { + correctType = BotSpellTypes::VeryFastHeals; + } + else if (IsFastHealSpell(spell_id)) { + correctType = BotSpellTypes::FastHeals; + } + else if (IsCompleteHealSpell(spell_id)) { + correctType = BotSpellTypes::CompleteHeal; + } + else if (IsHealOverTimeSpell(spell_id)) { + correctType = BotSpellTypes::HoTHeals; + } + else if (IsRegularSingleTargetHealSpell(spell_id)) { + correctType = BotSpellTypes::RegularHeal; + } + else if (IsRegularPetHealSpell(spell_id)) { + correctType = BotSpellTypes::RegularHeal; + } + } + } + else if (IsAnyBuffSpell(spell_id)) { + if (IsResistanceOnlySpell(spell_id)) { + correctType = BotSpellTypes::ResistBuffs; + } + else if (IsDamageShieldOnlySpell(spell_id)) { + correctType = BotSpellTypes::DamageShields; + } + else { + correctType = BotSpellTypes::Buff; + } + } + } + + + return correctType; +} + +uint16 GetPetSpellType(uint16 spellType) { + switch (spellType) { + case BotSpellTypes::Buff: + return BotSpellTypes::PetBuffs; + case BotSpellTypes::RegularHeal: + return BotSpellTypes::PetRegularHeals; + case BotSpellTypes::CompleteHeal: + return BotSpellTypes::PetCompleteHeals; + case BotSpellTypes::FastHeals: + return BotSpellTypes::PetFastHeals; + case BotSpellTypes::VeryFastHeals: + return BotSpellTypes::PetVeryFastHeals; + case BotSpellTypes::HoTHeals: + return BotSpellTypes::PetHoTHeals; + case BotSpellTypes::DamageShields: + return BotSpellTypes::PetDamageShields; + case BotSpellTypes::ResistBuffs: + return BotSpellTypes::PetResistBuffs; + default: + return spellType; + } + + return spellType; +} diff --git a/common/spdat.h b/common/spdat.h index e030143c7..ffe3525ad 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -752,6 +752,8 @@ bool SpellTypeRequiresTarget(uint16 spellType, uint16 cls = 0); bool SpellTypeRequiresAEChecks(uint16 spellType); bool IsCommandedSpellType(uint16 spellType); bool IsPullingSpellType(uint16 spellType); +uint16 GetCorrectSpellType(uint16 spellType, uint16 spell_id); +uint16 GetPetSpellType(uint16 spellType); // These should not be used to determine spell category.. // They are a graphical affects (effects?) index only @@ -1748,5 +1750,6 @@ bool IsResurrectSpell(uint16 spell_id); bool RequiresStackCheck(uint16 spellType); bool IsResistanceOnlySpell(uint16 spell_id); bool IsDamageShieldOnlySpell(uint16 spell_id); +bool IsHateSpell(uint16 spell_id); #endif diff --git a/zone/bot.cpp b/zone/bot.cpp index 747a30d9c..86ea16881 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -3560,7 +3560,7 @@ bool Bot::Spawn(Client* botCharacterOwner) { } if (RuleB(Bots, RunSpellTypeChecksOnSpawn)) { - OwnerMessage("Running SpellType checks. There may be some spells that are flagged as incorrect but actually are correct. Use this as a guideline."); + OwnerMessage("Running SpellType checks. There may be some spells that are mislabeled as incorrect. Use this as a loose guideline."); CheckBotSpells(); //This runs through a serious of checks and outputs any spells that are set to the wrong spell type in the database } diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index c6ac70397..835e99040 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -2753,10 +2753,11 @@ BotSpell Bot::GetBestBotSpellForNukeByBodyType(Bot* botCaster, uint8 bodyType, u } void Bot::CheckBotSpells() { - bool valid = false; - uint16 correctType; auto spellList = BotSpellsEntriesRepository::All(content_db); uint16 spell_id; + SPDat_Spell_Struct spell; + uint16 correctType; + uint16 parentType; for (const auto& s : spellList) { if (!IsValidSpell(s.spell_id)) { @@ -2764,64 +2765,65 @@ void Bot::CheckBotSpells() { continue; } - spell_id = s.spell_id; + spell = spells[s.spell_id]; + spell_id = spell.id; - if (spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] >= 255) { + if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) { LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id); //deleteme } else { - if (spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] > s.minlevel) { + if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) { LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}." , GetSpellName(spell_id) , spell_id - , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] , GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX) , s.npc_spells_id , s.minlevel ); //deleteme LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]" - , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] , spell_id , s.npc_spells_id , GetSpellName(spell_id) , spell_id , s.minlevel - , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] , GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX) , s.npc_spells_id ); //deleteme } - if (spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] < s.minlevel) { + if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) { LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}." , GetSpellName(spell_id) , spell_id - , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] , GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX) , s.npc_spells_id , s.minlevel ); //deleteme LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]" - , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] , spell_id , s.npc_spells_id , GetSpellName(spell_id) , spell_id , s.minlevel - , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] , GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX) , s.npc_spells_id ); //deleteme } - if (spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] > s.maxlevel) { + if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) { LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}." , GetSpellName(spell_id) , spell_id - , spells[spell_id].classes[s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX] + , spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] , GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX) , s.npc_spells_id , s.maxlevel @@ -2829,380 +2831,33 @@ void Bot::CheckBotSpells() { } } - correctType = UINT16_MAX; - valid = false; + correctType = GetCorrectSpellType(s.type, spell_id); + parentType = GetSpellListSpellType(correctType); - - switch (s.type) { - case BotSpellTypes::Nuke: - if (IsAnyNukeOrStunSpell(spell_id) && !IsEffectInSpell(spell_id, SE_Root) && !IsDebuffSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::RegularHeal: - if (IsAnyHealSpell(spell_id) && !IsEscapeSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::Root: - if (IsEffectInSpell(spell_id, SE_Root)) { - valid = true; - break; - } - break; - case BotSpellTypes::Buff: - if (IsAnyBuffSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::Pet: - if (IsSummonPetSpell(spell_id) || IsEffectInSpell(spell_id, SE_TemporaryPets)) { - valid = true; - break; - } - break; - case BotSpellTypes::Lifetap: - if (IsLifetapSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::Snare: - if (IsEffectInSpell(spell_id, SE_MovementSpeed) && IsDetrimentalSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::DOT: - if (IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::Dispel: - if (IsDispelSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::InCombatBuff: - if ( - IsSelfConversionSpell(spell_id) || - IsAnyBuffSpell(spell_id) - ) { - valid = true; - break; - } - break; - case BotSpellTypes::HateLine: - if ( - (IsEffectInSpell(spell_id, SE_Hate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_Hate)] > 0) || - (IsEffectInSpell(spell_id, SE_InstantHate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_InstantHate)] > 0) - ) { - valid = true; - break; - } - break; - case BotSpellTypes::Mez: - if (IsMesmerizeSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::Charm: - if (IsCharmSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::Slow: - if (IsSlowSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::Debuff: - if (IsDebuffSpell(spell_id) && !IsEscapeSpell(spell_id) && !IsHateReduxSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::Cure: - if (IsCureSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::PreCombatBuff: - if ( - IsBuffSpell(spell_id) && - IsBeneficialSpell(spell_id) && - !IsBardSong(spell_id) && - !IsEscapeSpell(spell_id) && - (!IsSummonPetSpell(spell_id) && !IsEffectInSpell(spell_id, SE_TemporaryPets)) - ) { - valid = true; - break; - } - break; - case BotSpellTypes::InCombatBuffSong: - case BotSpellTypes::OutOfCombatBuffSong: - case BotSpellTypes::PreCombatBuffSong: - if ( - IsBuffSpell(spell_id) && - IsBeneficialSpell(spell_id) && - IsBardSong(spell_id) && - !IsEscapeSpell(spell_id) && - (!IsSummonPetSpell(spell_id) && !IsEffectInSpell(spell_id, SE_TemporaryPets)) - ) { - valid = true; - break; - } - break; - case BotSpellTypes::Fear: - if (IsFearSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::Escape: - if (IsEscapeSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::HateRedux: - if (IsHateReduxSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::Resurrect: - if (IsEffectInSpell(spell_id, SE_Revive)) { - valid = true; - break; - } - break; - case BotSpellTypes::Lull: - if (IsHarmonySpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::Teleport: - if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))) { - valid = true; - break; - } - break; - case BotSpellTypes::Succor: - if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Succor)) { - valid = true; - break; - } - break; - case BotSpellTypes::BindAffinity: - if (IsEffectInSpell(spell_id, SE_BindAffinity)) { - valid = true; - break; - } - break; - case BotSpellTypes::Identify: - if (IsEffectInSpell(spell_id, SE_Identify)) { - valid = true; - break; - } - break; - case BotSpellTypes::Levitate: - if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Levitate))) { - valid = true; - break; - } - break; - case BotSpellTypes::Rune: - if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune)) { - valid = true; - break; - } - break; - case BotSpellTypes::WaterBreathing: - if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_WaterBreathing)) { - valid = true; - break; - } - break; - case BotSpellTypes::Size: - if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))) { - valid = true; - break; - } - break; - case BotSpellTypes::Invisibility: - if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id)) { - valid = true; - break; - } - break; - case BotSpellTypes::MovementSpeed: - if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) { - valid = true; - break; - } - break; - case BotSpellTypes::SendHome: - if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_GateToHomeCity)) { - valid = true; - break; - } - break; - case BotSpellTypes::SummonCorpse: - if (IsEffectInSpell(spell_id, SE_SummonCorpse)) { - valid = true; - break; - } - break; - default: - break; - - } - - if (IsAnyNukeOrStunSpell(spell_id) && !IsEffectInSpell(spell_id, SE_Root) && !IsDebuffSpell(spell_id)) { - correctType = BotSpellTypes::Nuke; - } - else if (IsAnyHealSpell(spell_id) && !IsEscapeSpell(spell_id)) { - correctType = BotSpellTypes::RegularHeal; - } - else if (IsEffectInSpell(spell_id, SE_Root)) { - correctType = BotSpellTypes::Root; - } - else if (IsAnyBuffSpell(spell_id)) { - correctType = BotSpellTypes::Buff; - } - else if (IsSummonPetSpell(spell_id) || IsEffectInSpell(spell_id, SE_TemporaryPets)) { - correctType = BotSpellTypes::Pet; - } - else if (IsLifetapSpell(spell_id)) { - correctType = BotSpellTypes::Lifetap; - } - else if (IsEffectInSpell(spell_id, SE_MovementSpeed) && IsDetrimentalSpell(spell_id)) { - correctType = BotSpellTypes::Snare; - } - else if (IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id)) { - correctType = BotSpellTypes::DOT; - } - else if (IsDispelSpell(spell_id)) { - correctType = BotSpellTypes::Dispel; - } - else if ( - IsSelfConversionSpell(spell_id) || - IsAnyBuffSpell(spell_id) - ) { - correctType = BotSpellTypes::InCombatBuff; - } - else if ( - (IsEffectInSpell(spell_id, SE_Hate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_Hate)] > 0) || - (IsEffectInSpell(spell_id, SE_InstantHate) && spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_InstantHate)] > 0) - ) { - correctType = BotSpellTypes::HateLine; - } - else if (IsMesmerizeSpell(spell_id)) { - correctType = BotSpellTypes::Mez; - } - else if (IsCharmSpell(spell_id)) { - correctType = BotSpellTypes::Charm; - } - else if (IsSlowSpell(spell_id)) { - correctType = BotSpellTypes::Slow; - } - else if (IsDebuffSpell(spell_id) && !IsEscapeSpell(spell_id) && !IsHateReduxSpell(spell_id)) { - correctType = BotSpellTypes::Debuff; - } - else if (IsCureSpell(spell_id)) { - correctType = BotSpellTypes::Cure; - } - else if ( - IsBuffSpell(spell_id) && - IsBeneficialSpell(spell_id) && - IsBardSong(spell_id) && - !IsEscapeSpell(spell_id) && - (!IsSummonPetSpell(spell_id) && !IsEffectInSpell(spell_id, SE_TemporaryPets)) - ) { - if ( - s.type == BotSpellTypes::InCombatBuffSong || - s.type == BotSpellTypes::OutOfCombatBuffSong || - s.type == BotSpellTypes::PreCombatBuffSong - ) { - correctType = s.type; - } - else { - correctType = BotSpellTypes::OutOfCombatBuffSong; + if (RuleB(Bots, UseParentSpellTypeForChecks)) { + if (s.type == parentType || s.type == correctType) { + continue; } } - else if ( - IsBuffSpell(spell_id) && - IsBeneficialSpell(spell_id) && - !IsBardSong(spell_id) && - !IsEscapeSpell(spell_id) && - (!IsSummonPetSpell(spell_id) && !IsEffectInSpell(spell_id, SE_TemporaryPets)) - ) { - correctType = BotSpellTypes::PreCombatBuff; - } - else if (IsFearSpell(spell_id)) { - correctType = BotSpellTypes::Fear; - } - else if (IsEscapeSpell(spell_id)) { - correctType = BotSpellTypes::Escape; - } - else if (IsHateReduxSpell(spell_id)) { - correctType = BotSpellTypes::HateRedux; - } - else if (IsEffectInSpell(spell_id, SE_Revive)) { - correctType = BotSpellTypes::Resurrect; - } - else if (IsHarmonySpell(spell_id)) { - correctType = BotSpellTypes::Lull; - } - else if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))) { - correctType = BotSpellTypes::Teleport; - } - else if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Succor)) { - correctType = BotSpellTypes::Succor; - } - else if (IsEffectInSpell(spell_id, SE_BindAffinity)) { - correctType = BotSpellTypes::BindAffinity; - } - else if (IsEffectInSpell(spell_id, SE_Identify)) { - correctType = BotSpellTypes::Identify; - } - else if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_Levitate))) { - correctType = BotSpellTypes::Levitate; - } - else if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune)) { - correctType = BotSpellTypes::Rune; - } - else if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_WaterBreathing)) { - correctType = BotSpellTypes::WaterBreathing; - } - else if (IsBeneficialSpell(spell_id) && (IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))) { - correctType = BotSpellTypes::Size; - } - else if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id)) { - correctType = BotSpellTypes::Invisibility; - } - else if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) { - correctType = BotSpellTypes::MovementSpeed; - } - else if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_GateToHomeCity)) { - correctType = BotSpellTypes::SendHome; - } - else if (IsEffectInSpell(spell_id, SE_SummonCorpse)) { - correctType = BotSpellTypes::SummonCorpse; + else { + if (IsPetBotSpellType(s.type)) { + correctType = GetPetSpellType(correctType); + } } - if (!valid || (correctType == UINT16_MAX) || (s.type != correctType)) { + if (correctType == s.type) { + continue; + } + + if (correctType == UINT16_MAX) { + LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown." + , GetSpellName(spell_id) + , spell_id + , GetSpellTypeNameByID(s.type) + , s.type + ); //deleteme + } + else { LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]" , GetSpellName(spell_id) , spell_id @@ -3211,7 +2866,7 @@ void Bot::CheckBotSpells() { , GetSpellTypeNameByID(correctType) , correctType ); //deleteme - LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spellid` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]" + LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]" , correctType , spell_id , GetSpellName(spell_id) From afbf1b74c4278ec10de41b4296480e9cb90cc070 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 11 Dec 2024 07:18:40 -0600 Subject: [PATCH 85/97] Add IsBlockedBuff to CastChecks --- zone/bot.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 86ea16881..5ecd824ae 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9652,7 +9652,7 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec spells[spell_id].target_type == ST_Self && tar != this && (spellType != BotSpellTypes::SummonCorpse || RuleB(Bots, AllowCommandedSummonCorpse)) - ) { + ) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to ST_Self.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } @@ -9667,6 +9667,11 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec return false; } + if (IsBeneficialSpell(spell_id) && tar->IsBlockedBuff(spell_id)) { + LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to IsBlockedBuff.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + LogBotPreChecksDetail("{} says, 'Doing CanCastSpellType checks of {} on {}.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme if (!CanCastSpellType(spellType, spell_id, tar)) { return false; From 186b06ef47fd4fabc277aab6360f9d29a3a7551c Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Wed, 11 Dec 2024 07:19:17 -0600 Subject: [PATCH 86/97] Add spellid option to ^cast to allow casting of a specific spell by ID --- zone/bot.cpp | 135 +++++++++++++++++++- zone/bot.h | 2 +- zone/bot_commands/cast.cpp | 249 ++++++++++++++++++++++++++----------- 3 files changed, 308 insertions(+), 78 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 5ecd824ae..a823b01af 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9709,7 +9709,7 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec return false; } - if (spellType == UINT16_MAX) { //AA cast checks, return here + if (spellType == UINT16_MAX) { //AA/Forced cast checks, return here return true; } @@ -11098,7 +11098,7 @@ bool Bot::AttemptAICastSpell(uint16 spellType) { } bool Bot::AttemptAACastSpell(Mob* tar, uint16 spell_id, AA::Rank* rank) { - if (!tar) { + if (!tar || spells[spell_id].target_type == ST_Self) { tar = this; } @@ -11132,11 +11132,25 @@ bool Bot::AttemptAACastSpell(Mob* tar, uint16 spell_id, AA::Rank* rank) { } + if (IsCasting()) { + BotGroupSay( + this, + fmt::format( + "Interrupting {}. I have been commanded to try to cast an AA - {} on {}.", + CastingSpellID() ? spells[CastingSpellID()].name : "my spell", + spells[spell_id].name, + tar->GetCleanName() + ).c_str() + ); + + InterruptSpell(); + } + if (CastSpell(spell_id, tar->GetID())) { BotGroupSay( this, fmt::format( - "Casting {} on {}.", + "Casting an AA - {} on {}.", GetSpellName(spell_id), (tar == this ? "myself" : tar->GetCleanName()) ).c_str() @@ -11172,6 +11186,121 @@ bool Bot::AttemptAACastSpell(Mob* tar, uint16 spell_id, AA::Rank* rank) { return true; } +bool Bot::AttemptForcedCastSpell(Mob* tar, uint16 spell_id) { + SPDat_Spell_Struct spell = spells[spell_id]; + uint16 forcedSpellID = spell.id; + + if (!tar || (spells[spell_id].target_type == ST_Self && tar != this)) { + LogTestDebug("{} set my target to myself for {} [#{}] due to !tar.", GetCleanName(), spell.name, forcedSpellID); //deleteme + tar = this; + } + + if (IsBeneficialSpell(forcedSpellID)) { + if ( + (tar->IsNPC() && !tar->GetOwner()) || + (tar->GetOwner() && tar->GetOwner()->IsOfClientBot() && !GetBotOwner()->IsInGroupOrRaid(tar->GetOwner())) || + (tar->IsOfClientBot() && !GetBotOwner()->IsInGroupOrRaid(tar)) + ) { + GetBotOwner()->Message( + Chat::Yellow, + fmt::format( + "[{}] is an invalid target. Only players or their pet in your group or raid are eligible targets." + , tar->GetCleanName() + ).c_str() + ); + + return false; + } + } + + if (IsDetrimentalSpell(forcedSpellID) && (!GetBotOwner()->IsAttackAllowed(tar) || !IsAttackAllowed(tar))) { + GetBotOwner()->Message( + Chat::Yellow, + fmt::format( + "{} says, 'I cannot attack [{}]'.", + GetCleanName(), + tar->GetCleanName() + ).c_str() + ); + + return false; + } + + if (!CheckSpellRecastTimer(forcedSpellID)) { + LogTestDebug("{} failed CheckSpellRecastTimer for {} [#{}].", GetCleanName(), spell.name, forcedSpellID); //deleteme + return false; + } + + if ( + !RuleB(Bots, EnableBotTGB) && + IsGroupSpell(forcedSpellID) && + !IsTGBCompatibleSpell(forcedSpellID) && + !IsInGroupOrRaid(tar, true) + ) { + LogTestDebug("{} failed TGB for {} [#{}].", GetCleanName(), spell.name, forcedSpellID); //deleteme + return false; + } + + if (!DoLosChecks(this, tar)) { + LogTestDebug("{} failed LoS for {} [#{}].", GetCleanName(), spell.name, forcedSpellID); //deleteme + return false; + } + + if (!CastChecks(forcedSpellID, tar, UINT16_MAX)) { + LogTestDebug("{} failed CastChecks for {} [#{}].", GetCleanName(), spell.name, forcedSpellID); //deleteme + GetBotOwner()->Message( + Chat::Red, + fmt::format( + "{} says, 'Ability failed to cast. This could be due to this to any number of things: range, mana, immune, etc.'", + GetBotOwner()->GetCleanName() + ).c_str() + ); + + return false; + } + + if (IsCasting()) { + BotGroupSay( + this, + fmt::format( + "Interrupting {}. I have been commanded to try to cast {} on {}.", + CastingSpellID() ? spells[CastingSpellID()].name : "my spell", + spell.name, + tar->GetCleanName() + ).c_str() + ); + + InterruptSpell(); + } + + if (CastSpell(forcedSpellID, tar->GetID())) { + BotGroupSay( + this, + fmt::format( + "Casting {} on {}.", + GetSpellName(forcedSpellID), + (tar == this ? "myself" : tar->GetCleanName()) + ).c_str() + ); + + int timer_duration = CalcBuffDuration(tar, this, forcedSpellID); + + if (timer_duration) { // negatives are perma buffs + timer_duration = GetActSpellDuration(forcedSpellID, timer_duration); + } + + if (timer_duration < 0) { + timer_duration = 0; + } + + SetSpellRecastTimer(forcedSpellID, timer_duration); + + return true; + } + + return false; +} + uint16 Bot::GetSpellListSpellType(uint16 spellType) { switch (spellType) { case BotSpellTypes::AENukes: diff --git a/zone/bot.h b/zone/bot.h index 3371a6b99..98475e909 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -403,6 +403,7 @@ public: bool AICastSpell(Mob* tar, uint8 iChance, uint16 spellType, uint16 subTargetType = UINT16_MAX, uint16 subType = UINT16_MAX); bool AttemptAICastSpell(uint16 spellType); bool AttemptAACastSpell(Mob* tar, uint16 spell_id, AA::Rank* rank); + bool AttemptForcedCastSpell(Mob* tar, uint16 spell_id); bool AI_EngagedCastCheck() override; bool AI_PursueCastCheck() override; bool AI_IdleCastCheck() override; @@ -546,7 +547,6 @@ public: void CheckBotSpells(); - [[nodiscard]] int GetMaxBuffSlots() const final { return EQ::spells::LONG_BUFFS; } [[nodiscard]] int GetMaxSongSlots() const final { return EQ::spells::SHORT_BUFFS; } [[nodiscard]] int GetMaxDiscSlots() const final { return EQ::spells::DISC_BUFFS; } diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index 5866bacf4..65d03ad2b 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -46,22 +46,6 @@ void bot_command_cast(Client* c, const Seperator* sep) ) }; std::vector examples_two = - { - "To tell all Enchanters to slow the target:", - fmt::format( - "{} {} byclass {}", - sep->arg[0], - Class::Enchanter, - c->GetSpellTypeShortNameByID(BotSpellTypes::Slow) - ), - fmt::format( - "{} {} byclass {}", - sep->arg[0], - Class::Enchanter, - BotSpellTypes::Slow - ) - }; - std::vector examples_three = { "To tell Skbot to Harm Touch the target:", fmt::format( @@ -73,6 +57,14 @@ void bot_command_cast(Client* c, const Seperator* sep) sep->arg[0] ) }; + std::vector examples_three = + { + "To tell all bots to try to cast spell #93 (Burst of Flame)", + fmt::format( + "{} spellid 93", + sep->arg[0] + ) + }; std::vector actionables = { @@ -188,8 +180,15 @@ void bot_command_cast(Client* c, const Seperator* sep) uint16 subTargetType = UINT16_MAX; bool aaType = false; int aaID = 0; + bool bySpellID = false; + uint16 chosenSpellID = UINT16_MAX; if (!arg1.compare("aa") || !arg1.compare("harmtouch") || !arg1.compare("layonhands")) { + if (!RuleB(Bots, AllowForcedCastsBySpellID)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + if (!arg1.compare("harmtouch")) { aaID = zone->GetAlternateAdvancementAbilityByRank(aaHarmTouch)->id; } @@ -208,8 +207,25 @@ void bot_command_cast(Client* c, const Seperator* sep) aaType = true; } - if (!aaType) { - // String/Int type checks + if (!arg1.compare("spellid")) { + if (!RuleB(Bots, AllowCastAAs)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + if (sep->IsNumber(2) && IsValidSpell(atoi(sep->arg[2]))) { + ++ab_arg; + chosenSpellID = atoi(sep->arg[2]); + bySpellID = true; + } + else { + c->Message(Chat::Yellow, "You must enter a valid spell ID."); + + return; + } + } + + if (!aaType && !bySpellID) { if (sep->IsNumber(1)) { spellType = atoi(sep->arg[1]); @@ -342,7 +358,7 @@ void bot_command_cast(Client* c, const Seperator* sep) spellType == BotSpellTypes::PetHoTHeals || spellType == BotSpellTypes::PetRegularHeals || spellType == BotSpellTypes::PetVeryFastHeals - ) { + ) { c->Message(Chat::Yellow, "Pet type heals and buffs are not supported, use the regular spell type."); return; } @@ -352,62 +368,88 @@ void bot_command_cast(Client* c, const Seperator* sep) //LogTestDebug("{}: 'Attempting {} [{}-{}] on {}'", __LINE__, c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (subTargetType != UINT16_MAX ? c->GetSubTypeNameByID(subTargetType) : "Standard"), (tar ? tar->GetCleanName() : "NOBODY")); //deleteme if (!tar) { - if (!aaType && spellType != BotSpellTypes::Escape && spellType != BotSpellTypes::Pet) { + if ((!aaType && !bySpellID) && spellType != BotSpellTypes::Escape && spellType != BotSpellTypes::Pet) { c->Message(Chat::Yellow, "You need a target for that."); return; } } - switch (spellType) { //Target Checks - case BotSpellTypes::Resurrect: - if (!tar->IsCorpse() || !tar->CastToCorpse()->IsPlayerCorpse()) { - c->Message(Chat::Yellow, "[%s] is not a player's corpse.", tar->GetCleanName()); - - return; - } - - break; - case BotSpellTypes::Identify: - case BotSpellTypes::SendHome: - case BotSpellTypes::BindAffinity: - case BotSpellTypes::SummonCorpse: - if (!tar->IsClient() || !c->IsInGroupOrRaid(tar)) { - c->Message(Chat::Yellow, "[%s] is an invalid target. Only players in your group or raid are eligible targets.", tar->GetCleanName()); - - return; - } - - break; - default: - if ( - (IsBotSpellTypeDetrimental(spellType) && !c->IsAttackAllowed(tar)) || - ( - spellType == BotSpellTypes::Charm && - ( - tar->IsClient() || - tar->IsCorpse() || - tar->GetOwner() - ) - ) - ) { - c->Message(Chat::Yellow, "You cannot attack [%s].", tar->GetCleanName()); - - return; - } - - if (IsBotSpellTypeBeneficial(spellType)) { - if ( - (tar->IsNPC() && !tar->GetOwner()) || - (tar->GetOwner() && tar->GetOwner()->IsOfClientBot() && !c->IsInGroupOrRaid(tar->GetOwner())) || - (tar->IsOfClientBot() && !c->IsInGroupOrRaid(tar)) - ) { - c->Message(Chat::Yellow, "[%s] is an invalid target. Only players or their pet in your group or raid are eligible targets.", tar->GetCleanName()); + if (!aaType && !bySpellID) { + switch (spellType) { //Target Checks + case BotSpellTypes::Resurrect: + if (!tar->IsCorpse() || !tar->CastToCorpse()->IsPlayerCorpse()) { + c->Message( + Chat::Yellow, + fmt::format( + "[{}] is not a player's corpse.", + tar->GetCleanName() + ).c_str() + ); return; } - } - break; + break; + case BotSpellTypes::Identify: + case BotSpellTypes::SendHome: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::SummonCorpse: + if (!tar->IsClient() || !c->IsInGroupOrRaid(tar)) { + c->Message( + Chat::Yellow, + fmt::format( + "[{}] is an invalid target. Only players in your group or raid are eligible targets.", + tar->GetCleanName() + ).c_str() + ); + + return; + } + + break; + default: + if ( + (IsBotSpellTypeDetrimental(spellType) && !c->IsAttackAllowed(tar)) || + ( + spellType == BotSpellTypes::Charm && + ( + tar->IsClient() || + tar->IsCorpse() || + tar->GetOwner() + ) + ) + ) { + c->Message( + Chat::Yellow, + fmt::format( + "You cannot attack [{}].", + tar->GetCleanName() + ).c_str() + ); + + return; + } + + if (IsBotSpellTypeBeneficial(spellType)) { + if ( + (tar->IsNPC() && !tar->GetOwner()) || + (tar->GetOwner() && tar->GetOwner()->IsOfClientBot() && !c->IsInGroupOrRaid(tar->GetOwner())) || + (tar->IsOfClientBot() && !c->IsInGroupOrRaid(tar)) + ) { + c->Message( + Chat::Yellow, + fmt::format( + "[{}] is an invalid target. Only players or their pet in your group or raid are eligible targets.", + tar->GetCleanName() + ).c_str() + ); + + return; + } + } + + break; + } } const int ab_mask = ActionableBots::ABM_Type1; @@ -451,7 +493,7 @@ void bot_command_cast(Client* c, const Seperator* sep) Mob* newTar = tar; - if (!aaType) { + if (!aaType && !bySpellID) { //LogTestDebug("{}: {} says, 'Attempting {} [{}-{}] on {}'", __LINE__, bot_iter->GetCleanName(), c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (subTargetType != UINT16_MAX ? c->GetSubTypeNameByID(subTargetType) : "Standard"), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme if (!SpellTypeRequiresTarget(spellType, bot_iter->GetClass())) { newTar = bot_iter; @@ -502,9 +544,48 @@ void bot_command_cast(Client* c, const Seperator* sep) isSuccess = true; ++successCount; + + continue; + } + else if (bySpellID) { + SPDat_Spell_Struct spell = spells[chosenSpellID]; + + LogTestDebug("Starting bySpellID checks."); //deleteme + if (!bot_iter->HasBotSpellEntry(chosenSpellID)) { + LogTestDebug("{} does not have {} [#{}].", bot_iter->GetCleanName(), spell.name, chosenSpellID); //deleteme + continue; + } + + if (!tar || (spell.target_type == ST_Self && tar != bot_iter)) { + LogTestDebug("{} set my target to myself for {} [#{}] due to !tar.", bot_iter->GetCleanName(), spell.name, chosenSpellID); //deleteme + tar = bot_iter; + } + + if (bot_iter->AttemptForcedCastSpell(tar, chosenSpellID)) { + if (!firstFound) { + firstFound = bot_iter; + } + + isSuccess = true; + ++successCount; + } + else { + c->Message( + Chat::Red, + fmt::format( + "{} says, '{} [#{}] failed to cast on [{}]. This could be due to this to any number of things: range, mana, immune, etc.'", + bot_iter->GetCleanName(), + spell.name, + chosenSpellID, + tar->GetCleanName() + ).c_str() + ); + } + + continue; } else { - LogTestDebug("{}: {} says, 'Attempting {} [{}-{}] on {}'", __LINE__, bot_iter->GetCleanName(), c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (subTargetType != UINT16_MAX ? c->GetSubTypeNameByID(subTargetType) : "Standard"), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme + LogTestDebug("{}: {} says, 'Attempting {} [{}-{}] on [{}]'", __LINE__, bot_iter->GetCleanName(), c->GetSpellTypeNameByID(spellType), (subType != UINT16_MAX ? c->GetSubTypeNameByID(subType) : "Standard"), (subTargetType != UINT16_MAX ? c->GetSubTypeNameByID(subTargetType) : "Standard"), (newTar ? newTar->GetCleanName() : "NOBODY")); //deleteme bot_iter->SetCommandedSpell(true); if (bot_iter->AICastSpell(newTar, 100, spellType, subTargetType, subType)) { @@ -516,34 +597,54 @@ void bot_command_cast(Client* c, const Seperator* sep) ++successCount; } else { - bot_iter->GetBotOwner()->Message(Chat::Red, "%s says, 'Ability failed to cast. This could be due to this to any number of things: range, mana, immune, etc.'", bot_iter->GetCleanName()); - - continue; + c->Message( + Chat::Red, + fmt::format( + "{} says, 'Ability failed to cast [{}]. This could be due to this to any number of things: range, mana, immune, etc.'", + bot_iter->GetCleanName(), + tar->GetCleanName() + ).c_str() + ); } bot_iter->SetCommandedSpell(false); + + continue; } continue; } + std::string type = ""; + + if (aaType) { + type = zone->GetAAName(zone->GetAlternateAdvancementAbility(aaID)->first_rank_id); + } + else if (bySpellID) { + type = "Forced"; + } + else { + type = c->GetSpellTypeNameByID(spellType); + } + if (!isSuccess) { c->Message( Chat::Yellow, fmt::format( "No bots are capable of casting [{}] on {}.", - (!aaType ? c->GetSpellTypeNameByID(spellType) : zone->GetAAName(zone->GetAlternateAdvancementAbility(aaID)->first_rank_id)), + (bySpellID ? spells[chosenSpellID].name : type), tar ? tar->GetCleanName() : "your target" ).c_str() ); } else { - c->Message( Chat::Yellow, + c->Message( + Chat::Yellow, fmt::format( "{} {} [{}]{}", ((successCount == 1 && firstFound) ? firstFound->GetCleanName() : (fmt::format("{}", successCount).c_str())), ((successCount == 1 && firstFound) ? "casted" : "of your bots casted"), - (!aaType ? c->GetSpellTypeNameByID(spellType) : zone->GetAAName(zone->GetAlternateAdvancementAbility(aaID)->first_rank_id)), + (bySpellID ? spells[chosenSpellID].name : type), tar ? (fmt::format(" on {}.", tar->GetCleanName()).c_str()) : "." ).c_str() ); From 783a5f0adf4fea75cb2974d61a12b78f5b4b9a87 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 12 Dec 2024 07:59:54 -0600 Subject: [PATCH 87/97] ^cast adjustments for spellid casts --- zone/bot.cpp | 8 ++++++++ zone/bot.h | 1 + zone/bot_commands/cast.cpp | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index a823b01af..d685a41f2 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -11195,6 +11195,14 @@ bool Bot::AttemptForcedCastSpell(Mob* tar, uint16 spell_id) { tar = this; } + if ((IsCharmSpell(forcedSpellID) || IsPetSpell(forcedSpellID) && HasPet())) { + return false; + } + + if (IsResurrectSpell(forcedSpellID) && (!tar->IsCorpse() || !tar->CastToCorpse()->IsPlayerCorpse())) { + return false; + } + if (IsBeneficialSpell(forcedSpellID)) { if ( (tar->IsNPC() && !tar->GetOwner()) || diff --git a/zone/bot.h b/zone/bot.h index 98475e909..287721ae9 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -752,6 +752,7 @@ public: // "Quest API" Methods bool HasBotSpellEntry(uint16 spell_id); + bool CanUseBotSpell(uint16 spell_id); void ApplySpell(int spell_id, int duration = 0, int level = -1, ApplySpellType apply_type = ApplySpellType::Solo, bool allow_pets = false, bool is_raid_group_only = true); void BreakInvis(); void Escape(); diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index 65d03ad2b..afc923118 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -551,7 +551,7 @@ void bot_command_cast(Client* c, const Seperator* sep) SPDat_Spell_Struct spell = spells[chosenSpellID]; LogTestDebug("Starting bySpellID checks."); //deleteme - if (!bot_iter->HasBotSpellEntry(chosenSpellID)) { + if (!bot_iter->CanUseBotSpell(chosenSpellID)) { LogTestDebug("{} does not have {} [#{}].", bot_iter->GetCleanName(), spell.name, chosenSpellID); //deleteme continue; } From 90fe8a31d751338ae0f2de489c0a8606babd34ae Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:00:12 -0600 Subject: [PATCH 88/97] Add missing alert round for ranged attacks --- zone/bot.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index d685a41f2..ad5aa206b 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -1839,7 +1839,8 @@ bool Bot::BotRangedAttack(Mob* other, bool CanDoubleAttack) { ) { if (!Ammo || ammoItem->GetCharges() < 1) { if (!GetCombatRoundForAlerts()) { - GetOwner()->Message(Chat::Yellow, "I do not have enough any ammo."); + SetCombatRoundForAlerts(); + BotGroupSay(this, "I do not have enough any ammo!"); } } From 4019e7da6500889aa0b6eca1d62f527e9205abe7 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:01:15 -0600 Subject: [PATCH 89/97] More castcheck improvements --- zone/bot.cpp | 25 +++++++++------ zone/botspellsai.cpp | 23 +------------- zone/spells.cpp | 76 ++++++++++++++++++++------------------------ 3 files changed, 50 insertions(+), 74 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index ad5aa206b..163bc8eb6 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -9663,6 +9663,21 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec return false; } + if (tar->GetSpecialAbility(SpecialAbility::MagicImmunity)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to MagicImmunity.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + + if (tar->GetSpecialAbility(SpecialAbility::CastingFromRangeImmunity) && !CombatRange(tar)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to CastingFromRangeImmunity.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + + if (tar->IsImmuneToBotSpell(spell_id, this)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsImmuneToBotSpell.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + if (!AECheck && !IsValidSpellRange(spell_id, tar)) { LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsValidSpellRange.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; @@ -9686,16 +9701,6 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsValidTargetType.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } - - if (tar->GetSpecialAbility(SpecialAbility::CastingFromRangeImmunity) && !CombatRange(tar)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IMMUNE_CASTING_FROM_RANGE.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme - return false; - } - - if (tar->IsImmuneToBotSpell(spell_id, this)) { - LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsImmuneToBotSpell.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme - return false; - } if ( (RequiresStackCheck(spellType) || (!RequiresStackCheck(spellType) && CalcBuffDuration(this, tar, spell_id) != 0)) diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 835e99040..d1eb10ecd 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -207,7 +207,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 spellType, uint16 subTarge break; case BotSpellTypes::Charm: - if (tar->IsCharmed() || !tar->IsNPC() || tar->GetSpecialAbility(SpecialAbility::CharmImmunity)) { + if (HasPet() || tar->IsCharmed() || !tar->IsNPC() || tar->GetSpecialAbility(SpecialAbility::CharmImmunity)) { return false; } @@ -596,27 +596,6 @@ bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain } else dist2 = DistanceSquared(m_Position, tar->GetPosition()); - //if ( - // ( - // ( - // ( - // (spells[AIBot_spells[i].spellid].target_type==ST_GroupTeleport && AIBot_spells[i].type == BotSpellTypes::RegularHeal) || - // spells[AIBot_spells[i].spellid].target_type ==ST_AECaster || - // spells[AIBot_spells[i].spellid].target_type ==ST_Group || - // spells[AIBot_spells[i].spellid].target_type ==ST_AEBard || - // ( - // tar == this && spells[AIBot_spells[i].spellid].target_type != ST_TargetsTarget - // ) - // ) && - // dist2 <= spells[AIBot_spells[i].spellid].aoe_range*spells[AIBot_spells[i].spellid].aoe_range - // ) || - // dist2 <= GetActSpellRange(AIBot_spells[i].spellid, spells[AIBot_spells[i].spellid].range)*GetActSpellRange(AIBot_spells[i].spellid, spells[AIBot_spells[i].spellid].range) - // ) && - // ( - // mana_cost <= GetMana() || - // IsBotNonSpellFighter() - // ) - //) { if (IsValidSpellRange(AIBot_spells[i].spellid, tar) && (mana_cost <= GetMana() || IsBotNonSpellFighter())) { casting_spell_AIindex = i; LogAI("spellid [{}] tar [{}] mana [{}] Name [{}]", AIBot_spells[i].spellid, tar->GetName(), mana_cost, spells[AIBot_spells[i].spellid].name); diff --git a/zone/spells.cpp b/zone/spells.cpp index 62b0e886b..160a414bb 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -7579,8 +7579,9 @@ bool Mob::IsImmuneToBotSpell(uint16 spell_id, Mob* caster) { int effect_index; - if (caster == nullptr) + if (caster == nullptr) { return(false); + } //TODO: this function loops through the effect list for //this spell like 10 times, this could easily be consolidated @@ -7588,14 +7589,19 @@ bool Mob::IsImmuneToBotSpell(uint16 spell_id, Mob* caster) LogSpells("Checking to see if we are immune to spell [{}] cast by [{}]", spell_id, caster->GetName()); - if (!IsValidSpell(spell_id)) + if (!IsValidSpell(spell_id)) { return true; + } - if (IsBeneficialSpell(spell_id) && (caster->GetNPCTypeID())) //then skip the rest, stop NPCs aggroing each other with buff spells. 2013-03-05 + if (IsDispelSpell(spell_id) && GetSpecialAbility(SpecialAbility::DispellImmunity)) { return false; + } - if (IsMesmerizeSpell(spell_id)) - { + if (IsHarmonySpell(spell_id) && GetSpecialAbility(SpecialAbility::PacifyImmunity)) { + return false; + } + + if (IsMesmerizeSpell(spell_id)) { if (GetSpecialAbility(SpecialAbility::MesmerizeImmunity)) { return true; } @@ -7604,90 +7610,76 @@ bool Mob::IsImmuneToBotSpell(uint16 spell_id, Mob* caster) effect_index = GetSpellEffectIndex(spell_id, SE_Mez); assert(effect_index >= 0); // NPCs get to ignore the max level - if ((GetLevel() > spells[spell_id].max_value[effect_index]) && - (!caster->IsNPC() || (caster->IsNPC() && !RuleB(Spells, NPCIgnoreBaseImmunity)))) - { + if ( + (GetLevel() > spells[spell_id].max_value[effect_index]) && + (!caster->IsNPC() || (caster->IsNPC() && !RuleB(Spells, NPCIgnoreBaseImmunity))) + ) { return true; } } // slow and haste spells - if (GetSpecialAbility(SpecialAbility::SlowImmunity) && IsEffectInSpell(spell_id, SE_AttackSpeed)) - { + if (GetSpecialAbility(SpecialAbility::SlowImmunity) && IsEffectInSpell(spell_id, SE_AttackSpeed)) { return true; } // client vs client fear - if (IsEffectInSpell(spell_id, SE_Fear)) - { + if (IsEffectInSpell(spell_id, SE_Fear)) { effect_index = GetSpellEffectIndex(spell_id, SE_Fear); + if (GetSpecialAbility(SpecialAbility::FearImmunity)) { return true; } - else if (IsClient() && caster->IsClient() && (caster->CastToClient()->GetGM() == false)) - { + else if (IsClient() && caster->IsClient() && (caster->CastToClient()->GetGM() == false)) { LogSpells("Clients cannot fear eachother!"); caster->MessageString(Chat::Red, IMMUNE_FEAR); // need to verify message type, not in MQ2Cast for easy look up return true; } - else if (GetLevel() > spells[spell_id].max_value[effect_index] && spells[spell_id].max_value[effect_index] != 0) - { + else if (GetLevel() > spells[spell_id].max_value[effect_index] && spells[spell_id].max_value[effect_index] != 0) { return true; } - else if (CheckAATimer(aaTimerWarcry)) - { + else if (CheckAATimer(aaTimerWarcry)) { return true; } } - if (IsCharmSpell(spell_id)) - { - if (GetSpecialAbility(SpecialAbility::CharmImmunity)) - { + if (IsCharmSpell(spell_id)) { + if (GetSpecialAbility(SpecialAbility::CharmImmunity)) { return true; } - if (this == caster) - { + if (this == caster) { return true; } //let npcs cast whatever charm on anyone - if (!caster->IsNPC()) - { + if (!caster->IsNPC()) { // check level limit of charm spell effect_index = GetSpellEffectIndex(spell_id, SE_Charm); assert(effect_index >= 0); - if (GetLevel() > spells[spell_id].max_value[effect_index] && spells[spell_id].max_value[effect_index] != 0) - { + if (GetLevel() > spells[spell_id].max_value[effect_index] && spells[spell_id].max_value[effect_index] != 0) { return true; } } } - if - ( - IsEffectInSpell(spell_id, SE_Root) || - IsEffectInSpell(spell_id, SE_MovementSpeed) - ) - { + if ( + IsEffectInSpell(spell_id, SE_Root) || + IsEffectInSpell(spell_id, SE_MovementSpeed) + ) { if (GetSpecialAbility(SpecialAbility::SnareImmunity)) { return true; } } - if (IsLifetapSpell(spell_id)) - { - if (this == caster) - { + if (IsLifetapSpell(spell_id)) { + if (this == caster) { return true; } } - if (IsSacrificeSpell(spell_id)) - { - if (this == caster) - { + if (IsSacrificeSpell(spell_id)) { + if (this == caster) { return true; } } From f67df44d99671cdff2fa04e795fa4a8f15ee1067 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:01:26 -0600 Subject: [PATCH 90/97] CanUseBotSpell for ^cast --- zone/botspellsai.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index d1eb10ecd..e61a407d1 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -2649,6 +2649,30 @@ bool Bot::HasBotSpellEntry(uint16 spell_id) { return false; } +bool Bot::CanUseBotSpell(uint16 spell_id) { + if (AIBot_spells.empty()) { + return false; + } + + for (const auto& s : AIBot_spells) { + if (!IsValidSpell(s.spellid)) { + return false; + } + + if (s.spellid != spell_id) { + continue; + } + + if (s.minlevel > GetLevel()) { + return false; + } + + return true; + } + + return false; +} + bool Bot::IsValidSpellRange(uint16 spell_id, Mob* tar) { if (!IsValidSpell(spell_id) || !tar) { return false; From a142298e3413c144c3fb4c105614312d04e39206 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 12 Dec 2024 12:58:25 -0600 Subject: [PATCH 91/97] remove ht/loh from attack ai --- zone/bot.cpp | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 163bc8eb6..9a498bc08 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -5223,7 +5223,6 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { bool taunt_time = taunt_timer.Check(); bool ca_time = classattack_timer.Check(false); bool ma_time = monkattack_timer.Check(false); - bool ka_time = knightattack_timer.Check(false); if (taunt_time) { @@ -5240,36 +5239,10 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { } } - if ((ca_time || ma_time || ka_time) && !IsAttackAllowed(target)) { + if ((ca_time || ma_time) && !IsAttackAllowed(target)) { return; } - if (ka_time) { - - switch (GetClass()) { - case Class::ShadowKnight: { - CastSpell(SPELL_NPC_HARM_TOUCH, target->GetID()); - knightattack_timer.Start(HarmTouchReuseTime * 1000); - - break; - } - case Class::Paladin: { - if (GetHPRatio() < 20) { - CastSpell(SPELL_LAY_ON_HANDS, GetID()); - knightattack_timer.Start(LayOnHandsReuseTime * 1000); - } - else { - knightattack_timer.Start(2000); - } - - break; - } - default: { - break; - } - } - } - if (IsTaunting() && target->IsNPC() && taunt_time) { if (GetTarget() && GetTarget()->GetHateTop() && GetTarget()->GetHateTop() != this) { BotGroupSay( From d9ab4a5f2786625fea969b92414f0d229f28bdbc Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:35:55 -0600 Subject: [PATCH 92/97] remove SetCombatRoundForAlerts that triggered every engagement --- zone/bot.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 9a498bc08..97e097827 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2885,10 +2885,6 @@ void Bot::CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, bo // Calculate melee distances CalcMeleeDistances(tar, p_item, s_item, backstab_weapon, behindMob, melee_distance_min, melee_distance, melee_distance_max, stopMeleeLevel); - if (!GetCombatRoundForAlerts()) { - SetCombatRoundForAlerts(); - } - if (tar_distance <= melee_distance) { atCombatRange = true; } From 67ce8d44bb8430c87385b28d25242d48634b3dec Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:36:09 -0600 Subject: [PATCH 93/97] Add RangedAttackImmunity checks before trying to ranged attack --- zone/bot.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 97e097827..56d8a39be 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2211,7 +2211,7 @@ void Bot::AI_Process() } if (atCombatRange) { - if (RuleB(Bots, AllowRangedPulling) && IsBotRanged() && ranged_timer.Check(false)) { + if (!tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) && RuleB(Bots, AllowRangedPulling) && IsBotRanged() && ranged_timer.Check(false)) { StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) { @@ -2288,7 +2288,7 @@ void Bot::AI_Process() return; } - if (IsBotRanged() && ranged_timer.Check(false)) { + if (!tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) && IsBotRanged() && ranged_timer.Check(false)) { if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) { BotRangedAttack(tar, true); } From 872abdc795434ee276c6798c0be97928dfb43164 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 12 Dec 2024 15:33:11 -0600 Subject: [PATCH 94/97] move bot backstab to mob --- zone/attack.cpp | 3 +- zone/bot.cpp | 28 +++++++++---- zone/bot.h | 3 -- zone/special_attacks.cpp | 85 ++++++++++++++++++++++++++++++---------- 4 files changed, 88 insertions(+), 31 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index 4a2e03bbf..fa6e25e06 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -6461,8 +6461,9 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac } else { int ass = TryAssassinate(defender, hit.skill); - if (ass > 0) + if (ass > 0) { hit.damage_done = ass; + } } } else if (hit.skill == EQ::skills::SkillFrenzy && GetClass() == Class::Berserker && GetLevel() > 50) { diff --git a/zone/bot.cpp b/zone/bot.cpp index 56d8a39be..25253c183 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -5383,18 +5383,32 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { } if (skill_to_use == EQ::skills::SkillFrenzy) { - int AtkRounds = 3; + int AtkRounds = 1; + float HasteMod = (FrenzyReuseTime - 1) / (GetHaste() * 0.01f); + reuse = (FrenzyReuseTime * 1000); DoAnim(anim2HSlashing); - reuse = (FrenzyReuseTime * 1000); - did_attack = true; - while(AtkRounds > 0) { - if (GetTarget() && (AtkRounds == 1 || zone->random.Int(0, 100) < 75)) { - DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillFrenzy, dmg, 0, dmg, reuse, true); - } + // bards can do riposte frenzy for some reason + if (!IsRiposte && GetClass() == Class::Berserker) { + int chance = GetLevel() * 2 + GetSkill(EQ::skills::SkillFrenzy); + if (zone->random.Roll0(450) < chance) + AtkRounds++; + if (zone->random.Roll0(450) < chance) + AtkRounds++; + } + while (AtkRounds > 0) { + if (GetTarget() != this) + + DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillFrenzy, dmg, 0, dmg, HasteMod); AtkRounds--; } + + if (reuse > 0 && IsRiposte) { + reuse = 0; + } + + did_attack = true; } if (skill_to_use == EQ::skills::SkillKick) { diff --git a/zone/bot.h b/zone/bot.h index 287721ae9..457f7eebf 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -249,9 +249,6 @@ public: inline uint16 MaxSkill(EQ::skills::SkillType skillid) { return MaxSkill(skillid, GetClass(), GetLevel()); } int GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target = nullptr) override; void DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool HitChance = false); - void TryBackstab(Mob *other,int ReuseTime = 10) override; - void RogueBackstab(Mob* other, bool min_damage = false, int ReuseTime = 10) override; - void RogueAssassinate(Mob* other) override; void DoClassAttacks(Mob *target, bool IsRiposte=false); void CalcBonuses() override; diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 5e9c6f792..cd8b8ac91 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -709,54 +709,78 @@ int Mob::MonkSpecialAttack(Mob *other, uint8 unchecked_type) } void Mob::TryBackstab(Mob *other, int ReuseTime) { - if(!other) + if (!other) { return; + } bool bIsBehind = false; bool bCanFrontalBS = false; //make sure we have a proper weapon if we are a client. - if(IsClient()) { + if (IsClient()) { const EQ::ItemInstance *wpn = CastToClient()->GetInv().GetItem(EQ::invslot::slotPrimary); + if (!wpn || (wpn->GetItem()->ItemType != EQ::item::ItemType1HPiercing)){ MessageString(Chat::Red, BACKSTAB_WEAPON); return; } } + else if (IsBot()) { + const EQ::ItemInstance* inst = CastToBot()->GetBotItem(EQ::invslot::slotPrimary); + const EQ::ItemData* botpiercer = nullptr; + + if (inst) { + botpiercer = inst->GetItem(); + } + + if (!botpiercer || (botpiercer->ItemType != EQ::item::ItemType1HPiercing)) { + if (!CastToBot()->GetCombatRoundForAlerts()) { + CastToBot()->SetCombatRoundForAlerts(); + CastToBot()->BotGroupSay(this, "I can't backstab with this weapon!"); + } + + return; + } + } //Live AA - Triple Backstab int tripleChance = itembonuses.TripleBackstab + spellbonuses.TripleBackstab + aabonuses.TripleBackstab; - if (BehindMob(other, GetX(), GetY())) + if (BehindMob(other, GetX(), GetY())) { bIsBehind = true; - + } else { //Live AA - Seized Opportunity int FrontalBSChance = itembonuses.FrontalBackstabChance + spellbonuses.FrontalBackstabChance + aabonuses.FrontalBackstabChance; - if (FrontalBSChance && zone->random.Roll(FrontalBSChance)) + if (FrontalBSChance && zone->random.Roll(FrontalBSChance)) { bCanFrontalBS = true; + } } if (bIsBehind || bCanFrontalBS || (IsNPC() && CanFacestab())) { // Player is behind other OR can do Frontal Backstab - if (bCanFrontalBS && IsClient()) // I don't think there is any message ... - CastToClient()->Message(Chat::White,"Your fierce attack is executed with such grace, your target did not see it coming!"); + if (bCanFrontalBS && IsClient()) { // I don't think there is any message ... + CastToClient()->Message(Chat::White, "Your fierce attack is executed with such grace, your target did not see it coming!"); + } RogueBackstab(other,false,ReuseTime); + if (level >= RuleI(Combat, DoubleBackstabLevelRequirement)) { // TODO: 55-59 doesn't appear to match just checking double attack, 60+ does though - if(IsClient() && CastToClient()->CheckDoubleAttack()) - { - if(other->GetHP() > 0) - RogueBackstab(other,false,ReuseTime); + if(IsOfClientBot() && CastToClient()->CheckDoubleAttack()) { + if (other->GetHP() > 0) { + RogueBackstab(other, false, ReuseTime); + } - if (tripleChance && other->GetHP() > 0 && zone->random.Roll(tripleChance)) - RogueBackstab(other,false,ReuseTime); + if (tripleChance && other->GetHP() > 0 && zone->random.Roll(tripleChance)) { + RogueBackstab(other, false, ReuseTime); + } } } - if(IsClient()) + if (IsClient()) { CastToClient()->CheckIncreaseSkill(EQ::skills::SkillBackstab, other, 10); + } } //Live AA - Chaotic Backstab @@ -766,14 +790,20 @@ void Mob::TryBackstab(Mob *other, int ReuseTime) { //we can stab from any angle, we do min damage though. // chaotic backstab can't double etc Seized can, but that's because it's a chance to do normal BS // Live actually added SPA 473 which grants chance to double here when they revamped chaotic/seized + RogueBackstab(other, true, ReuseTime); - if(IsClient()) + + if (IsClient()) { CastToClient()->CheckIncreaseSkill(EQ::skills::SkillBackstab, other, 10); + } + m_specialattacks = eSpecialAttacks::None; int double_bs_front = aabonuses.Double_Backstab_Front + itembonuses.Double_Backstab_Front + spellbonuses.Double_Backstab_Front; - if (double_bs_front && other->GetHP() > 0 && zone->random.Roll(double_bs_front)) + + if (double_bs_front && other->GetHP() > 0 && zone->random.Roll(double_bs_front)) { RogueBackstab(other, false, ReuseTime); + } } else { //We do a single regular attack if we attack from the front without chaotic stab Attack(other, EQ::invslot::slotPrimary); @@ -790,10 +820,25 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) // make sure we can hit (bane, magical, etc) if (IsClient()) { - const EQ::ItemInstance *wpn = CastToClient()->GetInv().GetItem(EQ::invslot::slotPrimary); - if (!GetWeaponDamage(other, wpn)) + const EQ::ItemInstance* wpn = CastToClient()->GetInv().GetItem(EQ::invslot::slotPrimary); + + if (!GetWeaponDamage(other, wpn)) { return; - } else if (!GetWeaponDamage(other, (const EQ::ItemData*)nullptr)){ + } + } + else if (IsBot()) { + EQ::ItemInstance* botweaponInst = CastToBot()->GetBotItem(EQ::invslot::slotPrimary); + + if (botweaponInst) { + if (!GetWeaponDamage(other, botweaponInst)) { + return; + } + } + else if (!GetWeaponDamage(other, (const EQ::ItemData*)nullptr)) { + return; + } + } + else if (!GetWeaponDamage(other, (const EQ::ItemData*)nullptr)) { return; } @@ -2454,7 +2499,7 @@ int Mob::TryAssassinate(Mob *defender, EQ::skills::SkillType skillInUse) int chance = GetDEX(); if (skillInUse == EQ::skills::SkillBackstab) { chance = 100 * chance / (chance + 3500); - if (IsClient() || IsBot()) { + if (IsOfClientBot()) { chance += GetHeroicDEX(); } chance *= 10; From 783781fe20c745daaeaa3d54e618f8617e14e084 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:40:22 -0600 Subject: [PATCH 95/97] fix MinStatusToBypassCreateLimit --- zone/client_bot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/client_bot.cpp b/zone/client_bot.cpp index bfe4cdb58..9d796cef1 100644 --- a/zone/client_bot.cpp +++ b/zone/client_bot.cpp @@ -20,7 +20,7 @@ uint32 Client::GetBotCreationLimit(uint8 class_id) uint32 bot_creation_limit = RuleI(Bots, CreationLimit); if (Admin() >= RuleI(Bots, MinStatusToBypassCreateLimit)) { - return RuleI(Bots, MinStatusToBypassCreateLimit); + return RuleI(Bots, StatusCreateLimit); } const auto bucket_name = fmt::format( From 372fd044defa5a95c84ba277038bb6aed72cdea6 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 12 Dec 2024 22:06:54 -0600 Subject: [PATCH 96/97] more backstab to mob cleanup --- zone/bot.cpp | 100 ++++----------------------------------------------- 1 file changed, 6 insertions(+), 94 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 25253c183..9949de888 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -5121,96 +5121,6 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 max TrySkillProc(who, skill, (ReuseTime * 1000), true); } -void Bot::TryBackstab(Mob *other, int ReuseTime) { - if (!other) - return; - - bool bIsBehind = false; - bool bCanFrontalBS = false; - const EQ::ItemInstance* inst = GetBotItem(EQ::invslot::slotPrimary); - const EQ::ItemData* botpiercer = nullptr; - if (inst) - botpiercer = inst->GetItem(); - - if (!botpiercer || (botpiercer->ItemType != EQ::item::ItemType1HPiercing)) { - if (!GetCombatRoundForAlerts()) { - SetCombatRoundForAlerts(); - BotGroupSay(this, "I can't backstab with this weapon!"); - } - - return; - } - - int tripleChance = (itembonuses.TripleBackstab + spellbonuses.TripleBackstab + aabonuses.TripleBackstab); - if (BehindMob(other, GetX(), GetY())) - bIsBehind = true; - else { - int FrontalBSChance = (itembonuses.FrontalBackstabChance + spellbonuses.FrontalBackstabChance + aabonuses.FrontalBackstabChance); - if (FrontalBSChance && (FrontalBSChance > zone->random.Int(0, 100))) - bCanFrontalBS = true; - } - - if (bIsBehind || bCanFrontalBS) { - int chance = (10 + (GetDEX() / 10) + (itembonuses.HeroicDEX / 10)); - if (level >= 60 && other->GetLevel() <= 45 && !other->CastToNPC()->IsEngaged() && other->GetHP()<= 32000 && other->IsNPC() && zone->random.Real(0, 99) < chance) { - entity_list.MessageCloseString(this, false, 200, Chat::MeleeCrit, ASSASSINATES, GetName()); - RogueAssassinate(other); - } else { - RogueBackstab(other); - if (level > 54) { - float DoubleAttackProbability = ((GetSkill(EQ::skills::SkillDoubleAttack) + GetLevel()) / 500.0f); - if (zone->random.Real(0, 1) < DoubleAttackProbability) { - if (other->GetHP() > 0) - RogueBackstab(other,false,ReuseTime); - - if (tripleChance && other->GetHP() > 0 && tripleChance > zone->random.Int(0, 100)) - RogueBackstab(other,false,ReuseTime); - } - } - } - } else if (aabonuses.FrontalBackstabMinDmg || itembonuses.FrontalBackstabMinDmg || spellbonuses.FrontalBackstabMinDmg) { - m_specialattacks = eSpecialAttacks::ChaoticStab; - RogueBackstab(other, true); - m_specialattacks = eSpecialAttacks::None; - } - else - Attack(other, EQ::invslot::slotPrimary); -} - -void Bot::RogueBackstab(Mob *other, bool min_damage, int ReuseTime) -{ - if (!other) - return; - - EQ::ItemInstance *botweaponInst = GetBotItem(EQ::invslot::slotPrimary); - if (botweaponInst) { - if (!GetWeaponDamage(other, botweaponInst)) - return; - } else if (!GetWeaponDamage(other, (const EQ::ItemData *)nullptr)) { - return; - } - - int64 hate = 0; - - int base_damage = GetBaseSkillDamage(EQ::skills::SkillBackstab, other); - hate = base_damage; - - DoSpecialAttackDamage(other, EQ::skills::SkillBackstab, base_damage, 0, hate, ReuseTime); - DoAnim(anim1HPiercing); -} - -void Bot::RogueAssassinate(Mob* other) { - EQ::ItemInstance* botweaponInst = GetBotItem(EQ::invslot::slotPrimary); - if (botweaponInst) { - if (GetWeaponDamage(other, botweaponInst)) - other->Damage(this, 32000, SPELL_UNKNOWN, EQ::skills::SkillBackstab); - else - other->Damage(this, -5, SPELL_UNKNOWN, EQ::skills::SkillBackstab); - } - - DoAnim(anim1HPiercing); -} - void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if (!target || GetAppearance() == eaDead || spellend_timer.Enabled() || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0 || !IsAttackAllowed(target)) { return; @@ -5399,7 +5309,6 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { while (AtkRounds > 0) { if (GetTarget() != this) - DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillFrenzy, dmg, 0, dmg, HasteMod); AtkRounds--; } @@ -5479,11 +5388,14 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if (skill_to_use == EQ::skills::SkillBackstab) { reuse = (BackstabReuseTime * 1000); did_attack = true; - if (IsRiposte) - reuse = 0; - TryBackstab(target,reuse); + if (IsRiposte) { + reuse = 0; + } + + Mob::TryBackstab(target, reuse); } + classattack_timer.Start(reuse / HasteModifier); } From a28a123209df01521c7454fdbcbd3ddc1db6f77c Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Thu, 12 Dec 2024 22:07:54 -0600 Subject: [PATCH 97/97] add bot checks to tryheadshot / tryassassinate --- zone/attack.cpp | 1 + zone/special_attacks.cpp | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index fa6e25e06..b29eb6266 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -6461,6 +6461,7 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac } else { int ass = TryAssassinate(defender, hit.skill); + if (ass > 0) { hit.damage_done = ass; } diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index cd8b8ac91..e7c5350be 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -2453,7 +2453,7 @@ int Mob::TryHeadShot(Mob *defender, EQ::skills::SkillType skillInUse) // Only works on YOUR target. if ( defender && - !defender->IsClient() && + !defender->IsOfClientBot() && skillInUse == EQ::skills::SkillArchery && GetTarget() == defender && (defender->GetBodyType() == BodyType::Humanoid || !RuleB(Combat, HeadshotOnlyHumanoids)) && @@ -2466,7 +2466,7 @@ int Mob::TryHeadShot(Mob *defender, EQ::skills::SkillType skillInUse) if (HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)) { int chance = GetDEX(); chance = 100 * chance / (chance + 3500); - if (IsClient() || IsBot()) { + if (IsOfClientBot()) { chance += GetHeroicDEX() / 25; } chance *= 10; @@ -2490,7 +2490,7 @@ int Mob::TryAssassinate(Mob *defender, EQ::skills::SkillType skillInUse) { if ( defender && - !defender->IsClient() && + !defender->IsOfClientBot() && GetLevel() >= RuleI(Combat, AssassinateLevelRequirement) && (skillInUse == EQ::skills::SkillBackstab || skillInUse == EQ::skills::SkillThrowing) && (defender->GetBodyType() == BodyType::Humanoid || !RuleB(Combat, AssassinateOnlyHumanoids)) &&