From 8ae9c99f3d29e4f67fae96e15703229bbc92611b Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Thu, 13 Dec 2018 19:41:19 -0500 Subject: [PATCH] Added new data bucket and quest functionality. Added multiple new instance related quest functions. 1. quest::GetInstanceIDByCharID(const char *zone, int16 version, uint32 char_id) - Allows you to pull the instance ID of a client by character ID. 2. quest::AssignToInstanceByCharID(uint16 instance_id, uint32 char_id) - Allows you to assign an instance to a client by character ID. 3. quest::RemoveFromInstanceByCharID(uint16 instance_id, uint32 char_id) - Allows you to remove a client from an instance by character ID. Added spell buckets, similar to spell globals. - Uses a new spell_buckets table and the Spells:EnableSpellBuckets rule. Added max level by data bucket. - Uses data bucket char_id-CharMaxLevel and Character:PerCharacterBucketMaxLevel rule. --- common/ruletypes.h | 4 +- common/version.h | 2 +- utils/sql/db_update_manifest.txt | 1 + .../git/required/2018_12_13_spell_buckets.sql | 10 +++ utils/sql/user_tables.txt | 1 + zone/client.h | 4 +- zone/exp.cpp | 28 ++++++ zone/questmgr.cpp | 90 +++++++++++++------ zone/questmgr.h | 3 + zone/spells.cpp | 79 +++++++++++----- 10 files changed, 170 insertions(+), 52 deletions(-) create mode 100644 utils/sql/git/required/2018_12_13_spell_buckets.sql diff --git a/common/ruletypes.h b/common/ruletypes.h index ab9acecf1..21c006099 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -38,6 +38,7 @@ RULE_CATEGORY(Character) RULE_INT(Character, MaxLevel, 65) RULE_BOOL(Character, PerCharacterQglobalMaxLevel, false) // This will check for qglobal 'CharMaxLevel' character qglobal (Type 5), if player tries to level beyond that point, it will not go beyond that level +RULE_BOOL(Character, PerCharacterBucketMaxLevel, false) // This will check for data bucket 'CharMaxLevel', if player tries to level beyond that point, it will not go beyond that level RULE_INT(Character, MaxExpLevel, 0) //Sets the Max Level attainable via Experience RULE_INT(Character, DeathExpLossLevel, 10) // Any level greater than this will lose exp on death RULE_INT(Character, DeathExpLossMaxLevel, 255) // Any level greater than this will no longer lose exp on death @@ -342,7 +343,8 @@ RULE_INT(Spells, TranslocateTimeLimit, 0) // If not zero, time in seconds to acc RULE_INT(Spells, SacrificeMinLevel, 46) //first level Sacrifice will work on RULE_INT(Spells, SacrificeMaxLevel, 69) //last level Sacrifice will work on RULE_INT(Spells, SacrificeItemID, 9963) //Item ID of the item Sacrifice will return (defaults to an EE) -RULE_BOOL(Spells, EnableSpellGlobals, false) // If Enabled, spells check the spell_globals table and compare character data from the quest globals before allowing that spell to scribe with scribespells +RULE_BOOL(Spells, EnableSpellGlobals, false) // If Enabled, spells check the spell_globals table and compare character data from their quest globals before allowing the spell to scribe with scribespells/traindiscs +RULE_BOOL(Spells, EnableSpellBuckets, false) // If Enabled, spells check the spell_buckets table and compare character data from their data buckets before allowing the spell to scribe with scribespells/traindiscs RULE_INT(Spells, MaxBuffSlotsNPC, 60) // default to Tit's limit RULE_INT(Spells, MaxSongSlotsNPC, 0) // NPCs don't have songs ... RULE_INT(Spells, MaxDiscSlotsNPC, 0) // NPCs don't have discs ... diff --git a/common/version.h b/common/version.h index e5603c247..baead2fe2 100644 --- a/common/version.h +++ b/common/version.h @@ -30,7 +30,7 @@ Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9130 +#define CURRENT_BINARY_DATABASE_VERSION 9131 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9021 #else diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 81a8fa2d8..a203b825b 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -384,6 +384,7 @@ 9128|2018_08_13_inventory_version_update.sql|SHOW TABLES LIKE 'inventory_version'|not_empty| 9129|2018_08_13_inventory_update.sql|SHOW TABLES LIKE 'inventory_versions'|empty| 9130|2018_11_25_name_filter_update.sql|SHOW COLUMNS FROM `name_filter` LIKE 'id'|empty| +9131|2018_12_13_spell_buckets.sql|SHOW TABLES LIKE 'spell_buckets'|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/required/2018_12_13_spell_buckets.sql b/utils/sql/git/required/2018_12_13_spell_buckets.sql new file mode 100644 index 000000000..1c56799f0 --- /dev/null +++ b/utils/sql/git/required/2018_12_13_spell_buckets.sql @@ -0,0 +1,10 @@ +CREATE TABLE `spell_buckets` ( + `spellid` bigint(11) unsigned NOT NULL, + `key` varchar(100) DEFAULT NULL, + `value` text, + PRIMARY KEY (`spellid`), + KEY `key_index` (`key`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:EnableSpellBuckets', 'false', 'Enables spell buckets'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Character:PerCharacterBucketMaxLevel', 'false', 'Enables data bucket-based max level.'); \ No newline at end of file diff --git a/utils/sql/user_tables.txt b/utils/sql/user_tables.txt index 4d77be3a1..a76e110f3 100644 --- a/utils/sql/user_tables.txt +++ b/utils/sql/user_tables.txt @@ -85,6 +85,7 @@ reports respawn_times sharedbank spell_globals +spell_buckets timers trader trader_audit diff --git a/zone/client.h b/zone/client.h index 023367e50..f7848202d 100644 --- a/zone/client.h +++ b/zone/client.h @@ -773,8 +773,10 @@ public: void UnscribeSpellAll(bool update_client = true); void UntrainDisc(int slot, bool update_client = true); void UntrainDiscAll(bool update_client = true); - bool SpellGlobalCheck(uint16 Spell_ID, uint32 Char_ID); + bool SpellGlobalCheck(uint16 spell_id, uint32 char_id); + bool SpellBucketCheck(uint16 spell_id, uint32 char_id); uint32 GetCharMaxLevelFromQGlobal(); + uint32 GetCharMaxLevelFromBucket(); inline bool IsSitting() const {return (playeraction == 1);} inline bool IsBecomeNPC() const { return npcflag; } diff --git a/zone/exp.cpp b/zone/exp.cpp index 94f36197d..302e29279 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -695,6 +695,18 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { } } } + + if(RuleB(Character, PerCharacterBucketMaxLevel)){ + uint32 MaxLevel = GetCharMaxLevelFromBucket(); + if(MaxLevel){ + if(GetLevel() >= MaxLevel){ + uint32 expneeded = GetEXPForLevel(MaxLevel); + if(set_exp > expneeded) { + set_exp = expneeded; + } + } + } + } if ((GetLevel() != check_level) && !(check_level >= maxlevel)) { char val1[20]={0}; @@ -1133,6 +1145,22 @@ uint32 Client::GetCharMaxLevelFromQGlobal() { return false; } +uint32 Client::GetCharMaxLevelFromBucket() { + uint32 char_id = this->CharacterID(); + std::string query = StringFormat("SELECT value FROM data_buckets WHERE key = '%i-CharMaxLevel'", char_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + Log(Logs::General, Logs::Error, "Data bucket for CharMaxLevel for char ID %i failed.", char_id); + return false; + } + + if (results.RowCount() > 0) { + auto row = results.begin(); + return atoi(row[0]); + } + return false; +} + uint32 Client::GetRequiredAAExperience() { #ifdef LUA_EQEMU uint32 lua_ret = 0; diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 1393b0221..e775fa368 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -976,37 +976,44 @@ void QuestManager::permagender(int gender_id) { uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) { QuestManagerCurrentQuestVars(); uint16 book_slot, count; - uint16 curspell; + uint16 spell_id; - uint32 Char_ID = initiator->CharacterID(); + uint32 char_id = initiator->CharacterID(); bool SpellGlobalRule = RuleB(Spells, EnableSpellGlobals); + bool SpellBucketRule = RuleB(Spells, EnableSpellBuckets); bool SpellGlobalCheckResult = 0; + bool SpellBucketCheckResult = 0; - for(curspell = 0, book_slot = initiator->GetNextAvailableSpellBookSlot(), count = 0; curspell < SPDAT_RECORDS && book_slot < MAX_PP_SPELLBOOK; curspell++, book_slot = initiator->GetNextAvailableSpellBookSlot(book_slot)) + for(spell_id = 0, book_slot = initiator->GetNextAvailableSpellBookSlot(), count = 0; spell_id < SPDAT_RECORDS && book_slot < MAX_PP_SPELLBOOK; spell_id++, book_slot = initiator->GetNextAvailableSpellBookSlot(book_slot)) { if ( - spells[curspell].classes[WARRIOR] != 0 && //check if spell exists - spells[curspell].classes[initiator->GetPP().class_-1] <= max_level && //maximum level - spells[curspell].classes[initiator->GetPP().class_-1] >= min_level && //minimum level - spells[curspell].skill != 52 && - spells[curspell].effectid[EFFECT_COUNT - 1] != 10 + spells[spell_id].classes[WARRIOR] != 0 && //check if spell exists + spells[spell_id].classes[initiator->GetPP().class_-1] <= max_level && //maximum level + spells[spell_id].classes[initiator->GetPP().class_-1] >= min_level && //minimum level + spells[spell_id].skill != 52 && + spells[spell_id].effectid[EFFECT_COUNT - 1] != 10 ) { if (book_slot == -1) //no more book slots break; - if(!IsDiscipline(curspell) && !initiator->HasSpellScribed(curspell)) { //isn't a discipline & we don't already have it scribed + if(!IsDiscipline(spell_id) && !initiator->HasSpellScribed(spell_id)) { //isn't a discipline & we don't already have it scribed if (SpellGlobalRule) { // Bool to see if the character has the required QGlobal to scribe it if one exists in the Spell_Globals table - SpellGlobalCheckResult = initiator->SpellGlobalCheck(curspell, Char_ID); + SpellGlobalCheckResult = initiator->SpellGlobalCheck(spell_id, char_id); if (SpellGlobalCheckResult) { - initiator->ScribeSpell(curspell, book_slot); + initiator->ScribeSpell(spell_id, book_slot); count++; } - } - else { - initiator->ScribeSpell(curspell, book_slot); + } else if (SpellBucketRule) { + SpellBucketCheckResult = initiator->SpellBucketCheck(spell_id, char_id); + if (SpellBucketCheckResult) { + initiator->ScribeSpell(spell_id, book_slot); + count++; + } + } else { + initiator->ScribeSpell(spell_id, book_slot); count++; } } @@ -1018,46 +1025,59 @@ uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) { uint16 QuestManager::traindiscs(uint8 max_level, uint8 min_level) { QuestManagerCurrentQuestVars(); uint16 count; - uint16 curspell; + uint16 spell_id; - uint32 Char_ID = initiator->CharacterID(); + uint32 char_id = initiator->CharacterID(); bool SpellGlobalRule = RuleB(Spells, EnableSpellGlobals); + bool SpellBucketRule = RuleB(Spells, EnableSpellBuckets); bool SpellGlobalCheckResult = 0; + bool SpellBucketCheckResult = 0; - for(curspell = 0, count = 0; curspell < SPDAT_RECORDS; curspell++) + for(spell_id = 0, count = 0; spell_id < SPDAT_RECORDS; spell_id++) { if ( - spells[curspell].classes[WARRIOR] != 0 && //check if spell exists - spells[curspell].classes[initiator->GetPP().class_-1] <= max_level && //maximum level - spells[curspell].classes[initiator->GetPP().class_-1] >= min_level && //minimum level - spells[curspell].skill != 52 && - ( !RuleB(Spells, UseCHAScribeHack) || spells[curspell].effectid[EFFECT_COUNT - 1] != 10 ) + spells[spell_id].classes[WARRIOR] != 0 && //check if spell exists + spells[spell_id].classes[initiator->GetPP().class_-1] <= max_level && //maximum level + spells[spell_id].classes[initiator->GetPP().class_-1] >= min_level && //minimum level + spells[spell_id].skill != 52 && + ( !RuleB(Spells, UseCHAScribeHack) || spells[spell_id].effectid[EFFECT_COUNT - 1] != 10 ) ) { - if(IsDiscipline(curspell)){ + if(IsDiscipline(spell_id)){ //we may want to come up with a function like Client::GetNextAvailableSpellBookSlot() to help speed this up a little for(uint32 r = 0; r < MAX_PP_DISCIPLINES; r++) { - if(initiator->GetPP().disciplines.values[r] == curspell) { + if(initiator->GetPP().disciplines.values[r] == spell_id) { initiator->Message(13, "You already know this discipline."); break; //continue the 1st loop } else if(initiator->GetPP().disciplines.values[r] == 0) { if (SpellGlobalRule) { // Bool to see if the character has the required QGlobal to train it if one exists in the Spell_Globals table - SpellGlobalCheckResult = initiator->SpellGlobalCheck(curspell, Char_ID); + SpellGlobalCheckResult = initiator->SpellGlobalCheck(spell_id, char_id); if (SpellGlobalCheckResult) { - initiator->GetPP().disciplines.values[r] = curspell; - database.SaveCharacterDisc(Char_ID, r, curspell); + initiator->GetPP().disciplines.values[r] = spell_id; + database.SaveCharacterDisc(char_id, r, spell_id); initiator->SendDisciplineUpdate(); initiator->Message(0, "You have learned a new discipline!"); count++; //success counter } break; //continue the 1st loop + } else if (SpellBucketRule) { + // Bool to see if the character has the required bucket to train it if one exists in the spell_buckets table + SpellBucketCheckResult = initiator->SpellBucketCheck(spell_id, char_id); + if (SpellBucketCheckResult) { + initiator->GetPP().disciplines.values[r] = spell_id; + database.SaveCharacterDisc(char_id, r, spell_id); + initiator->SendDisciplineUpdate(); + initiator->Message(0, "You have learned a new discipline!"); + count++; + } + break; } else { - initiator->GetPP().disciplines.values[r] = curspell; - database.SaveCharacterDisc(Char_ID, r, curspell); + initiator->GetPP().disciplines.values[r] = spell_id; + database.SaveCharacterDisc(char_id, r, spell_id); initiator->SendDisciplineUpdate(); initiator->Message(0, "You have learned a new discipline!"); count++; //success counter @@ -2661,6 +2681,10 @@ uint16 QuestManager::GetInstanceID(const char *zone, int16 version) return 0; } +uint16 QuestManager::GetInstanceIDByCharID(const char *zone, int16 version, uint32 char_id) { + return database.GetInstanceID(zone, char_id, version); +} + void QuestManager::AssignToInstance(uint16 instance_id) { QuestManagerCurrentQuestVars(); @@ -2670,6 +2694,10 @@ void QuestManager::AssignToInstance(uint16 instance_id) } } +void QuestManager::AssignToInstanceByCharID(uint16 instance_id, uint32 char_id) { + database.AddClientToInstance(instance_id, char_id); +} + void QuestManager::AssignGroupToInstance(uint16 instance_id) { QuestManagerCurrentQuestVars(); @@ -2710,6 +2738,10 @@ void QuestManager::RemoveFromInstance(uint16 instance_id) } } +void QuestManager::RemoveFromInstanceByCharID(uint16 instance_id, uint32 char_id) { + database.RemoveClientFromInstance(instance_id, char_id); +} + void QuestManager::RemoveAllFromInstance(uint16 instance_id) { QuestManagerCurrentQuestVars(); diff --git a/zone/questmgr.h b/zone/questmgr.h index a3b9fac43..6818ae09b 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -234,10 +234,13 @@ public: uint32 GetInstanceTimerByID(uint16 instance_id = 0); void DestroyInstance(uint16 instance_id); uint16 GetInstanceID(const char *zone, int16 version); + uint16 GetInstanceIDByCharID(const char *zone, int16 version, uint32 char_id); void AssignToInstance(uint16 instance_id); + void AssignToInstanceByCharID(uint16 instance_id, uint32 char_id); void AssignGroupToInstance(uint16 instance_id); void AssignRaidToInstance(uint16 instance_id); void RemoveFromInstance(uint16 instance_id); + void RemoveFromInstanceByCharID(uint16 instance_id, uint32 char_id); //void RemoveGroupFromInstance(uint16 instance_id); //potentially useful but not implmented at this time. //void RemoveRaidFromInstance(uint16 instance_id); //potentially useful but not implmented at this time. void RemoveAllFromInstance(uint16 instance_id); diff --git a/zone/spells.cpp b/zone/spells.cpp index 0fcb7fe2b..1cb920786 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -5154,14 +5154,11 @@ int Client::FindSpellBookSlotBySpellID(uint16 spellid) { return -1; //default } -bool Client::SpellGlobalCheck(uint16 spell_ID, uint32 char_ID) { - - std::string spell_Global_Name; - int spell_Global_Value; - int global_Value; - - std::string query = StringFormat("SELECT qglobal, value FROM spell_globals " - "WHERE spellid = %i", spell_ID); +bool Client::SpellGlobalCheck(uint16 spell_id, uint32 char_id) { + std::string spell_global_name; + int spell_global_value; + int global_value; + std::string query = StringFormat("SELECT qglobal, value FROM spell_globals WHERE spellid = %i", spell_id); auto results = database.QueryDatabase(query); if (!results.Success()) { return false; // Query failed, so prevent spell from scribing just in case @@ -5171,37 +5168,79 @@ bool Client::SpellGlobalCheck(uint16 spell_ID, uint32 char_ID) { return true; // Spell ID isn't listed in the spells_global table, so it is not restricted from scribing auto row = results.begin(); - spell_Global_Name = row[0]; - spell_Global_Value = atoi(row[1]); + spell_global_name = row[0]; + spell_global_value = atoi(row[1]); - if (spell_Global_Name.empty()) + if (spell_global_name.empty()) return true; // If the entry in the spell_globals table has nothing set for the qglobal name query = StringFormat("SELECT value FROM quest_globals " "WHERE charid = %i AND name = '%s'", - char_ID, spell_Global_Name.c_str()); + char_id, spell_global_name.c_str()); results = database.QueryDatabase(query); if (!results.Success()) { - Log(Logs::General, Logs::Error, "Spell ID %i query of spell_globals with Name: '%s' Value: '%i' failed", spell_ID, spell_Global_Name.c_str(), spell_Global_Value); + Log(Logs::General, Logs::Error, "Spell ID %i query of spell_globals with Name: '%s' Value: '%i' failed", spell_id, spell_global_name.c_str(), spell_global_value); return false; } if (results.RowCount() != 1) { - Log(Logs::General, Logs::Error, "Char ID: %i does not have the Qglobal Name: '%s' for Spell ID %i", char_ID, spell_Global_Name.c_str(), spell_ID); + Log(Logs::General, Logs::Error, "Char ID: %i does not have the Qglobal Name: '%s' for Spell ID %i", char_id, spell_global_name.c_str(), spell_id); + return false; + } + + row = results.begin(); + global_value = atoi(row[0]); + if (global_value == spell_global_value) + return true; // If the values match from both tables, allow the spell to be scribed + else if (global_value > spell_global_value) + return true; // Check if the qglobal value is greater than the require spellglobal value + + // If no matching result found in qglobals, don't scribe this spell + Log(Logs::General, Logs::Error, "Char ID: %i Spell_globals Name: '%s' Value: '%i' did not match QGlobal Value: '%i' for Spell ID %i", char_id, spell_global_name.c_str(), spell_global_value, global_value, spell_id); + return false; +} + +bool Client::SpellBucketCheck(uint16 spell_id, uint32 char_id) { + std::string spell_bucket_name; + int spell_bucket_value; + int bucket_value; + std::string query = StringFormat("SELECT key, value FROM spell_buckets WHERE spellid = %i", spell_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) + return false; + + if (results.RowCount() != 1) + return true; + + auto row = results.begin(); + spell_bucket_name = row[0]; + spell_bucket_value = atoi(row[1]); + if (spell_bucket_name.empty()) + return true; + + query = StringFormat("SELECT value FROM data_buckets WHERE key = '%i-%s'", char_id, spell_bucket_name.c_str()); + results = database.QueryDatabase(query); + if (!results.Success()) { + Log(Logs::General, Logs::Error, "Spell bucket %s for spell ID %i for char ID %i failed.", spell_bucket_name.c_str(), spell_id, char_id); + return false; + } + + if (results.RowCount() != 1) { + Log(Logs::General, Logs::Error, "Spell bucket %s does not exist for spell ID %i for char ID %i.", spell_bucket_name.c_str(), spell_id, char_id); return false; } row = results.begin(); - global_Value = atoi(row[0]); + bucket_value = atoi(row[0]); - if (global_Value == spell_Global_Value) + if (bucket_value == spell_bucket_value) return true; // If the values match from both tables, allow the spell to be scribed - else if (global_Value > spell_Global_Value) - return true; // Check if the qglobal value is greater than the require spellglobal value + else if (bucket_value > spell_bucket_value) + return true; // Check if the data bucket value is greater than the required spell bucket value - // If no matching result found in qglobals, don't scribe this spell - Log(Logs::General, Logs::Error, "Char ID: %i Spell_globals Name: '%s' Value: '%i' did not match QGlobal Value: '%i' for Spell ID %i", char_ID, spell_Global_Name.c_str(), spell_Global_Value, global_Value, spell_ID); + // If no matching result found in spell buckets, don't scribe this spell + Log(Logs::General, Logs::Error, "Spell bucket %s for spell ID %i for char ID %i did not match value %i.", spell_bucket_name.c_str(), spell_id, char_id, spell_bucket_value); return false; }