From d3a414a0488f9c336a1d7ec1bd3b7ab80e9ef791 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 17 Sep 2023 11:12:43 -0700 Subject: [PATCH] [Fixes] AA System Fixes (#3572) * -Always load AAs beyond our current expansion (Will need this for refunding invalid AAs). -AAs beyond our current expansion will no longer be buyable or sendable to clients. * #reload aa will now reload character aa data. * Base Implementation of auto grant AA * -Add DB manifest entry -Made has already purchased fn a bit better -Added auto grant to db entry * -Added grantaa command. -Reworked grantaa to not spam the client with packets, it still does spam messages because the feedback is important. * Port suggested changes for Finish AA purchase. --------- Co-authored-by: KimLS --- common/database/database_update_manifest.cpp | 11 + common/ruletypes.h | 1 + zone/aa.cpp | 203 ++++++++++++++++--- zone/aa_ability.h | 1 + zone/client.h | 5 +- zone/client_packet.cpp | 2 + zone/command.cpp | 2 + zone/command.h | 1 + zone/entity.cpp | 4 + zone/exp.cpp | 2 + zone/gm_commands/grantaa.cpp | 20 ++ 11 files changed, 218 insertions(+), 34 deletions(-) create mode 100644 zone/gm_commands/grantaa.cpp diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index e5bf32183..225534868 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -4930,6 +4930,17 @@ CREATE TABLE `character_stats_record` ( `updated_at` datetime DEFAULT NULL, PRIMARY KEY (`character_id`) ); +)" + }, + ManifestEntry{ + .version = 9236, + .description = "2023_08_24_aa_ability_auto_grant.sql", + .check = "SHOW COLUMNS FROM `aa_ability` LIKE 'auto_grant_enabled';", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `aa_ability` ADD COLUMN `auto_grant_enabled` TINYINT(4) NOT NULL DEFAULT '0' AFTER `reset_on_death`; +UPDATE `aa_ability` SET `auto_grant_enabled` = 1 WHERE `grant_only` = 0 AND `charges` = 0 AND `category` = -1; )" }, diff --git a/common/ruletypes.h b/common/ruletypes.h index 2b2f0a477..b09681ba5 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -820,6 +820,7 @@ RULE_CATEGORY_END() RULE_CATEGORY(Expansion) RULE_INT(Expansion, CurrentExpansion, -1, "The current expansion enabled for the server [-1 = ALL, 0 = Classic, 1 = Kunark etc.]") RULE_BOOL(Expansion, UseCurrentExpansionAAOnly, false, "When true will only load AA ranks that match CurrentExpansion rule") +RULE_INT(Expansion, AutoGrantAAExpansion, -1, "Expansion to auto grant AAs up to, [-1 = Disabled, 0 = Classic, 1 = Kunark etc.]") RULE_CATEGORY_END() RULE_CATEGORY(Instances) diff --git a/zone/aa.cpp b/zone/aa.cpp index 4b706459c..517f39f39 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -1111,7 +1111,7 @@ void Client::PurchaseAlternateAdvancementRank(int rank_id) { return; } - FinishAlternateAdvancementPurchase(rank, false); + FinishAlternateAdvancementPurchase(rank, false, true); } bool Client::GrantAlternateAdvancementAbility(int aa_id, int points, bool ignore_cost) { @@ -1134,13 +1134,13 @@ bool Client::GrantAlternateAdvancementAbility(int aa_id, int points, bool ignore } ret = true; - FinishAlternateAdvancementPurchase(rank, ignore_cost); + FinishAlternateAdvancementPurchase(rank, ignore_cost, true); } return ret; } -void Client::FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost) { +void Client::FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost, bool send_message_and_save) { auto rank_id = rank->base_ability->first_rank_id; if (rank->base_ability->charges) { @@ -1156,7 +1156,7 @@ void Client::FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost SetAA(rank_id, rank->current_value, 0); //if not max then send next aa - if (rank->next) { + if (rank->next && send_message_and_save) { SendAlternateAdvancementRank(rank->base_ability->id, rank->next->current_value); } } @@ -1164,10 +1164,12 @@ void Client::FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost auto cost = !ignore_cost ? rank->cost : 0; m_pp.aapoints -= static_cast(cost); - SaveAA(); - SendAlternateAdvancementPoints(); - SendAlternateAdvancementStats(); + if (send_message_and_save) { + SaveAA(); + SendAlternateAdvancementPoints(); + SendAlternateAdvancementStats(); + } if (player_event_logs.IsEventEnabled(PlayerEvent::AA_PURCHASE)) { auto e = PlayerEvent::AAPurchasedEvent{ @@ -1181,14 +1183,16 @@ void Client::FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost } if (rank->prev) { - MessageString( - Chat::Yellow, - AA_IMPROVE, - std::to_string(rank->title_sid).c_str(), - std::to_string(rank->prev->current_value).c_str(), - std::to_string(cost).c_str(), - cost == 1 ? std::to_string(AA_POINT).c_str() : std::to_string(AA_POINTS).c_str() - ); + if (send_message_and_save) { + MessageString( + Chat::Yellow, + AA_IMPROVE, + std::to_string(rank->title_sid).c_str(), + std::to_string(rank->prev->current_value).c_str(), + std::to_string(cost).c_str(), + cost == 1 ? std::to_string(AA_POINT).c_str() : std::to_string(AA_POINTS).c_str() + ); + } /* QS: Player_Log_AA_Purchases */ if (RuleB(QueryServ, PlayerLogAAPurchases)) { @@ -1203,13 +1207,15 @@ void Client::FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost QServ->PlayerLogEvent(Player_Log_AA_Purchases, CharacterID(), event_desc); } } else { - MessageString( - Chat::Yellow, - AA_GAIN_ABILITY, - std::to_string(rank->title_sid).c_str(), - std::to_string(cost).c_str(), - cost == 1 ? std::to_string(AA_POINT).c_str() : std::to_string(AA_POINTS).c_str() - ); + if (send_message_and_save) { + MessageString( + Chat::Yellow, + AA_GAIN_ABILITY, + std::to_string(rank->title_sid).c_str(), + std::to_string(cost).c_str(), + cost == 1 ? std::to_string(AA_POINT).c_str() : std::to_string(AA_POINTS).c_str() + ); + } /* QS: Player_Log_AA_Purchases */ if (RuleB(QueryServ, PlayerLogAAPurchases)) { @@ -1594,6 +1600,15 @@ bool Mob::CanUseAlternateAdvancementRank(AA::Rank *rank) { } } + int expansion = RuleI(Expansion, CurrentExpansion); + bool use_expansion_aa = RuleB(Expansion, UseCurrentExpansionAAOnly); + if (use_expansion_aa && expansion >= 0) { + if (rank->expansion > expansion) { + return false; + } + } + + if (IsClient()) { if (rank->expansion && !(CastToClient()->GetPP().expansions & (1 << (rank->expansion - 1)))) { return false; @@ -1645,6 +1660,10 @@ bool Mob::CanPurchaseAlternateAdvancementRank(AA::Rank *rank, bool check_price, return false; } + if (IsClient() && CastToClient()->HasAlreadyPurchasedRank(rank)) { + return false; + } + //You can't purchase grant only AAs they can only be assigned if(check_grant && ability->grant_only) { return false; @@ -1764,7 +1783,7 @@ bool ZoneDatabase::LoadAlternateAdvancementAbilities(std::unordered_mapcharges = Strings::ToInt(row[9]); ability->grant_only = Strings::ToBool(row[10]); ability->reset_on_death = Strings::ToBool(row[11]); - ability->first_rank_id = Strings::ToInt(row[12]); + ability->auto_grant_enabled = Strings::ToBool(row[12]); + ability->first_rank_id = Strings::ToInt(row[13]); ability->first = nullptr; abilities[ability->id] = std::unique_ptr(ability); @@ -1793,17 +1813,11 @@ bool ZoneDatabase::LoadAlternateAdvancementAbilities(std::unordered_map= 0) { - query = fmt::format("SELECT id, upper_hotkey_sid, lower_hotkey_sid, title_sid, desc_sid, cost, level_req, spell, spell_type, recast_time, " - "next_id, expansion FROM aa_ranks WHERE expansion <= {}", expansion); - } else { - query = "SELECT id, upper_hotkey_sid, lower_hotkey_sid, title_sid, desc_sid, cost, level_req, spell, spell_type, recast_time, " + + query = "SELECT id, upper_hotkey_sid, lower_hotkey_sid, title_sid, desc_sid, cost, level_req, spell, spell_type, recast_time, " "next_id, expansion FROM aa_ranks"; - } + results = QueryDatabase(query); if(results.Success()) { for(auto row = results.begin(); row != results.end(); ++row) { @@ -2068,3 +2082,126 @@ void Client::TogglePurchaseAlternativeAdvancementRank(int rank_id){ CalcBonuses(); } +void Client::AutoGrantAAPoints() { + int auto_grant_expansion = RuleI(Expansion, AutoGrantAAExpansion); + + if (auto_grant_expansion == -1) { + return; + } + + //iterate through every AA + for (auto& iter : zone->aa_abilities) { + auto ability = iter.second.get(); + + if (ability->grant_only) { + continue; + } + + if (ability->charges > 0) { + continue; + } + + if (!ability->auto_grant_enabled) { + continue; + } + + auto level = GetLevel(); + auto p = 1; + auto rank = ability->first; + while (rank != nullptr) { + if (CanUseAlternateAdvancementRank(rank)) { + if (rank->expansion <= auto_grant_expansion && rank->level_req <= level && !HasAlreadyPurchasedRank(rank)) { + FinishAlternateAdvancementPurchase(rank, true, false); + + if (rank->prev) { + MessageString( + Chat::Yellow, + AA_IMPROVE, + std::to_string(rank->title_sid).c_str(), + std::to_string(rank->prev->current_value).c_str(), + "0", + std::to_string(AA_POINTS).c_str() + ); + } + else { + MessageString( + Chat::Yellow, + AA_GAIN_ABILITY, + std::to_string(rank->title_sid).c_str(), + "0", + std::to_string(AA_POINTS).c_str() + ); + } + } + } + else { + break; + } + + p++; + rank = rank->next; + } + } + + SendClearAA(); + SendAlternateAdvancementTable(); + SendAlternateAdvancementPoints(); + SendAlternateAdvancementStats(); +} + +void Client::GrantAllAAPoints() +{ + //iterate through every AA + for (auto& iter : zone->aa_abilities) { + auto ability = iter.second.get(); + + if (ability->charges > 0) { + continue; + } + + auto level = GetLevel(); + auto p = 1; + auto rank = ability->first; + while (rank != nullptr) { + if (CanUseAlternateAdvancementRank(rank)) { + if (rank->level_req <= level && !HasAlreadyPurchasedRank(rank)) { + FinishAlternateAdvancementPurchase(rank, true, false); + } + } + else { + break; + } + + p++; + rank = rank->next; + } + } + + SaveAA(); + SendClearAA(); + SendAlternateAdvancementTable(); + SendAlternateAdvancementPoints(); + SendAlternateAdvancementStats(); +} + +bool Client::HasAlreadyPurchasedRank(AA::Rank *rank) { + auto iter = aa_ranks.find(rank->base_ability->id); + + if (iter == aa_ranks.end()) { + return false; + } + + auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(iter->first, iter->second.first); + auto ability = ability_rank.first; + auto current = ability_rank.second; + + while (current != nullptr) { + if (current == rank) { + return true; + } + + current = current->prev; + } + + return false; +} diff --git a/zone/aa_ability.h b/zone/aa_ability.h index 84c212dc2..eb102ca84 100644 --- a/zone/aa_ability.h +++ b/zone/aa_ability.h @@ -50,6 +50,7 @@ public: int status; bool grant_only; bool reset_on_death; + bool auto_grant_enabled; int type; int charges; int first_rank_id; diff --git a/zone/client.h b/zone/client.h index 5cc672aef..212e8bbc1 100644 --- a/zone/client.h +++ b/zone/client.h @@ -905,6 +905,9 @@ public: int GetAAPoints() { return m_pp.aapoints; } int GetSpentAA() { return m_pp.aapoints_spent; } uint32 GetRequiredAAExperience(); + void AutoGrantAAPoints(); + void GrantAllAAPoints(); + bool HasAlreadyPurchasedRank(AA::Rank* rank); bool SendGMCommand(std::string message, bool ignore_status = false); @@ -1662,7 +1665,7 @@ protected: bool client_data_loaded; - void FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost); + void FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost, bool send_message_and_save); Mob* bind_sight_target; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 203361e80..7ad94d2b4 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -918,6 +918,8 @@ void Client::CompleteConnect() RecordStats(); + AutoGrantAAPoints(); + // enforce some rules.. if (!CanEnterZone()) { LogInfo("Kicking character [{}] from zone, not allowed here (missing requirements)", GetCleanName()); diff --git a/zone/command.cpp b/zone/command.cpp index 96a7a9613..2ad53154f 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -137,6 +137,7 @@ int command_init(void) command_add("givemoney", "[Platinum] [Gold] [Silver] [Copper] - Gives specified amount of money to you or your player target", AccountStatus::GMMgmt, command_givemoney) || command_add("gmzone", "[Zone ID|Zone Short Name] [Version] [Instance Identifier] - Zones to a private GM instance (Version defaults to 0 and Instance Identifier defaults to 'gmzone' if not used)", AccountStatus::GMAdmin, command_gmzone) || command_add("goto", "[playername] or [x y z] [h] - Teleport to the provided coordinates or to your target", AccountStatus::Steward, command_goto) || + command_add("grantaa", "Grants a player all available AA points for their level.", AccountStatus::GMMgmt, command_grantaa) || command_add("grid", "[add/delete] [grid_num] [wandertype] [pausetype] - Create/delete a wandering grid", AccountStatus::GMAreas, command_grid) || command_add("guild", "Guild manipulation commands. Use argument help for more info.", AccountStatus::Steward, command_guild) || command_add("help", "[Search Criteria] - List available commands and their description, specify partial command as argument to search", AccountStatus::Player, command_help) || @@ -826,6 +827,7 @@ void command_bot(Client *c, const Seperator *sep) #include "gm_commands/givemoney.cpp" #include "gm_commands/gmzone.cpp" #include "gm_commands/goto.cpp" +#include "gm_commands/grantaa.cpp" #include "gm_commands/grid.cpp" #include "gm_commands/guild.cpp" #include "gm_commands/hp.cpp" diff --git a/zone/command.h b/zone/command.h index 553c93c09..5c06e2c9c 100644 --- a/zone/command.h +++ b/zone/command.h @@ -87,6 +87,7 @@ void command_giveitem(Client *c, const Seperator *sep); void command_givemoney(Client *c, const Seperator *sep); void command_gmzone(Client *c, const Seperator *sep); void command_goto(Client *c, const Seperator *sep); +void command_grantaa(Client* c, const Seperator* sep); void command_grid(Client *c, const Seperator *sep); void command_guild(Client *c, const Seperator *sep); void command_help(Client *c, const Seperator *sep); diff --git a/zone/entity.cpp b/zone/entity.cpp index 74046c77b..c4e82a4e8 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -5652,6 +5652,10 @@ void EntityList::SendAlternateAdvancementStats() { for (auto &c : client_list) { c.second->Message(Chat::White, "Reloading AA"); c.second->ReloadExpansionProfileSetting(); + if (!database.LoadAlternateAdvancement(c.second)) { + c.second->Message(Chat::Red, "Error loading alternate advancement character data"); + } + c.second->SendClearPlayerAA(); c.second->SendAlternateAdvancementTable(); c.second->SendAlternateAdvancementStats(); diff --git a/zone/exp.cpp b/zone/exp.cpp index 76a7e0c7d..d95aa7f66 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -938,6 +938,8 @@ void Client::SetLevel(uint8 set_level, bool command) m_pp.exp = GetEXPForLevel(set_level); Message(Chat::Yellow, fmt::format("Welcome to level {}!", set_level).c_str()); lu->exp = 0; + + AutoGrantAAPoints(); } else { const auto temporary_xp = ( static_cast(m_pp.exp - GetEXPForLevel(GetLevel())) / diff --git a/zone/gm_commands/grantaa.cpp b/zone/gm_commands/grantaa.cpp new file mode 100644 index 000000000..f2b77df21 --- /dev/null +++ b/zone/gm_commands/grantaa.cpp @@ -0,0 +1,20 @@ +#include "../client.h" + +void command_grantaa(Client *c, const Seperator *sep) +{ + if (!c->GetTarget() || !c->GetTarget()->IsClient()) { + c->Message(Chat::White, "You must target a player to use this command."); + return; + } + + auto t = c->GetTarget()->CastToClient(); + t->GrantAllAAPoints(); + + c->Message( + Chat::White, + fmt::format( + "Successfully granted all Alternate Advancements for {}.", + c->GetTargetDescription(t) + ).c_str() + ); +}