/* EQEMu: Everquest Server Emulator Copyright (C) 2001-2006 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY except by those people which sell it, which are required to give you total support for your newly bought product; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "rulesys.h" #include "logsys.h" #include "database.h" #include "string_util.h" #include #include /* FatherNitwit: Added new rules subsystem to allow game rules to be changed at runtime. more about this will come as time goes on. FatherNitwit: Added #rules command to manage rules data from in game. FatherNitwit: Renamed old #rules to #serverrules FatherNitwit: Moved max level into the rules system (Character:MaxLevel) Requred SQL: CREATE TABLE rule_sets ( ruleset_id TINYINT UNSIGNED NOT NULL auto_increment, name VARCHAR(255) NOT NULL, PRIMARY KEY(ruleset_id) ); INSERT INTO rule_sets VALUES(0, "default"); UPDATE rule_sets SET ruleset_id=0; CREATE TABLE rule_values ( ruleset_id TINYINT UNSIGNED NOT NULL, rule_name VARCHAR(64) NOT NULL, rule_value VARCHAR(10) NOT NULL, INDEX(ruleset_id), PRIMARY KEY(ruleset_id,rule_name) ); Commands: #rules: current -> lists current set name switch (set name) -> change set in the DB, but dont reload load (set name) -> load set into this zone without changing the world wload (set name) -> tell world and all zones to load this rule set store [set name] -> store the current rules in this zone to the set (or active if not specified) reset -> reset all rule values to their defaults. list [catname] set (cat) (rule) (value) values [catname] -> show the values of all rules in the specified category/ */ const char *RuleManager::s_categoryNames[_CatCount+1] = { #define RULE_CATEGORY(catname) \ #catname , #include "ruletypes.h" "InvalidCategory" }; 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 }, #include "ruletypes.h" #define RULE_REAL(cat, rule, default_value) \ { #cat ":" #rule, Category__##cat, RealRule, Real__##rule }, #include "ruletypes.h" #define RULE_BOOL(cat, rule, default_value) \ { #cat ":" #rule, Category__##cat, BoolRule, Bool__##rule }, #include "ruletypes.h" { "Invalid Rule", _CatCount, IntRule } }; RuleManager::RuleManager() : m_activeRuleset(0), m_activeName("default") { ResetRules(); } RuleManager::CategoryType RuleManager::FindCategory(const char *catname) { int r; for(r = 0; r < _CatCount; r++) { if(strcasecmp(catname, s_categoryNames[r]) == 0) return((CategoryType) r); } return(InvalidCategory); } bool RuleManager::ListRules(const char *catname, std::vector &into) { CategoryType cat = InvalidCategory; if(catname != nullptr) { cat = FindCategory(catname); if(cat == InvalidCategory) { Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Unable to find category '%s'", catname); return(false); } } int r; int rcount = CountRules(); for(r = 0; r < rcount; r++) { const RuleInfo &rule = s_RuleInfo[r]; if(catname == nullptr || cat == rule.category) { into.push_back(rule.name); } } return(true); } bool RuleManager::ListCategories(std::vector &into) { int r; for(r = 0; r < _CatCount; r++) { into.push_back(s_categoryNames[r]); } return(true); } bool RuleManager::GetRule(const char *rule_name, std::string &ret_val) { RuleType type; uint16 index; if (!_FindRule(rule_name, type, index)) return false; char tmp[255] = ""; switch(type) { case IntRule: sprintf(tmp, "%i", m_RuleIntValues[index]); break; case RealRule: sprintf(tmp, "%f", m_RuleRealValues[index]); break; case BoolRule: std::string tmp_val = m_RuleBoolValues[index] ? "true" : "false"; sprintf(tmp, "%s", tmp_val.c_str()); break; } ret_val = tmp; return true; } bool RuleManager::SetRule(const char *rule_name, const char *rule_value, Database *db, bool db_save) { if(rule_name == nullptr || rule_value == nullptr) return(false); RuleType type; uint16 index; if(!_FindRule(rule_name, type, index)) return(false); switch(type) { case IntRule: m_RuleIntValues [index] = atoi(rule_value); Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Set rule %s to value %d", rule_name, m_RuleIntValues[index]); break; case RealRule: m_RuleRealValues[index] = atof(rule_value); Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Set rule %s to value %.13f", rule_name, m_RuleRealValues[index]); break; case BoolRule: uint32 val = 0; if(!strcasecmp(rule_value, "on") || !strcasecmp(rule_value, "true") || !strcasecmp(rule_value, "yes") || !strcasecmp(rule_value, "enabled") || !strcmp(rule_value, "1")) val = 1; m_RuleBoolValues[index] = val; Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Set rule %s to value %s", rule_name, m_RuleBoolValues[index] == 1 ?"true":"false"); break; } if(db_save) _SaveRule(db, type, index); return(true); } void RuleManager::ResetRules() { Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Resetting running rules to default values"); #define RULE_INT(cat, rule, default_value) \ m_RuleIntValues[ Int__##rule ] = default_value; #define RULE_REAL(cat, rule, default_value) \ m_RuleRealValues[ Real__##rule ] = default_value; #define RULE_BOOL(cat, rule, default_value) \ m_RuleBoolValues[ Bool__##rule ] = default_value; #include "ruletypes.h" } bool RuleManager::_FindRule(const char *rule_name, RuleType &type_into, uint16 &index_into) { if(rule_name == nullptr) return(false); int r; int rcount = CountRules(); for(r = 0; r < rcount; r++) { const RuleInfo &rule = s_RuleInfo[r]; if(strcmp(rule_name, rule.name) == 0) { type_into = rule.type; index_into = rule.rule_index; return(true); } } Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Unable to find rule '%s'", rule_name); return(false); } //assumes index is valid! const char *RuleManager::_GetRuleName(RuleType type, uint16 index) { switch(type) { case IntRule: return(s_RuleInfo[index].name); case RealRule: return(s_RuleInfo[index+_IntRuleCount].name); case BoolRule: return(s_RuleInfo[index+_IntRuleCount+_RealRuleCount].name); } //should never happen return("InvalidRule??"); } void RuleManager::SaveRules(Database *db, const char *ruleset) { if(ruleset != nullptr) { //saving to a specific name if(m_activeName != ruleset) { //a new name... m_activeRuleset = _FindOrCreateRuleset(db, ruleset); if(m_activeRuleset == -1) { Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Unable to find or create rule set %s", ruleset); return; } m_activeName = ruleset; } Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Saving running rules into rule set %s (%d)", ruleset, m_activeRuleset); } else { Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Saving running rules into running rule set %s", m_activeName.c_str(), m_activeRuleset); } int r; for(r = 0; r < _IntRuleCount; r++) { _SaveRule(db, IntRule, r); } for(r = 0; r < _RealRuleCount; r++) { _SaveRule(db, RealRule, r); } for(r = 0; r < _BoolRuleCount; r++) { _SaveRule(db, BoolRule, r); } } bool RuleManager::LoadRules(Database *db, const char *ruleset) { int rsid = GetRulesetID(db, ruleset); if(rsid < 0) { Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Failed to find ruleset '%s' for load operation. Canceling.", ruleset); return(false); } Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Loading rule set '%s' (%d)", ruleset, rsid); m_activeRuleset = rsid; m_activeName = ruleset; std::string query = StringFormat("SELECT rule_name, rule_value FROM rule_values WHERE ruleset_id=%d", rsid); auto results = db->QueryDatabase(query); if (!results.Success()) { Log.DebugCategory(EQEmuLogSys::General, EQEmuLogSys::Error, "Error in LoadRules query %s: %s", query.c_str(), results.ErrorMessage().c_str()); return false; } for(auto row = results.begin(); row != results.end(); ++row) if(!SetRule(row[0], row[1], nullptr, false)) Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Unable to interpret rule record for %s", row[0]); return true; } void RuleManager::_SaveRule(Database *db, RuleType type, uint16 index) { char vstr[100]; switch(type) { case IntRule: sprintf(vstr, "%d", m_RuleIntValues[index]); break; case RealRule: sprintf(vstr, "%.13f", m_RuleRealValues[index]); break; case BoolRule: sprintf(vstr, "%s", m_RuleBoolValues[index]?"true":"false"); break; } std::string query = StringFormat("REPLACE INTO rule_values " "(ruleset_id, rule_name, rule_value) " " VALUES(%d, '%s', '%s')", m_activeRuleset, _GetRuleName(type, index), vstr); auto results = db->QueryDatabase(query); if (!results.Success()) Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Fauled to set rule in the database: %s: %s", query.c_str(), results.ErrorMessage().c_str()); } int RuleManager::GetRulesetID(Database *db, const char *rulesetname) { uint32 len = strlen(rulesetname); char* rst = new char[2*len+1]; db->DoEscapeString(rst, rulesetname, len); std::string query = StringFormat("SELECT ruleset_id FROM rule_sets WHERE name='%s'", rst); safe_delete_array(rst); auto results = db->QueryDatabase(query); if (!results.Success()) { Log.DebugCategory(EQEmuLogSys::General, EQEmuLogSys::Error, "Error in LoadRules query %s: %s", query.c_str(), results.ErrorMessage().c_str()); return -1; } if (results.RowCount() == 0) return -1; auto row = results.begin(); return atoi(row[0]); } int RuleManager::_FindOrCreateRuleset(Database *db, const char *ruleset) { int res = GetRulesetID(db, ruleset); if(res >= 0) return res; //found and existing one... uint32 len = strlen(ruleset); char* rst = new char[2*len+1]; db->DoEscapeString(rst, ruleset, len); std::string query = StringFormat("INSERT INTO rule_sets (ruleset_id, name) VALUES(0, '%s')", rst); safe_delete_array(rst); auto results = db->QueryDatabase(query); if (!results.Success()) { Log.DebugCategory(EQEmuLogSys::Detail, EQEmuLogSys::Rules, "Fauled to create rule set in the database: %s: %s", query.c_str(), results.ErrorMessage().c_str()); return -1; } return results.LastInsertedID(); } std::string RuleManager::GetRulesetName(Database *db, int id) { std::string query = StringFormat("SELECT name FROM rule_sets WHERE ruleset_id=%d", id); auto results = db->QueryDatabase(query); if (!results.Success()) { Log.DebugCategory(EQEmuLogSys::General, EQEmuLogSys::Error, "Error in LoadRules query %s: %s", query.c_str(), results.ErrorMessage().c_str()); return ""; } if (results.RowCount() == 0) return ""; auto row = results.begin(); return row[0]; } bool RuleManager::ListRulesets(Database *db, std::map &into) { //start out with the default set which is always present. into[0] = "default"; std::string query = "SELECT ruleset_id, name FROM rule_sets"; auto results = db->QueryDatabase(query); if (results.Success()) { Log.DebugCategory(EQEmuLogSys::General, EQEmuLogSys::Error, "Error in ListRulesets query %s: %s", query.c_str(), results.ErrorMessage().c_str()); return false; } for (auto row = results.begin(); row != results.end(); ++row) into[ atoi(row[0]) ] = row[1]; return true; } int32 RuleManager::GetIntRule(RuleManager::IntType t) const { return(m_RuleIntValues[t]); } float RuleManager::GetRealRule(RuleManager::RealType t) const { return(m_RuleRealValues[t]); } bool RuleManager::GetBoolRule(RuleManager::BoolType t) const { return (m_RuleBoolValues[t] == 1); }