Cvars: Add bind_pcvar_num/float/string
This commit is contained in:
		| @@ -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<cvar_t*>(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<cvar_t*>(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]; | ||||
|   | ||||
| @@ -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> forward; | ||||
| }; | ||||
|  | ||||
| typedef ke::Vector<CvarPlugin*> 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<CvarHook*> CvarsHook; | ||||
| typedef ke::Vector<CvarBind*>   CvarsBind; | ||||
|  | ||||
| struct CvarInfo : public ke::InlineListNode<CvarInfo> | ||||
| { | ||||
| 	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<CvarInfo> | ||||
| 	ke::AString  plugin; | ||||
| 	int          pluginId; | ||||
|  | ||||
| 	CvarsBind    binds; | ||||
| 	CvarsHook    hooks; | ||||
| 	bool         amxmodx; | ||||
|  | ||||
|   | ||||
| @@ -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 <typename T> 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); | ||||
|   | ||||
| @@ -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<cvar_t *>(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<cvar_t *>(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<cvar_t *>(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}, | ||||
| 	 | ||||
|   | ||||
| @@ -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)); | ||||
|   | ||||
| @@ -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. | ||||
|  * | ||||
|   | ||||
		Reference in New Issue
	
	Block a user