diff --git a/amxmodx/CvarManager.cpp b/amxmodx/CvarManager.cpp index a1654c16..b5668007 100644 --- a/amxmodx/CvarManager.cpp +++ b/amxmodx/CvarManager.cpp @@ -50,30 +50,61 @@ bool Cvar_DirectSet_Custom(cvar_t* var, const char* value) } } - if (info->hooks.empty()) // No hooked cvars, nothing to call. + if (!info->hooks.empty()) { - return true; + int lastResult = 0; + + for (size_t i = 0; i < info->hooks.length(); ++i) + { + CvarHook* hook = info->hooks[i]; + + if (hook->forward->state == Forward::FSTATE_OK) // Our callback can be enable/disabled by natives. + { + int result = executeForwards(hook->forward->id, reinterpret_cast(var), var->string, value); + + if (result >= lastResult) + { + lastResult = result; + } + } + } + + if (lastResult) // A plugin wants to block the forward. + { + return false; + } } - int lastResult = 0; - int result; - - for (size_t i = 0; i < info->hooks.length(); ++i) + if (!info->binds.empty()) // Time to update our available binds. { - CvarPlugin* p = info->hooks[i]; - - if (p->forward->state == Forward::FSTATE_OK) // Our callback can be enable/disabled by natives. + for (size_t i = 0; i < info->binds.length(); ++i) { - result = executeForwards(p->forward->id, reinterpret_cast(var), var->string, value); + CvarBind* bind = info->binds[i]; - if (result >= lastResult) + switch (bind->type) { - lastResult = result; + case CvarBind::CvarType_Int: + { + *bind->varAddress = atoi(value); + break; + } + case CvarBind::CvarType_Float: + { + float fvalue = atof(value); + *bind->varAddress = amx_ftoc(fvalue); + break; + } + case CvarBind::CvarType_String: + { + set_amxstring_simple(bind->varAddress, value, bind->varLength); + break; + } } } } - return !!!lastResult; + // Nothing to block. + return true; } DETOUR_DECL_STATIC2(Cvar_DirectSet, void, struct cvar_s*, var, const char*, value) @@ -204,6 +235,9 @@ cvar_t* CvarManager::CreateCvar(const char* name, const char* value, const char* CVAR_DIRECTSET(var, value); } + // Detour is disabled on map change. + m_HookDetour->EnableDetour(); + return info->var; } @@ -212,6 +246,9 @@ CvarInfo* CvarManager::FindCvar(const char* name) cvar_t* var = nullptr; CvarInfo* info = nullptr; + // Detour is disabled on map change. + m_HookDetour->EnableDetour(); + // Do we have already cvar in cache? if (CacheLookup(name, &info)) { @@ -293,7 +330,7 @@ Forward* CvarManager::HookCvarChange(cvar_t* var, AMX* amx, cell param, const ch m_HookDetour->EnableDetour(); Forward* forward = new Forward(forwardId, *callback); - info->hooks.append(new CvarPlugin(g_plugins.findPlugin(amx)->getId(), forward)); + info->hooks.append(new CvarHook(g_plugins.findPlugin(amx)->getId(), forward)); return forward; } @@ -332,11 +369,17 @@ void CvarManager::OnPluginUnloaded() // Clear only plugin hooks list. for (CvarsList::iterator cvar = m_Cvars.begin(); cvar != m_Cvars.end(); cvar++) { + for (size_t i = 0; i < (*cvar)->binds.length(); ++i) + { + delete (*cvar)->binds[i]; + } + for (size_t i = 0; i < (*cvar)->hooks.length(); ++i) { delete (*cvar)->hooks[i]; } + (*cvar)->binds.clear(); (*cvar)->hooks.clear(); } @@ -350,6 +393,11 @@ void CvarManager::OnAmxxShutdown() // Free everything. for (CvarsList::iterator cvar = m_Cvars.begin(); cvar != m_Cvars.end(); cvar = m_Cvars.erase(cvar)) { + for (size_t i = 0; i < (*cvar)->binds.length(); ++i) + { + delete (*cvar)->binds[i]; + } + for (size_t i = 0; i < (*cvar)->hooks.length(); ++i) { delete (*cvar)->hooks[i]; diff --git a/amxmodx/CvarManager.h b/amxmodx/CvarManager.h index 68012f8e..f6c250a5 100644 --- a/amxmodx/CvarManager.h +++ b/amxmodx/CvarManager.h @@ -45,28 +45,51 @@ struct Forward ke::AString callback; }; -struct CvarPlugin +struct CvarHook { - CvarPlugin(int id, Forward* fwd) : pluginId(id), forward(fwd) {}; - CvarPlugin(int id) : pluginId(id), forward(new Forward()) {}; + CvarHook(int id, Forward* fwd) : pluginId(id), forward(fwd) {}; + CvarHook(int id) : pluginId(id), forward(new Forward()) {}; int pluginId; ke::AutoPtr forward; }; -typedef ke::Vector CvarsHook; +struct CvarBind +{ + enum CvarType + { + CvarType_Int, + CvarType_Float, + CvarType_String, + }; + + CvarBind(int id_, CvarType type_, cell* varAddress_, size_t varLength_) + : + pluginId(id_), type(type_), varAddress(varAddress_), varLength(varLength_) {}; + + int pluginId; + CvarType type; + cell* varAddress; + size_t varLength; +}; + +typedef ke::Vector CvarsHook; +typedef ke::Vector CvarsBind; struct CvarInfo : public ke::InlineListNode { - CvarInfo(const char* name_, const char* helpText, bool hasMin_, float min_, bool hasMax_, float max_, + CvarInfo(const char* name_, const char* helpText, + bool hasMin_, float min_, bool hasMax_, float max_, const char* plugin_, int pluginId_) : - name(name_), description(helpText), hasMin(hasMin_), minVal(min_), hasMax(hasMax_), maxVal(max_), + name(name_), description(helpText), + hasMin(hasMin_), minVal(min_), hasMax(hasMax_), maxVal(max_), plugin(plugin_), pluginId(pluginId_) {}; CvarInfo(const char* name_) : - name(name_), defaultval(""), description(""), hasMin(false), minVal(0), hasMax(false), maxVal(0), + name(name_), defaultval(""), description(""), + hasMin(false), minVal(0), hasMax(false), maxVal(0), plugin(""), pluginId(-1), amxmodx(false) {}; cvar_t* var; @@ -81,6 +104,7 @@ struct CvarInfo : public ke::InlineListNode ke::AString plugin; int pluginId; + CvarsBind binds; CvarsHook hooks; bool amxmodx; diff --git a/amxmodx/amxmodx.h b/amxmodx/amxmodx.h index dcc0ed24..2596a1ee 100755 --- a/amxmodx/amxmodx.h +++ b/amxmodx/amxmodx.h @@ -282,6 +282,7 @@ int amxstring_len(cell* cstr); int load_amxscript(AMX* amx, void** program, const char* path, char error[64], int debug); int set_amxnatives(AMX* amx, char error[64]); int set_amxstring(AMX *amx, cell amx_addr, const char *source, int max); +int set_amxstring_simple(cell *dest, const char *source, int max); template int set_amxstring_utf8(AMX *amx, cell amx_addr, const T *source, size_t sourcelen, size_t maxlen); int set_amxstring_utf8_char(AMX *amx, cell amx_addr, const char *source, size_t sourcelen, size_t maxlen); int set_amxstring_utf8_cell(AMX *amx, cell amx_addr, const cell *source, size_t sourcelen, size_t maxlen); diff --git a/amxmodx/cvars.cpp b/amxmodx/cvars.cpp index d77bba29..acd2ffd4 100644 --- a/amxmodx/cvars.cpp +++ b/amxmodx/cvars.cpp @@ -340,6 +340,103 @@ static cell AMX_NATIVE_CALL get_pcvar_bounds(AMX *amx, cell *params) return hasBound; } +bool bind_pcvar(CvarInfo* info, CvarBind::CvarType type, AMX* amx, cell varofs, size_t varlen = 0) +{ + if (varofs > amx->hlw) // If variable address is not inside global area, we can't bind it. + { + LogError(amx, AMX_ERR_NATIVE, "A global variable must be provided"); + return false; + } + + int pluginId = g_plugins.findPluginFast(amx)->getId(); + cell* address = get_amxaddr(amx, varofs); + + // To avoid unexpected behavior, probably better to error such situations. + for (size_t i = 0; i < info->binds.length(); ++i) + { + CvarBind* bind = info->binds[i]; + + if (bind->pluginId == pluginId) + { + if (bind->varAddress == address) + { + LogError(amx, AMX_ERR_NATIVE, "A same variable can't be binded with several cvars"); + } + else + { + LogError(amx, AMX_ERR_NATIVE, "A cvar can't be binded with several variables"); + } + + return false; + } + } + + CvarBind* bind = new CvarBind(pluginId, type, get_amxaddr(amx, varofs), varlen); + + info->binds.append(bind); + + // Update right away variable with current cvar value. + switch (type) + { + case CvarBind::CvarType_Int: + *bind->varAddress = atoi(info->var->string); + break; + case CvarBind::CvarType_Float: + *bind->varAddress = amx_ftoc(info->var->value); + break; + case CvarBind::CvarType_String: + set_amxstring_simple(bind->varAddress, info->var->string, bind->varLength); + break; + } + + return true; +} + +// bind_pcvar_float(pcvar, &Float:var) +static cell AMX_NATIVE_CALL bind_pcvar_float(AMX *amx, cell *params) +{ + cvar_t *ptr = reinterpret_cast(params[1]); + CvarInfo* info = nullptr; + + if (!ptr || !(info = g_CvarManager.FindCvar(ptr->name))) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid CVAR pointer"); + return 0; + } + + return bind_pcvar(info, CvarBind::CvarType_Float, amx, params[2]); +} + +// bind_pcvar_num(pcvar, &any:var) +static cell AMX_NATIVE_CALL bind_pcvar_num(AMX *amx, cell *params) +{ + cvar_t *ptr = reinterpret_cast(params[1]); + CvarInfo* info = nullptr; + + if (!ptr || !(info = g_CvarManager.FindCvar(ptr->name))) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid CVAR pointer"); + return 0; + } + + return bind_pcvar(info, CvarBind::CvarType_Int, amx, params[2]); +} + +// bind_pcvar_string(pcvar, any:var[], varlen) +static cell AMX_NATIVE_CALL bind_pcvar_string(AMX *amx, cell *params) +{ + cvar_t *ptr = reinterpret_cast(params[1]); + CvarInfo* info = nullptr; + + if (!ptr || !(info = g_CvarManager.FindCvar(ptr->name))) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid CVAR pointer"); + return 0; + } + + return bind_pcvar(info, CvarBind::CvarType_String, amx, params[2], params[3]); +} + // set_pcvar_flags(pcvar, flags) static cell AMX_NATIVE_CALL set_pcvar_flags(AMX *amx, cell *params) { @@ -613,6 +710,10 @@ AMX_NATIVE_INFO g_CvarNatives[] = {"remove_cvar_flags", remove_cvar_flags}, + {"bind_pcvar_float", bind_pcvar_float}, + {"bind_pcvar_num", bind_pcvar_num}, + {"bind_pcvar_string", bind_pcvar_string}, + {"get_plugins_cvar", get_plugins_cvar}, {"get_plugins_cvarsnum", get_plugins_cvarsnum}, diff --git a/amxmodx/string.cpp b/amxmodx/string.cpp index 877d54cb..e9ca20f7 100755 --- a/amxmodx/string.cpp +++ b/amxmodx/string.cpp @@ -65,6 +65,20 @@ cell* get_amxaddr(AMX *amx, cell amx_addr) return (cell *)(amx->base + (int)(((AMX_HEADER *)amx->base)->dat + amx_addr)); } +int set_amxstring_simple(cell *dest, const char *source, int max) +{ + cell* start = dest; + + while (max-- && *source) + { + *dest++ = (unsigned char)*source++; + } + + *dest = 0; + + return dest - start; +} + int set_amxstring(AMX *amx, cell amx_addr, const char *source, int max) { register cell* dest = (cell *)(amx->base + (int)(((AMX_HEADER *)amx->base)->dat + amx_addr)); diff --git a/plugins/include/cvars.inc b/plugins/include/cvars.inc index 8498d16d..68a62246 100644 --- a/plugins/include/cvars.inc +++ b/plugins/include/cvars.inc @@ -89,7 +89,7 @@ native cvar_exists(const cvar[]); native get_cvar_pointer(const cvar[]); /** - * Creates a hook for when a console variable's value is changed. + * Creates a hook for when a cvar's value is changed. * * @note Callback will be called in the following manner: * @@ -394,6 +394,40 @@ native bool:get_pcvar_bounds(pcvar, CvarBounds:type, &Float:value); */ native set_pcvar_bounds(pcvar, CvarBounds:type, bool:set, Float:value = 0.0); +/** + * Binds a cvar to a global integer variable. + * This means that variable will be automagically updated on cvar's value change. + * + * @param pcvar Pointer to cvar + * @param var Global variable to update to + * + * @error Invalid cvar pointer, invalid provided variable or cvar/variable already binded. + */ +native bind_pcvar_num(pcvar, &any:var); + +/** + * Binds a cvar to a global float variable. + * This means that variable will be automagically updated on cvar's value change. + * + * @param pcvar Pointer to cvar + * @param var Global variable to update to + * + * @error Invalid cvar pointer, invalid provided variable or cvar/variable already binded. + */ +native bind_pcvar_float(pcvar, &Float:var); + +/** + * Binds a cvar to a global string variable. + * This means that variable will be automagically updated on cvar's value change. + * + * @param pcvar Pointer to cvar + * @param var Global variable to update to + * @param varlen Maximum length of string buffer + * + * @error Invalid cvar pointer, invalid provided variable or cvar/variable already binded. + */ +native bind_pcvar_string(pcvar, any:var[], varlen); + /** * Returns the number of plugin-registered cvars. *