diff --git a/common/item_data.h b/common/item_data.h index 45ca8e9e0..58bc4c889 100644 --- a/common/item_data.h +++ b/common/item_data.h @@ -413,7 +413,7 @@ namespace EQEmu int32 SkillModMax; // Max skill point modification uint32 SkillModType; // Type of skill for SkillModValue to apply to uint32 BaneDmgRace; // Bane Damage Race - int8 BaneDmgAmt; // Bane Damage Body Amount + int32 BaneDmgAmt; // Bane Damage Body Amount uint32 BaneDmgBody; // Bane Damage Body bool Magic; // True=Magic Item, False=not int32 CastTime_; diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index d8a115a98..b50df595c 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -2303,7 +2303,10 @@ namespace Titanium ob << '|' << itoa(item->SkillModType); ob << '|' << itoa(item->BaneDmgRace); - ob << '|' << itoa(item->BaneDmgAmt); + if (item->BaneDmgAmt > 255) + ob << '|' << "255"; + else + ob << '|' << itoa(item->BaneDmgAmt); ob << '|' << itoa(item->BaneDmgBody); ob << '|' << itoa(item->Magic); diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 46e3a7512..97585670c 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -998,7 +998,7 @@ void SharedDatabase::LoadItems(void *data, uint32 size, int32 items, uint32 max_ item.SkillModMax = (int32)atoul(row[ItemField::skillmodmax]); item.SkillModType = (uint32)atoul(row[ItemField::skillmodtype]); item.BaneDmgRace = (uint32)atoul(row[ItemField::banedmgrace]); - item.BaneDmgAmt = (int8)atoi(row[ItemField::banedmgamt]); + item.BaneDmgAmt = (int32)atoul(row[ItemField::banedmgamt]); item.BaneDmgBody = (uint32)atoul(row[ItemField::banedmgbody]); item.Magic = (atoi(row[ItemField::magic]) == 0) ? false : true; item.CastTime_ = (int32)atoul(row[ItemField::casttime_]); diff --git a/common/version.h b/common/version.h index f7a161dc1..c9bb6d361 100644 --- a/common/version.h +++ b/common/version.h @@ -34,7 +34,7 @@ #define CURRENT_BINARY_DATABASE_VERSION 9141 #ifdef BOTS - #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9024 + #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9025 #else #define CURRENT_BINARY_BOTS_DATABASE_VERSION 0 // must be 0 #endif diff --git a/utils/sql/git/bots/bots_db_update_manifest.txt b/utils/sql/git/bots/bots_db_update_manifest.txt index 982200f2a..405cf3f5c 100644 --- a/utils/sql/git/bots/bots_db_update_manifest.txt +++ b/utils/sql/git/bots/bots_db_update_manifest.txt @@ -23,6 +23,7 @@ 9022|2019_02_07_bots_stance_type_update.sql|SELECT * FROM `bot_spell_casting_chances` WHERE `spell_type_index` = '255' AND `class_id` = '255' AND `stance_index` = '0'|not_empty| 9023|2019_06_22_bots_owner_option_stats_update.sql|SHOW COLUMNS FROM `bot_owner_options` LIKE 'stats_update'|empty| 9024|2019_06_27_bots_pet_get_lost.sql|SELECT `bot_command` FROM `bot_command_settings` WHERE `bot_command` LIKE 'petgetlost'|empty| +9025|2019_08_26_bots_owner_option_spawn_message.sql|SHOW COLUMNS FROM `bot_owner_options` LIKE 'spawn_message_enabled'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/bots/required/2019_08_26_bots_owner_option_spawn_message.sql b/utils/sql/git/bots/required/2019_08_26_bots_owner_option_spawn_message.sql new file mode 100644 index 000000000..04910cd10 --- /dev/null +++ b/utils/sql/git/bots/required/2019_08_26_bots_owner_option_spawn_message.sql @@ -0,0 +1,2 @@ +ALTER TABLE `bot_owner_options` ADD COLUMN `spawn_message_enabled` SMALLINT(3) UNSIGNED NULL DEFAULT '1' AFTER `stats_update`; +ALTER TABLE `bot_owner_options` ADD COLUMN `spawn_message_type` SMALLINT(3) UNSIGNED NULL DEFAULT '1' AFTER `spawn_message_enabled`; diff --git a/zone/bot.cpp b/zone/bot.cpp index 9cce41c2f..971c1d1b6 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -7465,9 +7465,14 @@ void Bot::DoEnduranceUpkeep() { void Bot::Camp(bool databaseSave) { Sit(); - if(IsGrouped()) + //auto group = GetGroup(); + if(GetGroup()) RemoveBotFromGroup(this, GetGroup()); + // RemoveBotFromGroup() code is too complicated for this to work as-is (still needs to be addressed to prevent memory leaks) + //if (group->GroupCount() < 2) + // group->DisbandGroup(); + LeaveHealRotationMemberPool(); if(databaseSave) diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 749553de1..31270cc0f 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -95,6 +95,7 @@ namespace enum { EffectIDFirst = 1, EffectIDLast = 12 }; +#define VALIDATECLASSID(x) ((x >= WARRIOR && x <= BERSERKER) ? (x) : (0)) #define CLASSIDTOINDEX(x) ((x >= WARRIOR && x <= BERSERKER) ? (x - 1) : (0)) #define EFFECTIDTOINDEX(x) ((x >= EffectIDFirst && x <= EffectIDLast) ? (x - 1) : (0)) #define AILMENTIDTOINDEX(x) ((x >= BCEnum::AT_Blindness && x <= BCEnum::AT_Corruption) ? (x - 1) : (0)) @@ -3443,16 +3444,17 @@ void bot_command_owner_option(Client *c, const Seperator *sep) { if (helper_is_help_or_usage(sep->arg[1])) { c->Message(m_usage, "usage: %s [deathmarquee | statsupdate] (argument: enable | disable | null (toggles))", sep->arg[0]); + c->Message(m_usage, "usage: %s [spawnmessage] [argument: say | tell | silent | class | default]", sep->arg[0]); return; } std::string owner_option = sep->arg[1]; - std::string flag = sep->arg[2]; + std::string argument = sep->arg[2]; if (!owner_option.compare("deathmarquee")) { - if (!flag.compare("enable")) + if (!argument.compare("enable")) c->SetBotOptionDeathMarquee(true); - else if (!flag.compare("disable")) + else if (!argument.compare("disable")) c->SetBotOptionDeathMarquee(false); else c->SetBotOptionDeathMarquee(!c->GetBotOptionDeathMarquee()); @@ -3461,9 +3463,9 @@ void bot_command_owner_option(Client *c, const Seperator *sep) c->Message(m_action, "Bot 'death marquee' is now %s.", (c->GetBotOptionDeathMarquee() == true ? "enabled" : "disabled")); } else if (!owner_option.compare("statsupdate")) { - if (!flag.compare("enable")) + if (!argument.compare("enable")) c->SetBotOptionStatsUpdate(true); - else if (!flag.compare("disable")) + else if (!argument.compare("disable")) c->SetBotOptionStatsUpdate(false); else c->SetBotOptionStatsUpdate(!c->GetBotOptionStatsUpdate()); @@ -3471,6 +3473,35 @@ void bot_command_owner_option(Client *c, const Seperator *sep) database.botdb.SaveOwnerOptionStatsUpdate(c->CharacterID(), c->GetBotOptionStatsUpdate()); c->Message(m_action, "Bot 'stats update' is now %s.", (c->GetBotOptionStatsUpdate() == true ? "enabled" : "disabled")); } + else if (!owner_option.compare("spawnmessage")) { + if (!argument.compare("say")) { + c->SetBotOptionSpawnMessageSay(); + } + else if (!argument.compare("tell")) { + c->SetBotOptionSpawnMessageTell(); + } + else if (!argument.compare("silent")) { + c->SetBotOptionSpawnMessageSilent(); + } + else if (!argument.compare("class")) { + c->SetBotOptionSpawnMessageClassSpecific(true); + } + else if (!argument.compare("default")) { + c->SetBotOptionSpawnMessageClassSpecific(false); + } + else { + c->Message(m_fail, "Owner option '%s' argument '%s' is not recognized.", owner_option.c_str(), argument.c_str()); + return; + } + + database.botdb.SaveOwnerOptionSpawnMessage( + c->CharacterID(), + c->GetBotOptionSpawnMessageSay(), + c->GetBotOptionSpawnMessageTell(), + c->GetBotOptionSpawnMessageClassSpecific() + ); + c->Message(m_action, "Bot 'spawn message' is now %s.", argument.c_str()); + } else { c->Message(m_fail, "Owner option '%s' is not recognized.", owner_option.c_str()); } @@ -4284,17 +4315,17 @@ void bot_subcommand_bot_clone(Client *c, const Seperator *sep) void bot_subcommand_bot_create(Client *c, const Seperator *sep) { const std::string class_substrs[17] = { "", - "%u(WAR)", "%u(CLR)", "%u(PAL)", "%u(RNG)", - "%u(SHD)", "%u(DRU)", "%u(MNK)", "%u(BRD)", - "%u(ROG)", "%u(SHM)", "%u(NEC)", "%u(WIZ)", - "%u(MAG)", "%u(ENC)", "%u(BST)", "%u(BER)" + "%u (WAR)", "%u (CLR)", "%u (PAL)", "%u (RNG)", + "%u (SHD)", "%u (DRU)", "%u (MNK)", "%u (BRD)", + "%u (ROG)", "%u (SHM)", "%u (NEC)", "%u (WIZ)", + "%u (MAG)", "%u (ENC)", "%u (BST)", "%u (BER)" }; const std::string race_substrs[17] = { "", - "%u(HUM)", "%u(BAR)", "%u(ERU)", "%u(ELF)", - "%u(HIE)", "%u(DEF)", "%u(HEF)", "%u(DWF)", - "%u(TRL)", "%u(OGR)", "%u(HFL)", "%u(GNM)", - "%u(IKS)", "%u(VAH)", "%u(FRG)", "%u(DRK)" + "%u (HUM)", "%u (BAR)", "%u (ERU)", "%u (ELF)", + "%u (HIE)", "%u (DEF)", "%u (HEF)", "%u (DWF)", + "%u (TRL)", "%u (OGR)", "%u (HFL)", "%u (GNM)", + "%u (IKS)", "%u (VAH)", "%u (FRG)", "%u (DRK)" }; const uint16 race_values[17] = { 0, @@ -4305,54 +4336,70 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep) }; const std::string gender_substrs[2] = { - "%u(M)", "%u(F)", + "%u (M)", "%u (F)", }; - std::string msg_class = "class:"; - std::string msg_race = "race:"; - std::string msg_gender = "gender:"; - std::string msg_separator; - - msg_separator = " "; - for (int i = 0; i <= 15; ++i) { - if (((1 << i) & RuleI(Bots, AllowedClasses)) == 0) - continue; - - msg_class.append(const_cast(msg_separator)); - msg_class.append(StringFormat(class_substrs[i + 1].c_str(), (i + 1))); - - msg_separator = ", "; - } - - msg_separator = " "; - for (int i = 0; i <= 15; ++i) { - if (((1 << i) & RuleI(Bots, AllowedRaces)) == 0) - continue; - - msg_race.append(const_cast(msg_separator)); - msg_race.append(StringFormat(race_substrs[i + 1].c_str(), race_values[i + 1])); - - msg_separator = ", "; - } - - msg_separator = " "; - for (int i = 0; i <= 1; ++i) { - if (((1 << i) & RuleI(Bots, AllowedGenders)) == 0) - continue; - - msg_gender.append(const_cast(msg_separator)); - msg_gender.append(StringFormat(gender_substrs[i].c_str(), i)); - - msg_separator = ", "; - } - if (helper_command_alias_fail(c, "bot_subcommand_bot_create", sep->arg[0], "botcreate")) return; if (helper_is_help_or_usage(sep->arg[1])) { c->Message(m_usage, "usage: %s [bot_name] [bot_class] [bot_race] [bot_gender]", sep->arg[0]); - c->Message(m_note, msg_class.c_str()); - c->Message(m_note, msg_race.c_str()); - c->Message(m_note, msg_gender.c_str()); + std::string window_title = "Bot Create Options"; + std::string window_text; + std::string message_separator; + int object_count = 0; + const int object_max = 5; + + window_text.append("Classes:"); + message_separator = " "; + object_count = 1; + for (int i = 0; i <= 15; ++i) { + if (((1 << i) & RuleI(Bots, AllowedClasses)) == 0) + continue; + + window_text.append(const_cast(message_separator)); + if (object_count >= object_max) { + window_text.append("
"); + object_count = 0; + } + window_text.append(StringFormat(class_substrs[i + 1].c_str(), (i + 1))); + ++object_count; + message_separator = ", "; + } + window_text.append("

