Updated the rule system to automatically add new rules and remove orphaned entries from the rule values tables

This commit is contained in:
Uleat 2019-09-03 04:04:05 -04:00
parent a534ab83ec
commit f9536f9621
8 changed files with 1064 additions and 709 deletions

View File

@ -1,5 +1,16 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50)
-------------------------------------------------------
== 9/02/2019 ==
Uleat: Added code to inject new rules into the 'default' ruleset and remove orphaned rules from all rulesets
- New rules are only added using the 'default' ruleset - Other rulesets will need to be added manually or through in-game updates
-- Rule notes are now loaded into the system's hard-coded entries and will now propagate properly into database updates
- Defunct rules will have their orhpaned entries removed from the `rule_values` table for the all rulesets
Note: If you would like to add these rules before starting your server so that you can modify them, start world.exe
manually and wait for the console messages to finish. It should take 5-10 seconds, or so. The world log should contain
a list of the added and removed entries, IF the `file` field of the 'Status' logging category is set to 1 or higher.
(Don't forget to manually stop the process after the update is complete.)
== 8/30/2019 ==
Uleat: Added code to inject new commands and remove orphaned commands from both command systems
- New commands are added with their status (`access`) set to the server default value - no aliases are defined

View File

@ -21,6 +21,7 @@
#include "string_util.h"
#include <cstdlib>
#include <cstring>
#include <fmt/format.h>
/*
Commands:
@ -45,14 +46,14 @@ const char *RuleManager::s_categoryNames[_CatCount+1] = {
const RuleManager::RuleInfo RuleManager::s_RuleInfo[_IntRuleCount+_RealRuleCount+_BoolRuleCount+1] = {
/* this is done in three steps so we can reliably get to them by index*/
#define RULE_INT(cat, rule, default_value) \
{ #cat ":" #rule, Category__##cat, IntRule, Int__##rule },
#define RULE_INT(cat, rule, default_value, notes) \
{ #cat ":" #rule, Category__##cat, IntRule, Int__##rule, notes },
#include "ruletypes.h"
#define RULE_REAL(cat, rule, default_value) \
{ #cat ":" #rule, Category__##cat, RealRule, Real__##rule },
#define RULE_REAL(cat, rule, default_value, notes) \
{ #cat ":" #rule, Category__##cat, RealRule, Real__##rule, notes },
#include "ruletypes.h"
#define RULE_BOOL(cat, rule, default_value) \
{ #cat ":" #rule, Category__##cat, BoolRule, Bool__##rule },
#define RULE_BOOL(cat, rule, default_value, notes) \
{ #cat ":" #rule, Category__##cat, BoolRule, Bool__##rule, notes },
#include "ruletypes.h"
{ "Invalid Rule", _CatCount, IntRule }
};
@ -178,11 +179,11 @@ void RuleManager::ResetRules(bool reload) {
}
Log(Logs::Detail, Logs::Rules, "Resetting running rules to default values");
#define RULE_INT(cat, rule, default_value) \
#define RULE_INT(cat, rule, default_value, notes) \
m_RuleIntValues[ Int__##rule ] = default_value;
#define RULE_REAL(cat, rule, default_value) \
#define RULE_REAL(cat, rule, default_value, notes) \
m_RuleRealValues[ Real__##rule ] = default_value;
#define RULE_BOOL(cat, rule, default_value) \
#define RULE_BOOL(cat, rule, default_value, notes) \
m_RuleBoolValues[ Bool__##rule ] = default_value;
#include "ruletypes.h"
@ -225,7 +226,89 @@ const char *RuleManager::_GetRuleName(RuleType type, uint16 index) {
return("InvalidRule??");
}
//assumes index is valid!
const std::string &RuleManager::_GetRuleNotes(RuleType type, uint16 index) {
switch (type) {
case IntRule:
return(s_RuleInfo[index].notes);
case RealRule:
return(s_RuleInfo[index + _IntRuleCount].notes);
case BoolRule:
return(s_RuleInfo[index + _IntRuleCount + _RealRuleCount].notes);
}
//should never happen
return(std::string());
}
bool RuleManager::LoadRules(Database *database, const char *ruleset_name, bool reload) {
int ruleset_id = this->GetRulesetID(database, ruleset_name);
if (ruleset_id < 0) {
Log(Logs::Detail, Logs::Rules, "Failed to find ruleset '%s' for load operation. Canceling.", ruleset_name);
return (false);
}
m_activeRuleset = ruleset_id;
m_activeName = ruleset_name;
/* Load default ruleset values first if we're loading something other than default */
if (strcasecmp(ruleset_name, "default") != 0) {
std::string default_ruleset_name = "default";
int default_ruleset_id = GetRulesetID(database, default_ruleset_name.c_str());
if (default_ruleset_id < 0) {
Log(Logs::Detail,
Logs::Rules,
"Failed to find default ruleset '%s' for load operation. Canceling.",
default_ruleset_name.c_str()
);
return (false);
}
Log(Logs::Detail, Logs::Rules, "Processing rule set '%s' (%d) load...", default_ruleset_name.c_str(), default_ruleset_id);
std::string query = StringFormat(
"SELECT `rule_name`, `rule_value` FROM `rule_values` WHERE `ruleset_id` = '%d'",
default_ruleset_id
);
auto results = database->QueryDatabase(query);
if (!results.Success()) {
return false;
}
for (auto row = results.begin(); row != results.end(); ++row) {
if (!SetRule(row[0], row[1], nullptr, false, reload)) {
Log(Logs::Detail, Logs::Rules, "Unable to interpret rule record for '%s'", row[0]);
}
}
}
Log(Logs::Detail, Logs::Rules, "Processing rule set '%s' (%d) load...", ruleset_name, ruleset_id);
std::string query = StringFormat("SELECT `rule_name`, `rule_value` FROM `rule_values` WHERE `ruleset_id` = '%d'", ruleset_id);
auto results = database->QueryDatabase(query);
if (!results.Success()) {
return false;
}
for (auto row = results.begin(); row != results.end(); ++row) {
if (!SetRule(row[0], row[1], nullptr, false, reload)) {
Log(Logs::Detail, Logs::Rules, "Unable to interpret rule record for '%s'", row[0]);
}
}
return true;
}
// convert this to a single REPLACE query (it can handle it - currently at 608 rules)
void RuleManager::SaveRules(Database *database, const char *ruleset_name) {
// tbh, UpdateRules() can probably be called since it will handle deletions of
// orphaned entries of existing rulesets as well as adding the new ones
// (it already does it as a single REPLACE query)
if (ruleset_name != nullptr) {
//saving to a specific name
@ -245,6 +328,10 @@ void RuleManager::SaveRules(Database *database, const char *ruleset_name) {
Log(Logs::Detail, Logs::Rules, "Saving running rules into running rule set %s", m_activeName.c_str(), m_activeRuleset);
}
// this should be all that is needed..with the exception of handling expansion-based rules...
// (those really need to be put somewhere else - probably variables)
//UpdateRules(database, m_activeName.c_str(), m_activeRuleset, true);
int i;
for (i = 0; i < _IntRuleCount; i++) {
_SaveRule(database, IntRule, i);
@ -257,56 +344,6 @@ void RuleManager::SaveRules(Database *database, const char *ruleset_name) {
}
}
bool RuleManager::LoadRules(Database *database, const char *ruleset_name, bool reload) {
int ruleset_id = this->GetRulesetID(database, ruleset_name);
if (ruleset_id < 0) {
Log(Logs::Detail, Logs::Rules, "Failed to find ruleset '%s' for load operation. Canceling.", ruleset_name);
return (false);
}
Log(Logs::Detail, Logs::Rules, "Loading rule set '%s' (%d)", ruleset_name, ruleset_id);
m_activeRuleset = ruleset_id;
m_activeName = ruleset_name;
/* Load default ruleset values first if we're loading something other than default */
if (strcasecmp(ruleset_name, "default") != 0) {
std::string default_ruleset_name = "default";
int default_ruleset_id = GetRulesetID(database, default_ruleset_name.c_str());
if (default_ruleset_id < 0) {
Log(Logs::Detail, Logs::Rules, "Failed to find default ruleset '%s' for load operation. Canceling.",
default_ruleset_name.c_str());
return (false);
}
Log(Logs::Detail, Logs::Rules, "Loading rule set '%s' (%d)", default_ruleset_name.c_str(), default_ruleset_id);
std::string query = StringFormat(
"SELECT rule_name, rule_value FROM rule_values WHERE ruleset_id = %d",
default_ruleset_id
);
auto results = database->QueryDatabase(query);
if (!results.Success())
return false;
for (auto row = results.begin(); row != results.end(); ++row)
if (!SetRule(row[0], row[1], nullptr, false, reload))
Log(Logs::Detail, Logs::Rules, "Unable to interpret rule record for %s", row[0]);
}
std::string query = StringFormat("SELECT rule_name, rule_value FROM rule_values WHERE ruleset_id=%d", ruleset_id);
auto results = database->QueryDatabase(query);
if (!results.Success())
return false;
for (auto row = results.begin(); row != results.end(); ++row)
if (!SetRule(row[0], row[1], nullptr, false, reload))
Log(Logs::Detail, Logs::Rules, "Unable to interpret rule record for %s", row[0]);
return true;
}
void RuleManager::_SaveRule(Database *database, RuleType type, uint16 index) {
char value_string[100];
@ -328,17 +365,273 @@ void RuleManager::_SaveRule(Database *database, RuleType type, uint16 index) {
}
std::string query = StringFormat(
"REPLACE INTO rule_values "
"(ruleset_id, rule_name, rule_value) "
" VALUES(%d, '%s', '%s')",
"REPLACE INTO `rule_values`"
"(`ruleset_id`, `rule_name`, `rule_value`, `notes`)"
" VALUES('%d', '%s', '%s', '%s')",
m_activeRuleset,
_GetRuleName(type, index),
value_string
value_string,
EscapeString(_GetRuleNotes(type, index)).c_str()
);
database->QueryDatabase(query);
}
bool RuleManager::UpdateChangedRules(Database *db, const char *ruleset_name, bool quiet_update)
{
return false;
}
bool RuleManager::UpdateInjectedRules(Database *db, const char *ruleset_name, bool quiet_update)
{
std::vector<std::string> database_data;
std::map<std::string, std::pair<std::string, const std::string *>> rule_data;
std::vector<std::tuple<int, std::string, std::string, std::string>> injected_rule_entries;
if (!db) {
return false;
}
if (ruleset_name == nullptr) {
return false;
}
int ruleset_id = GetRulesetID(db, ruleset_name);
if (ruleset_id < 0) {
return false;
}
// load database rule names
std::string query(StringFormat("SELECT `rule_name` FROM `rule_values` WHERE `ruleset_id` = '%i'", ruleset_id));
auto results = db->QueryDatabase(query);
if (!results.Success()) {
return false;
}
// build database data entries
for (auto &row : results) {
database_data.push_back(std::string(row[0]));
}
// build rule data entries
for (const auto &ri_iter : s_RuleInfo) {
if (strcasecmp(ri_iter.name, "Invalid Rule") == 0) {
continue;
}
char buffer[100];
switch (ri_iter.type) {
case IntRule:
sprintf(buffer, "%d", m_RuleIntValues[ri_iter.rule_index]);
rule_data[ri_iter.name].first = buffer;
rule_data[ri_iter.name].second = &ri_iter.notes;
break;
case RealRule:
sprintf(buffer, "%.13f", m_RuleRealValues[ri_iter.rule_index]);
rule_data[ri_iter.name].first = buffer;
rule_data[ri_iter.name].second = &ri_iter.notes;
break;
case BoolRule:
sprintf(buffer, "%s", (m_RuleBoolValues[ri_iter.rule_index] ? "true" : "false"));
rule_data[ri_iter.name].first = buffer;
rule_data[ri_iter.name].second = &ri_iter.notes;
break;
default:
break;
}
}
// build injected entries
for (const auto &rd_iter : rule_data) {
const auto &dd_iter = std::find(database_data.begin(), database_data.end(), rd_iter.first);
if (dd_iter == database_data.end()) {
injected_rule_entries.push_back(
std::tuple<int, std::string, std::string, std::string>(
ruleset_id, // `ruleset_id`
rd_iter.first, // `rule_name`
rd_iter.second.first, // `rule_value`
EscapeString(*rd_iter.second.second) // `notes`
)
);
if (!quiet_update) {
Log(Logs::General,
Logs::Status,
"New Rule '%s' found... Adding to `rule_values` table with ruleset '%s' (%i) and rule value '%s'...",
rd_iter.first.c_str(),
ruleset_name,
ruleset_id,
rd_iter.second.first.c_str()
);
}
}
}
if (injected_rule_entries.size()) {
return _UpdateRules(db, ruleset_name, ruleset_id, injected_rule_entries, std::vector<std::string>());
}
else {
return true;
}
}
bool RuleManager::UpdateOrphanedRules(Database *db, bool quiet_update)
{
std::vector<std::string> rule_data;
std::vector<std::string> orphaned_rule_entries;
if (!db) {
return false;
}
// load database rule names
std::string query("SELECT `rule_name` FROM `rule_values` GROUP BY `rule_name`");
auto results = db->QueryDatabase(query);
if (!results.Success()) {
return false;
}
// build rule data entries
for (const auto &ri_iter : s_RuleInfo) {
if (strcasecmp(ri_iter.name, "Invalid Rule") == 0) {
continue;
}
rule_data.push_back(ri_iter.name);
}
// build orphaned entries
for (auto &row : results) {
const auto &rd_iter = std::find(rule_data.begin(), rule_data.end(), row[0]);
if (rd_iter == rule_data.end()) {
orphaned_rule_entries.push_back(std::string(row[0]));
if (!quiet_update) {
Log(Logs::General,
Logs::Status,
"Rule '%s' no longer exists... Deleting orphaned entry from `rule_values` table...",
row[0]
);
}
}
}
if (orphaned_rule_entries.size()) {
return _UpdateRules(
db,
"All Rulesets",
-1,
std::vector<std::tuple<int, std::string, std::string, std::string>>(),
orphaned_rule_entries
);
}
else {
return true;
}
}
bool RuleManager::_UpdateRules(
Database *db,
const char *ruleset_name,
const int ruleset_id,
const std::vector<std::tuple<int, std::string, std::string, std::string>> &injected,
const std::vector<std::string> &orphaned
)
{
bool return_value = true;
if (!db) {
return false;
}
if (ruleset_name == nullptr) {
return false;
}
if (injected.size()) {
if (ruleset_id >= 0 && strcasecmp(ruleset_name, "All Rulesets") != 0) {
std::string query(
fmt::format(
"REPLACE INTO `rule_values`(`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES {}",
implode(
",",
std::pair<char, char>('(', ')'),
join_tuple(",", std::pair<char, char>('\'', '\''), injected)
)
)
);
if (!db->QueryDatabase(query).Success()) {
return_value = false;
}
else {
Log(Logs::General,
Logs::Status,
"%u New Command%s Added to ruleset '%s' (%i)",
injected.size(),
(injected.size() == 1 ? "" : "s"),
ruleset_name,
ruleset_id
);
}
}
else {
return_value = false;
}
}
if (orphaned.size()) {
std::string query;
if (ruleset_id < 0 && strcasecmp(ruleset_name, "All Rulesets") == 0) {
query = fmt::format(
"DELETE FROM `rule_values` WHERE `rule_name` IN ({})",
implode(",", std::pair<char, char>('\'', '\''), orphaned)
);
}
else if (ruleset_id >= 0 && strcasecmp(ruleset_name, "All Rulesets") != 0) {
query = fmt::format(
"DELETE FROM `rule_values` WHERE `ruleset_id` = '%i' AND `rule_name` IN ({})",
ruleset_id,
implode(",", std::pair<char, char>('\'', '\''), orphaned)
);
}
if (query.size() > 0) {
if (!db->QueryDatabase(query).Success()) {
return_value = false;
}
else {
Log(Logs::General,
Logs::Status,
"%u Orphaned Command%s Deleted from ruleset '%s' (%i)",
orphaned.size(),
(orphaned.size() == 1 ? "" : "s"),
ruleset_name,
ruleset_id
);
}
}
else {
return_value = false;
}
}
return return_value;
}
int RuleManager::GetRulesetID(Database *database, const char *ruleset_name) {

View File

@ -49,21 +49,21 @@ class RuleManager {
public:
//generate our rule enums:
typedef enum {
#define RULE_INT(cat, rule, default_value) \
#define RULE_INT(cat, rule, default_value, notes) \
Int__##rule,
#include "ruletypes.h"
_IntRuleCount
} IntType;
typedef enum {
#define RULE_REAL(cat, rule, default_value) \
#define RULE_REAL(cat, rule, default_value, notes) \
Real__##rule,
#include "ruletypes.h"
_RealRuleCount
} RealType;
typedef enum {
#define RULE_BOOL(cat, rule, default_value) \
#define RULE_BOOL(cat, rule, default_value, notes) \
Bool__##rule,
#include "ruletypes.h"
_BoolRuleCount
@ -113,6 +113,9 @@ public:
void ResetRules(bool reload = false);
bool LoadRules(Database *db, const char *ruleset = nullptr, bool reload = false);
void SaveRules(Database *db, const char *ruleset = nullptr);
bool UpdateChangedRules(Database *db, const char *ruleset_name, bool quiet_update = false);
bool UpdateInjectedRules(Database *db, const char *ruleset_name, bool quiet_update = false);
bool UpdateOrphanedRules(Database *db, bool quiet_update = false);
private:
RuleManager();
@ -137,8 +140,16 @@ private:
static bool _FindRule(const char *rule_name, RuleType &type_into, uint16 &index_into);
static const char *_GetRuleName(RuleType type, uint16 index);
static const std::string &_GetRuleNotes(RuleType type, uint16 index);
static int _FindOrCreateRuleset(Database *db, const char *ruleset);
void _SaveRule(Database *db, RuleType type, uint16 index);
bool _UpdateRules(
Database *db,
const char *ruleset_name,
const int ruleset_id,
const std::vector<std::tuple<int, std::string, std::string, std::string>> &injected,
const std::vector<std::string> &orphaned
);
static const char *s_categoryNames[];
typedef struct {
@ -146,6 +157,7 @@ private:
CategoryType category;
RuleType type;
uint16 rule_index; //index into its 'type' array
std::string notes;
} RuleInfo;
static const RuleInfo s_RuleInfo[];

File diff suppressed because it is too large Load Diff

View File

@ -1481,12 +1481,7 @@ bool SharedDatabase::UpdateCommandSettings(const std::vector<std::pair<std::stri
implode(
",",
std::pair<char, char>('(', ')'),
join_pair(
",",
std::pair<char, char>('\'', '\''),
std::pair<char, char>('\'', '\''),
injected
)
join_pair(",", std::pair<char, char>('\'', '\''), injected)
)
);

View File

@ -20,6 +20,7 @@
#include <string.h>
#include <vector>
#include <cstdarg>
#include <tuple>
#include "types.h"
@ -33,27 +34,27 @@ const std::string vStringFormat(const char* format, va_list args);
std::string implode(std::string glue, std::vector<std::string> src);
template <typename T>
std::string implode(std::string glue, std::pair<char, char> encapsulation, std::vector<T> src)
std::string implode(const std::string &glue, const std::pair<char, char> &encapsulation, const std::vector<T> &src)
{
if (src.empty()) {
return {};
}
std::ostringstream output;
std::ostringstream oss;
for (const T &src_iter : src) {
output << encapsulation.first << src_iter << encapsulation.second << glue;
oss << encapsulation.first << src_iter << encapsulation.second << glue;
}
std::string final_output = output.str();
final_output.resize(output.str().size() - glue.size());
return final_output;
std::string output(oss.str());
output.resize(output.size() - glue.size());
return output;
}
// this requires that #include<fmt/format.h> be included in whatever file the invocation is made from
// this requires that #include<fmt/format.h> be included in whatever code file the invocation is made from
template <typename T1, typename T2>
std::vector<std::string> join_pair(std::string glue, std::pair<char, char> first_encap, std::pair<char, char> second_encap, std::vector<std::pair<T1, T2>> src)
std::vector<std::string> join_pair(const std::string &glue, const std::pair<char, char> &encapsulation, const std::vector<std::pair<T1, T2>> &src)
{
if (src.empty()) {
return {};
@ -65,17 +66,55 @@ std::vector<std::string> join_pair(std::string glue, std::pair<char, char> first
output.push_back(
// There are issues with including <fmt/format.h> in a header file that result in compile
// failure. I'm not sure if this applies only within the same project or across projects.
// Since templates act similar to macros in regards to initialization, this call should be
// safe so long as the '#include<fmt/format.h>' rule above is observed.
// Since templates act similar to macros in regards to initialization, this definition
// should be safe so long as the '#include<fmt/format.h>' rule above is observed.
fmt::format(
"{}{}{}{}{}{}{}",
first_encap.first,
encapsulation.first,
src_iter.first,
first_encap.second,
encapsulation.second,
glue,
second_encap.first,
encapsulation.first,
src_iter.second,
second_encap.second
encapsulation.second
)
);
}
return output;
}
// this requires that #include<fmt/format.h> be included in whatever code file the invocation is made from
template <typename T1, typename T2, typename T3, typename T4>
std::vector<std::string> join_tuple(const std::string &glue, const std::pair<char, char> &encapsulation, const std::vector<std::tuple<T1, T2, T3, T4>> &src)
{
if (src.empty()) {
return {};
}
std::vector<std::string> output;
for (const std::tuple<T1, T2, T3, T4> &src_iter : src) {
output.push_back(
// note: see join_pair(...)
fmt::format(
"{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}",
encapsulation.first,
std::get<0>(src_iter),
encapsulation.second,
glue,
encapsulation.first,
std::get<1>(src_iter),
encapsulation.second,
glue,
encapsulation.first,
std::get<2>(src_iter),
encapsulation.second,
glue,
encapsulation.first,
std::get<3>(src_iter),
encapsulation.second
)
);
}

View File

@ -332,16 +332,28 @@ int main(int argc, char** argv) {
Log(Logs::General, Logs::World_Server, "Error: Could not load skill cap data. But ignoring");
Log(Logs::General, Logs::World_Server, "Loading guilds..");
guild_mgr.LoadGuilds();
//rules:
{
if (!RuleManager::Instance()->UpdateInjectedRules(&database, "default")) {
Log(Logs::General, Logs::World_Server, "Failed to process 'Injected Rules' for ruleset 'default' update operation.");
}
if (!RuleManager::Instance()->UpdateOrphanedRules(&database)) {
Log(Logs::General, Logs::World_Server, "Failed to process 'Orphaned Rules' update operation.");
}
std::string tmp;
if (database.GetVariable("RuleSet", tmp)) {
Log(Logs::General, Logs::World_Server, "Loading rule set '%s'", tmp.c_str());
if (!RuleManager::Instance()->LoadRules(&database, tmp.c_str(), false)) {
Log(Logs::General, Logs::World_Server, "Failed to load ruleset '%s', falling back to defaults.", tmp.c_str());
}
}
else {
if (!RuleManager::Instance()->LoadRules(&database, "default", false)) {
Log(Logs::General, Logs::World_Server, "No rule set configured, using default rules");
}
@ -349,18 +361,16 @@ int main(int argc, char** argv) {
Log(Logs::General, Logs::World_Server, "Loaded default rule set 'default'", tmp.c_str());
}
}
EQEmu::InitializeDynamicLookups();
Log(Logs::General, Logs::World_Server, "Initialized dynamic dictionary entries");
}
EQEmu::InitializeDynamicLookups();
Log(Logs::General, Logs::World_Server, "Initialized dynamic dictionary entries");
if (RuleB(World, ClearTempMerchantlist)) {
Log(Logs::General, Logs::World_Server, "Clearing temporary merchant lists..");
database.ClearMerchantTemp();
}
RuleManager::Instance()->SaveRules(&database);
Log(Logs::General, Logs::World_Server, "Loading EQ time of day..");
TimeOfDay_Struct eqTime;
time_t realtime;

View File

@ -65,12 +65,7 @@ bool BotDatabase::UpdateBotCommandSettings(const std::vector<std::pair<std::stri
implode(
",",
std::pair<char, char>('(', ')'),
join_pair(
",",
std::pair<char, char>('\'', '\''),
std::pair<char, char>('\'', '\''),
injected
)
join_pair(",", std::pair<char, char>('\'', '\''), injected)
)
);