"); + + window_text.append("Races:"); + message_separator = " "; + object_count = 1; + for (int i = 0; i <= 15; ++i) { + if (((1 << i) & RuleI(Bots, AllowedRaces)) == 0) + continue; + + window_text.append(const_cast(message_separator)); + if (object_count >= object_max) { + window_text.append("
"); + object_count = 0; + } + window_text.append(StringFormat(race_substrs[i + 1].c_str(), race_values[i + 1])); + ++object_count; + message_separator = ", "; + } + window_text.append("

"); + + window_text.append("Genders:"); + message_separator = " "; + for (int i = 0; i <= 1; ++i) { + if (((1 << i) & RuleI(Bots, AllowedGenders)) == 0) + continue; + + window_text.append(const_cast(message_separator)); + window_text.append(StringFormat(gender_substrs[i].c_str(), i)); + + message_separator = ", "; + } + + + c->SendPopupToClient(window_title.c_str(), window_text.c_str()); + return; } @@ -4363,19 +4410,19 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep) std::string bot_name = sep->arg[1]; if (sep->arg[2][0] == '\0' || !sep->IsNumber(2)) { - c->Message(m_fail, msg_class.c_str()); + c->Message(m_fail, "Invalid Class!"); return; } uint8 bot_class = atoi(sep->arg[2]); if (sep->arg[3][0] == '\0' || !sep->IsNumber(3)) { - c->Message(m_fail, msg_race.c_str()); + c->Message(m_fail, "Invalid Race!"); return; } uint16 bot_race = atoi(sep->arg[3]); if (sep->arg[4][0] == '\0') { - c->Message(m_fail, msg_gender.c_str()); + c->Message(m_fail, "Invalid Gender!"); return; } uint8 bot_gender = atoi(sep->arg[4]); @@ -5153,8 +5200,9 @@ void bot_subcommand_bot_spawn(Client *c, const Seperator *sep) return; } - static const char* bot_spawn_message[16] = { - "A solid weapon is my ally!", // WARRIOR / 'generic' + static const char* bot_spawn_message[17] = { + "I am ready to fight!", // DEFAULT + "A solid weapon is my ally!", // WARRIOR "The pious shall never die!", // CLERIC "I am the symbol of Light!", // PALADIN "There are enemies near!", // RANGER @@ -5172,7 +5220,14 @@ void bot_subcommand_bot_spawn(Client *c, const Seperator *sep) "My bloodthirst shall not be quenched!" // BERSERKER }; - Bot::BotGroupSay(my_bot, "%s", bot_spawn_message[CLASSIDTOINDEX(my_bot->GetClass())]); + uint8 message_index = 0; + if (c->GetBotOptionSpawnMessageClassSpecific()) + message_index = VALIDATECLASSID(my_bot->GetClass()); + + if (c->GetBotOptionSpawnMessageSay()) + Bot::BotGroupSay(my_bot, "%s", bot_spawn_message[message_index]); + else if (c->GetBotOptionSpawnMessageTell()) + c->Message(Chat::Tell, "%s tells you, \"%s\"", my_bot->GetCleanName(), bot_spawn_message[message_index]); } void bot_subcommand_bot_stance(Client *c, const Seperator *sep) diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 2aaf677bb..29d0f37e9 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -2157,7 +2157,7 @@ bool BotDatabase::LoadOwnerOptions(Client *owner) return false; query = StringFormat( - "SELECT `death_marquee`, `stats_update` FROM `bot_owner_options`" + "SELECT `death_marquee`, `stats_update`, `spawn_message_enabled`, `spawn_message_type` FROM `bot_owner_options`" " WHERE `owner_id` = '%u'", owner->CharacterID() ); @@ -2174,6 +2174,18 @@ bool BotDatabase::LoadOwnerOptions(Client *owner) auto row = results.begin(); owner->SetBotOptionDeathMarquee((atoi(row[0]) != 0)); owner->SetBotOptionStatsUpdate((atoi(row[1]) != 0)); + switch (atoi(row[2])) { + case 2: + owner->SetBotOptionSpawnMessageSay(); + break; + case 1: + owner->SetBotOptionSpawnMessageTell(); + break; + default: + owner->SetBotOptionSpawnMessageSilent(); + break; + } + owner->SetBotOptionSpawnMessageClassSpecific((atoi(row[3]) != 0)); return true; } @@ -2216,6 +2228,38 @@ bool BotDatabase::SaveOwnerOptionStatsUpdate(const uint32 owner_id, const bool f return true; } +bool BotDatabase::SaveOwnerOptionSpawnMessage(const uint32 owner_id, const bool say, const bool tell, const bool class_specific) +{ + if (!owner_id) + return false; + + uint8 enabled_value = 0; + if (say) + enabled_value = 2; + else if (tell) + enabled_value = 1; + + uint8 type_value = 0; + if (class_specific) + type_value = 1; + + query = StringFormat( + "UPDATE `bot_owner_options`" + " SET" + " `spawn_message_enabled` = '%u'," + " `spawn_message_type` = '%u'" + " WHERE `owner_id` = '%u'", + enabled_value, + type_value, + owner_id + ); + auto results = database.QueryDatabase(query); + if (!results.Success()) + return false; + + return true; +} + /* Bot bot-group functions */ bool BotDatabase::QueryBotGroupExistence(const std::string& group_name, bool& extant_flag) diff --git a/zone/bot_database.h b/zone/bot_database.h index a94d2a902..0c18eade5 100644 --- a/zone/bot_database.h +++ b/zone/bot_database.h @@ -141,6 +141,7 @@ public: bool LoadOwnerOptions(Client *owner); bool SaveOwnerOptionDeathMarquee(const uint32 owner_id, const bool flag); bool SaveOwnerOptionStatsUpdate(const uint32 owner_id, const bool flag); + bool SaveOwnerOptionSpawnMessage(const uint32 owner_id, const bool say, const bool tell, const bool class_specific); /* Bot bot-group functions */ bool QueryBotGroupExistence(const std::string& botgroup_name, bool& extant_flag); diff --git a/zone/client.h b/zone/client.h index bb80e7ffc..88bee0b03 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1629,21 +1629,34 @@ private: struct BotOwnerOptions { bool death_marquee; bool stats_update; + bool spawn_message_say; + bool spawn_message_tell; + bool spawn_message_class_specific; }; BotOwnerOptions bot_owner_options; const BotOwnerOptions DefaultBotOwnerOptions = { false, // death_marquee - false // stats_update + false, // stats_update + false, // spawn_message_say + true, // spawn_message_tell + true // spawn_message_class_specific }; public: void SetBotOptionDeathMarquee(bool flag) { bot_owner_options.death_marquee = flag; } void SetBotOptionStatsUpdate(bool flag) { bot_owner_options.stats_update = flag; } + void SetBotOptionSpawnMessageSay() { bot_owner_options.spawn_message_say = true; bot_owner_options.spawn_message_tell = false; } + void SetBotOptionSpawnMessageTell() { bot_owner_options.spawn_message_say = false; bot_owner_options.spawn_message_tell = true; } + void SetBotOptionSpawnMessageSilent() { bot_owner_options.spawn_message_say = false; bot_owner_options.spawn_message_tell = false; } + void SetBotOptionSpawnMessageClassSpecific(bool flag) { bot_owner_options.spawn_message_class_specific = flag; } bool GetBotOptionDeathMarquee() const { return bot_owner_options.death_marquee; } bool GetBotOptionStatsUpdate() const { return bot_owner_options.stats_update; } + bool GetBotOptionSpawnMessageSay() const { return bot_owner_options.spawn_message_say; } + bool GetBotOptionSpawnMessageTell() const { return bot_owner_options.spawn_message_tell; } + bool GetBotOptionSpawnMessageClassSpecific() const { return bot_owner_options.spawn_message_class_specific; } private: #endif diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 38e7aaff4..ccb4ee96a 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -3952,6 +3952,9 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app) #ifdef BOTS // This block is necessary to clean up any bot objects owned by a Client Bot::BotOrderCampAll(this); + auto group = GetGroup(); + if (group && group->GroupCount() < 2) + group->DisbandGroup(); #endif if (IsLFP()) worldserver.StopLFP(CharacterID()); diff --git a/zone/mob.cpp b/zone/mob.cpp index 46a5c2fc6..cf26e4177 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -1435,6 +1435,25 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal } } +#ifdef BOTS + if (GetOwner() && GetOwner()->IsBot() && GetOwner()->CastToBot()->GetBotOwner() && GetOwner()->CastToBot()->GetBotOwner()->IsClient()) { + auto bot_owner = GetOwner()->CastToBot()->GetBotOwner()->CastToClient(); + if (bot_owner) { + bot_owner->QueuePacket(&hp_packet, false); + group = entity_list.GetGroupByClient(bot_owner); + + if (group) { + group->SendHPPacketsFrom(this); + } + + Raid *raid = entity_list.GetRaidByClient(bot_owner); + if (raid) { + raid->SendHPManaEndPacketsFrom(this); + } + } + } +#endif + if (GetPet() && GetPet()->IsClient()) { GetPet()->CastToClient()->QueuePacket(&hp_packet, false); }