diff --git a/configs/sql-init-scripts/mysql/clientprefs-mysql.sql b/configs/sql-init-scripts/mysql/clientprefs-mysql.sql index 08ce04b86..35f78ea64 100644 --- a/configs/sql-init-scripts/mysql/clientprefs-mysql.sql +++ b/configs/sql-init-scripts/mysql/clientprefs-mysql.sql @@ -3,6 +3,7 @@ CREATE TABLE sm_cookies id INTEGER unsigned NOT NULL auto_increment, name varchar(30) NOT NULL UNIQUE, description varchar(255), + access INTEGER, PRIMARY KEY (id) ); diff --git a/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sq3 b/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sq3 index 491062a19..04d300534 100644 Binary files a/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sq3 and b/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sq3 differ diff --git a/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sql b/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sql index 512bb3361..d16622f26 100644 --- a/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sql +++ b/configs/sql-init-scripts/sqlite/clientprefs-sqlite.sql @@ -2,7 +2,8 @@ CREATE TABLE sm_cookies ( id INTEGER PRIMARY KEY AUTOINCREMENT, name varchar(30) NOT NULL UNIQUE, - description varchar(255) + description varchar(255), + access INTEGER ); CREATE TABLE sm_cookie_cache diff --git a/extensions/clientprefs/Makefile b/extensions/clientprefs/Makefile index f10e334d9..7dfe73678 100644 --- a/extensions/clientprefs/Makefile +++ b/extensions/clientprefs/Makefile @@ -17,7 +17,7 @@ PROJECT = clientprefs #Uncomment for Metamod: Source enabled extension #USEMETA = true -OBJECTS = sdk/smsdk_ext.cpp extension.cpp query.cpp cookie.cpp natives.cpp +OBJECTS = sdk/smsdk_ext.cpp extension.cpp query.cpp cookie.cpp natives.cpp menus.cpp ############################################## ### CONFIGURE ANY OTHER FLAGS/OPTIONS HERE ### @@ -59,7 +59,8 @@ ifeq "$(USEMETA)" "true" -I$(METAMOD) -I$(METAMOD)/sourcehook -I$(METAMOD)/sourcemm -I$(SMSDK)/public \ -I$(SMSDK)/public/sourcepawn else - INCLUDE += -I. -I.. -Isdk -I$(SMSDK)/public -I$(SMSDK)/public/sourcepawn -I$(SOURCEMM16)/sourcehook + INCLUDE += -I. -I.. -Isdk -I$(SMSDK)/public -I$(SMSDK)/public/sourcepawn -I$(SOURCEMM16)/sourcehook \ + -I$(HL2PUB) -I$(HL2PUB)/engine -I$(HL2PUB)/tier0 -I$(HL2PUB)/tier1 endif LINK += -static-libgcc diff --git a/extensions/clientprefs/cookie.cpp b/extensions/clientprefs/cookie.cpp index d13e7a1e8..afc905e5e 100644 --- a/extensions/clientprefs/cookie.cpp +++ b/extensions/clientprefs/cookie.cpp @@ -30,6 +30,7 @@ */ #include "cookie.h" +#include "menus.h" #include "query.h" CookieManager g_CookieManager; @@ -42,7 +43,8 @@ CookieManager::CookieManager() statsLoaded[i] = false; } - cookiesLoadedForward = NULL; + cookieDataLoadedForward = NULL; + clientMenu = NULL; } CookieManager::~CookieManager(){} @@ -97,26 +99,45 @@ Cookie *CookieManager::FindCookie(const char *name) } -Cookie *CookieManager::CreateCookie(const char *name, const char *description) +Cookie *CookieManager::CreateCookie(const char *name, const char *description, CookieAccess access) { Cookie *pCookie = FindCookie(name); /* Check if cookie already exists */ if (pCookie != NULL) { + /* Update data fields to the provided values */ + strncpy(pCookie->description, description, MAX_DESC_LENGTH); + pCookie->description[MAX_DESC_LENGTH-1] = '\0'; + + pCookie->access = access; + return pCookie; } /* First time cookie - Create from scratch */ - pCookie = new Cookie(name, description); + pCookie = new Cookie(name, description, access); cookieTrie.insert(name, pCookie); cookieList.push_back(pCookie); + char quotedname[2 * MAX_NAME_LENGTH + 1]; + char quoteddesc[2 * MAX_DESC_LENGTH + 1]; + + g_ClientPrefs.Database->QuoteString(pCookie->name, quotedname, sizeof(quotedname), NULL); + g_ClientPrefs.Database->QuoteString(pCookie->description, quoteddesc, sizeof(quoteddesc), NULL); - /* Attempt to load cookie from the db and get its ID num */ - g_ClientPrefs.Query_InsertCookie->BindParamString(0, name, true); - g_ClientPrefs.Query_InsertCookie->BindParamString(1, description, true); - TQueryOp *op = new TQueryOp(g_ClientPrefs.Database, g_ClientPrefs.Query_InsertCookie, Query_InsertCookie, pCookie); + /* Attempt to insert cookie into the db and get its ID num */ + char query[300]; + if (driver == DRIVER_SQLITE) + { + UTIL_Format(query, sizeof(query), "INSERT OR IGNORE INTO sm_cookies(name, description, access) VALUES('%s', '%s', %i)", quotedname, quoteddesc, access); + } + else + { + UTIL_Format(query, sizeof(query), "INSERT IGNORE INTO sm_cookies(name, description, access) VALUES('%s', '%s', %i)", quotedname, quoteddesc, access); + } + + TQueryOp *op = new TQueryOp(g_ClientPrefs.Database, query, Query_InsertCookie, pCookie); dbi->AddToThreadQueue(op, PrioQueue_Normal); return pCookie; @@ -177,8 +198,10 @@ void CookieManager::OnClientAuthorized(int client, const char *authstring) { connected[client] = true; - g_ClientPrefs.Query_SelectData->BindParamString(0, authstring, true); - TQueryOp *op = new TQueryOp(g_ClientPrefs.Database, g_ClientPrefs.Query_SelectData, Query_SelectData, client); + char query[300]; + /* Assume that the authstring doesn't need to be quoted */ + UTIL_Format(query, sizeof(query), "SELECT sm_cookies.name, sm_cookie_cache.value, sm_cookies.description, sm_cookies.access FROM sm_cookies JOIN sm_cookie_cache ON sm_cookies.id = sm_cookie_cache.cookie_id WHERE player = '%s'", authstring); + TQueryOp *op = new TQueryOp(g_ClientPrefs.Database, query, Query_SelectData, client); dbi->AddToThreadQueue(op, PrioQueue_Normal); } @@ -220,18 +243,20 @@ void CookieManager::OnClientDisconnecting(int client) return; } - g_ClientPrefs.Query_InsertData->BindParamString(0, player->GetAuthString(), true); - g_ClientPrefs.Query_InsertData->BindParamInt(1, dbId, false); - g_ClientPrefs.Query_InsertData->BindParamString(2, current->value, true); - g_ClientPrefs.Query_InsertData->BindParamInt(3, time(NULL), false); + char quotedvalue[2 * MAX_VALUE_LENGTH + 1]; + g_ClientPrefs.Database->QuoteString(current->value, quotedvalue, sizeof(quotedvalue), NULL); - if (driver == DRIVER_MYSQL) + char query[300]; + if (driver == DRIVER_SQLITE) { - g_ClientPrefs.Query_InsertData->BindParamString(4, current->value, true); - g_ClientPrefs.Query_InsertData->BindParamInt(5, time(NULL), false); + UTIL_Format(query, sizeof(query), "INSERT OR REPLACE INTO sm_cookie_cache(player,cookie_id, value, timestamp) VALUES('%s', %i, '%s', %i)", player->GetAuthString(), dbId, quotedvalue, time(NULL)); + } + else + { + UTIL_Format(query, sizeof(query), "INSERT INTO sm_cookie_cache(player,cookie_id, value, timestamp) VALUES('%s', %i, '%s', %i) ON DUPLICATE KEY UPDATE value = '%s', timestamp = %i", player->GetAuthString(), dbId, quotedvalue, time(NULL), quotedvalue, time(NULL)); } - TQueryOp *op = new TQueryOp(g_ClientPrefs.Database, g_ClientPrefs.Query_InsertData, Query_InsertData, client); + TQueryOp *op = new TQueryOp(g_ClientPrefs.Database, query, Query_InsertData, client); dbi->AddToThreadQueue(op, PrioQueue_Normal); current->parent->data[client] = NULL; @@ -241,7 +266,7 @@ void CookieManager::OnClientDisconnecting(int client) } } -void CookieManager::ClientConnectCallback(int client, IPreparedQuery *data) +void CookieManager::ClientConnectCallback(int client, IQuery *data) { IResultSet *results = data->GetResultSet(); if (results == NULL) @@ -274,7 +299,10 @@ void CookieManager::ClientConnectCallback(int client, IPreparedQuery *data) const char *desc; row->GetString(2, &desc, NULL); - parent = CreateCookie(name, desc); + CookieAccess access = CookieAccess_Public; + row->GetInt(3, (int *)&access); + + parent = CreateCookie(name, desc, access); cookieTrie.insert(name, parent); cookieList.push_back(parent); } @@ -286,15 +314,47 @@ void CookieManager::ClientConnectCallback(int client, IPreparedQuery *data) } while (results->MoreRows()); + statsLoaded[client] = true; - cookiesLoadedForward->PushCell(client); - cookiesLoadedForward->Execute(NULL); + cookieDataLoadedForward->PushCell(client); + cookieDataLoadedForward->Execute(NULL); } void CookieManager::InsertCookieCallback(Cookie *pCookie, int dbId) { - pCookie->dbid = dbId; + if (dbId > 0) + { + pCookie->dbid = dbId; + return; + } + + char quotedname[2 * MAX_NAME_LENGTH + 1]; + g_ClientPrefs.Database->QuoteString(pCookie->name, quotedname, sizeof(quotedname), NULL); + + char query[300]; + UTIL_Format(query, sizeof(query), "SELECT id FROM sm_cookies WHERE name='%s'", quotedname); + + TQueryOp *op = new TQueryOp(g_ClientPrefs.Database, query, Query_SelectId, pCookie); + dbi->AddToThreadQueue(op, PrioQueue_Normal); +} + +void CookieManager::SelectIdCallback(Cookie *pCookie, IQuery *data) +{ + IResultSet *results = data->GetResultSet(); + if (results == NULL) + { + return; + } + + IResultRow *row = results->FetchRow(); + + if (row == NULL) + { + return; + } + + row->GetInt(0, &pCookie->dbid); } bool CookieManager::AreClientCookiesCached(int client) @@ -302,3 +362,51 @@ bool CookieManager::AreClientCookiesCached(int client) return statsLoaded[client]; } +void CookieManager::OnPluginDestroyed(IPlugin *plugin) +{ + SourceHook::List *pList; + + if (plugin->GetProperty("SettingsMenuItems", (void **)&pList, true)) + { + SourceHook::List::iterator p_iter = pList->begin(); + char *name; + + while (p_iter != pList->end()) + { + name = (char *)*p_iter; + + p_iter = pList->erase(p_iter); //remove from this plugins list + + ItemDrawInfo draw; + + for (unsigned int i=0; iGetItemCount(); i++) + { + const char *info = clientMenu->GetItemInfo(i, &draw); + + if (info == NULL) + { + continue; + } + + if (strcmp(draw.display, name) == 0) + { + ItemDrawInfo draw; + const char *info = clientMenu->GetItemInfo(i, &draw); + AutoMenuData *data = (AutoMenuData *)strtol(info, NULL, 16); + + if (data->handler->forward != NULL) + { + forwards->ReleaseForward(data->handler->forward); + } + delete data->handler; + delete data; + + clientMenu->RemoveItem(i); + break; + } + } + + delete name; + } + } +} diff --git a/extensions/clientprefs/cookie.h b/extensions/clientprefs/cookie.h index c9425108a..90bff36de 100644 --- a/extensions/clientprefs/cookie.h +++ b/extensions/clientprefs/cookie.h @@ -41,6 +41,13 @@ #define MAX_DESC_LENGTH 255 #define MAX_VALUE_LENGTH 100 +enum CookieAccess +{ + CookieAccess_Public, /**< Visible and Changeable by users */ + CookieAccess_Protected, /**< Read only to users */ + CookieAccess_Private, /**< Completely hidden cookie */ +}; + struct Cookie; struct CookieData @@ -58,13 +65,15 @@ struct CookieData struct Cookie { - Cookie(const char *name, const char *description) + Cookie(const char *name, const char *description, CookieAccess access) { strncpy(this->name, name, MAX_NAME_LENGTH); this->name[MAX_NAME_LENGTH-1] = '\0'; strncpy(this->description, description, MAX_DESC_LENGTH); this->description[MAX_DESC_LENGTH-1] = '\0'; + this->access = access; + dbid = -1; for (int i=0; i<=MAXCLIENTS; i++) @@ -88,10 +97,10 @@ struct Cookie char description[MAX_DESC_LENGTH]; int dbid; CookieData *data[MAXCLIENTS+1]; + CookieAccess access; }; - -class CookieManager : public IClientListener +class CookieManager : public IClientListener, public IPluginsListener { public: CookieManager(); @@ -105,18 +114,24 @@ public: void Unload(); - void ClientConnectCallback(int client, IPreparedQuery *data); + void ClientConnectCallback(int client, IQuery *data); void InsertCookieCallback(Cookie *pCookie, int dbId); + void SelectIdCallback(Cookie *pCookie, IQuery *data); Cookie *FindCookie(const char *name); - Cookie *CreateCookie(const char *name, const char *description); + Cookie *CreateCookie(const char *name, const char *description, CookieAccess access); bool AreClientCookiesCached(int client); - IForward *cookiesLoadedForward; + IForward *cookieDataLoadedForward; + + SourceHook::List cookieList; + + IBaseMenu *clientMenu; + + void OnPluginDestroyed(IPlugin *plugin); private: - SourceHook::List cookieList; KTrie cookieTrie; SourceHook::List clientData[MAXCLIENTS]; diff --git a/extensions/clientprefs/extension.cpp b/extensions/clientprefs/extension.cpp index 5599d34bc..023d9cac3 100644 --- a/extensions/clientprefs/extension.cpp +++ b/extensions/clientprefs/extension.cpp @@ -43,6 +43,9 @@ SMEXT_LINK(&g_ClientPrefs); HandleType_t g_CookieType = 0; CookieTypeHandler g_CookieTypeHandler; +HandleType_t g_CookieIterator = 0; +CookieIteratorHandler g_CookieIteratorHandler; + int driver = 0; @@ -100,44 +103,9 @@ bool ClientPrefs::SDK_OnLoad(char *error, size_t maxlength, bool late) return false; } - if (driver == DRIVER_SQLITE) - { - Query_InsertCookie = Database->PrepareQuery("INSERT OR IGNORE INTO sm_cookies(name, description) VALUES(?, ?)", error, maxlength); - } - else - { - Query_InsertCookie = Database->PrepareQuery("INSERT IGNORE INTO sm_cookies(name, description) VALUES(?, ?)", error, maxlength); - } - - if (Query_InsertCookie == NULL) - { - return false; - } - - Query_SelectData = Database->PrepareQuery("SELECT sm_cookies.name, sm_cookie_cache.value, sm_cookies.description FROM sm_cookies JOIN sm_cookie_cache ON sm_cookies.id = sm_cookie_cache.cookie_id WHERE player = ?", error, maxlength); - - if (Query_SelectData == NULL) - { - return false; - } - - if (driver == DRIVER_SQLITE) - { - Query_InsertData = Database->PrepareQuery("INSERT OR REPLACE INTO sm_cookie_cache(player,cookie_id, value, timestamp) VALUES(?, ?, ?, ?)", error, maxlength); - } - else - { - Query_InsertData = Database->PrepareQuery("INSERT INTO sm_cookie_cache(player,cookie_id, value, timestamp) VALUES(?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = ?, timestamp = ?", error, maxlength); - } - - if (Query_InsertData == NULL) - { - return false; - } - sharesys->AddNatives(myself, g_ClientPrefNatives); sharesys->RegisterLibrary(myself, "clientprefs"); - g_CookieManager.cookiesLoadedForward = forwards->CreateForward("OnClientCookiesLoaded", ET_Ignore, 1, NULL, Param_Cell); + g_CookieManager.cookieDataLoadedForward = forwards->CreateForward("OnClientCookiesCached", ET_Ignore, 1, NULL, Param_Cell); g_CookieType = handlesys->CreateType("Cookie", &g_CookieTypeHandler, @@ -147,6 +115,25 @@ bool ClientPrefs::SDK_OnLoad(char *error, size_t maxlength, bool late) myself->GetIdentity(), NULL); + g_CookieIterator = handlesys->CreateType("CookieIterator", + &g_CookieIteratorHandler, + 0, + NULL, + NULL, + myself->GetIdentity(), + NULL); + + IMenuStyle *style = menus->GetDefaultStyle(); + g_CookieManager.clientMenu = style->CreateMenu(&g_Handler, NULL); + g_CookieManager.clientMenu->SetDefaultTitle("Client Settings:"); + + plsys->AddPluginsListener(&g_CookieManager); + + phrases = translator->CreatePhraseCollection(); + phrases->AddPhraseFile("clientprefs.phrases"); + phrases->AddPhraseFile("common.phrases"); + + return true; } @@ -161,13 +148,78 @@ void ClientPrefs::SDK_OnUnload() g_CookieManager.Unload(); - Query_InsertCookie->Destroy(); - Query_InsertData->Destroy(); - Query_SelectData->Destroy(); - Database->Close(); - forwards->ReleaseForward(g_CookieManager.cookiesLoadedForward); + forwards->ReleaseForward(g_CookieManager.cookieDataLoadedForward); + + g_CookieManager.clientMenu->Destroy(); +} + +size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + size_t len = vsnprintf(buffer, maxlength, fmt, ap); + va_end(ap); + + if (len >= maxlength) + { + buffer[maxlength - 1] = '\0'; + return (maxlength - 1); + } + else + { + return len; + } +} + +bool Translate(char *buffer, + size_t maxlength, + const char *format, + unsigned int numparams, + size_t *pOutLength, + ...) +{ + va_list ap; + unsigned int i; + const char *fail_phrase; + void *params[MAX_TRANSLATE_PARAMS]; + + if (numparams > MAX_TRANSLATE_PARAMS) + { + assert(false); + return false; + } + + va_start(ap, pOutLength); + for (i = 0; i < numparams; i++) + { + params[i] = va_arg(ap, void *); + } + va_end(ap); + + if (!g_ClientPrefs.phrases->FormatString(buffer, + maxlength, + format, + params, + numparams, + pOutLength, + &fail_phrase)) + { + if (fail_phrase != NULL) + { + g_pSM->LogError(myself, "[SM] Could not find core phrase: %s", fail_phrase); + } + else + { + g_pSM->LogError(myself, "[SM] Unknown fatal error while translating a core phrase."); + } + + return false; + } + + return true; } + diff --git a/extensions/clientprefs/extension.h b/extensions/clientprefs/extension.h index 0f1ccc904..dea64b434 100644 --- a/extensions/clientprefs/extension.h +++ b/extensions/clientprefs/extension.h @@ -35,11 +35,16 @@ #define MAXCLIENTS 64 #include "smsdk_ext.h" +#include "sh_list.h" #include "cookie.h" +#include "menus.h" #define DRIVER_MYSQL 1 #define DRIVER_SQLITE 0 +#define MAX_TRANSLATE_PARAMS 32 + + /** * @brief Sample implementation of the SDK Extension. * Note: Uncomment one of the pre-defined virtual functions in order to use it. @@ -117,10 +122,7 @@ public: public: IDBDriver *Driver; IDatabase *Database; - - IPreparedQuery *Query_InsertCookie; - IPreparedQuery *Query_SelectData; - IPreparedQuery *Query_InsertData; + IPhraseCollection *phrases; }; class CookieTypeHandler : public IHandleTypeDispatch @@ -128,16 +130,37 @@ class CookieTypeHandler : public IHandleTypeDispatch public: void OnHandleDestroy(HandleType_t type, void *object) { - /* No delete needed since this Cookies are persistant */ + /* No delete needed since Cookies are persistant */ } }; +class CookieIteratorHandler : public IHandleTypeDispatch +{ +public: + void OnHandleDestroy(HandleType_t type, void *object) + { + delete (SourceHook::List::iterator *)object; + } +}; + +size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...); + extern sp_nativeinfo_t g_ClientPrefNatives[]; extern ClientPrefs g_ClientPrefs; extern HandleType_t g_CookieType; extern CookieTypeHandler g_CookieTypeHandler; +extern HandleType_t g_CookieIterator; +extern CookieIteratorHandler g_CookieIteratorHandler; + +bool Translate(char *buffer, + size_t maxlength, + const char *format, + unsigned int numparams, + size_t *pOutLength, + ...); + extern int driver; #endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ diff --git a/extensions/clientprefs/menus.cpp b/extensions/clientprefs/menus.cpp new file mode 100644 index 000000000..ca2e84015 --- /dev/null +++ b/extensions/clientprefs/menus.cpp @@ -0,0 +1,179 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Client Preferences Extension + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; 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, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include "menus.h" + +ClientMenuHandler g_Handler; +AutoMenuHandler g_AutoHandler; + +void ClientMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item) +{ + ItemDrawInfo draw; + + const char *info = menu->GetItemInfo(item, &draw); + + AutoMenuData *data = (AutoMenuData *)strtol(info, NULL, 16); + + if (data->handler->forward != NULL) + { + data->handler->forward->PushCell(client); + data->handler->forward->PushCell(CookieMenuAction_SelectOption); + data->handler->forward->PushCell(data->datavalue); + data->handler->forward->PushString(""); + data->handler->forward->PushCell(0); + data->handler->forward->Execute(NULL); + } + + if (!data->handler->isAutoMenu) + { + return; + } + + IBaseMenu *submenu = menus->GetDefaultStyle()->CreateMenu(&g_AutoHandler, NULL); + + char message[256]; + + Translate(message, sizeof(message), "%T:", 2, NULL, "Choose Option", &client); + submenu->SetDefaultTitle(message); + + if (data->type == CookieMenu_YesNo || data->type == CookieMenu_YesNo_Int) + { + Translate(message, sizeof(message), "%T", 2, NULL, "Yes", &client); + submenu->AppendItem(info, message); + + Translate(message, sizeof(message), "%T", 2, NULL, "No", &client); + submenu->AppendItem(info, message); + } + else if (data->type == CookieMenu_OnOff || data->type == CookieMenu_OnOff_Int) + { + Translate(message, sizeof(message), "%T", 2, NULL, "On", &client); + submenu->AppendItem(info, message); + + Translate(message, sizeof(message), "%T", 2, NULL, "Off", &client); + submenu->AppendItem(info, message); + } + + submenu->Display(client, 0, NULL); +} + +unsigned int ClientMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, + int client, + IMenuPanel *panel, + unsigned int item, + const ItemDrawInfo &dr) +{ + ItemDrawInfo draw; + + const char *info = menu->GetItemInfo(item, &draw); + + AutoMenuData *data = (AutoMenuData *)strtol(info, NULL, 16); + + if (data->handler->forward != NULL) + { + char buffer[100]; + UTIL_Format(buffer, sizeof(buffer), "%s", dr.display); + + data->handler->forward->PushCell(client); + data->handler->forward->PushCell(CookieMenuAction_DisplayOption); + data->handler->forward->PushCell(data->datavalue); + data->handler->forward->PushStringEx(buffer, sizeof(buffer), SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); + data->handler->forward->PushCell(sizeof(buffer)); + data->handler->forward->Execute(NULL); + + ItemDrawInfo newdraw(buffer, draw.style); + + return panel->DrawItem(newdraw); + } + + return 0; +} + +void AutoMenuHandler::OnMenuSelect(SourceMod::IBaseMenu *menu, int client, unsigned int item) +{ + ItemDrawInfo draw; + + const char *info = menu->GetItemInfo(item, &draw); + + AutoMenuData *data = (AutoMenuData *)strtol(info, NULL, 16); + + switch (data->type) + { + case CookieMenu_YesNo: + { + if (item == 0) + { + g_CookieManager.SetCookieValue(data->pCookie, client, "yes"); + } + else + { + g_CookieManager.SetCookieValue(data->pCookie, client, "no"); + } + break; + } + case CookieMenu_YesNo_Int: + case CookieMenu_OnOff_Int: + { + if (item == 0) + { + g_CookieManager.SetCookieValue(data->pCookie, client, "1"); + } + else + { + g_CookieManager.SetCookieValue(data->pCookie, client, "0"); + } + break; + } + case CookieMenu_OnOff: + { + if (item == 0) + { + g_CookieManager.SetCookieValue(data->pCookie, client, "on"); + } + else + { + g_CookieManager.SetCookieValue(data->pCookie, client, "off"); + } + break; + } + } + + char message[255]; + char *value; + g_CookieManager.GetCookieValue(data->pCookie, client, &value); + Translate(message, sizeof(message), "[SM] %T", 4, NULL, "Cookie Changed Value", &client, &(data->pCookie->name), value); + + gamehelpers->TextMsg(client, 3, message); +} + +void AutoMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason) +{ + menu->Destroy(true); +} diff --git a/extensions/clientprefs/menus.h b/extensions/clientprefs/menus.h new file mode 100644 index 000000000..cf404cadd --- /dev/null +++ b/extensions/clientprefs/menus.h @@ -0,0 +1,103 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Client Preferences Extension + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; 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, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_CLIENTPREFS_MENUS_H_ +#define _INCLUDE_SOURCEMOD_CLIENTPREFS_MENUS_H_ + +#include "extension.h" +#include "cookie.h" + +enum CookieMenuAction +{ + /** + * An option is being drawn for a menu. + * + * INPUT : Client index and any data if available. + * OUTPUT: Buffer for rendering, maxlength of buffer. + */ + CookieMenuAction_DisplayOption = 0, + + /** + * A menu option has been selected. + * + * INPUT : Client index and any data if available. + */ + CookieMenuAction_SelectOption = 1, +}; + +enum CookieMenu +{ + CookieMenu_YesNo, /**< Yes/No menu with "yes"/"no" results saved into the cookie */ + CookieMenu_YesNo_Int, /**< Yes/No menu with 1/0 saved into the cookie */ + CookieMenu_OnOff, /**< On/Off menu with "on"/"off" results saved into the cookie */ + CookieMenu_OnOff_Int, /**< On/Off menu with 1/0 saved into the cookie */ +}; + +struct ItemHandler +{ + bool isAutoMenu; + IChangeableForward *forward; + CookieMenu autoMenuType; +}; + +class ClientMenuHandler : public IMenuHandler +{ + void OnMenuSelect(IBaseMenu *menu, int client, unsigned int item); + unsigned int OnMenuDisplayItem(IBaseMenu *menu, + int client, + IMenuPanel *panel, + unsigned int item, + const ItemDrawInfo &dr); +}; + +class AutoMenuHandler : public IMenuHandler +{ + void OnMenuSelect(IBaseMenu *menu, int client, unsigned int item); + void OnMenuEnd(IBaseMenu *menu, MenuEndReason reason); +}; + +extern ClientMenuHandler g_Handler; +extern AutoMenuHandler g_AutoHandler; + +/* Something went wrong with the includes and made me do this */ +struct Cookie; +enum CookieMenu; +struct ItemHandler; + +struct AutoMenuData +{ + ItemHandler *handler; + Cookie *pCookie; + CookieMenu type; + cell_t datavalue; +}; + +#endif // _INCLUDE_SOURCEMOD_CLIENTPREFS_MENUS_H_ diff --git a/extensions/clientprefs/msvc9/clientprefs.vcproj b/extensions/clientprefs/msvc9/clientprefs.vcproj index f41582119..437c78bc1 100644 --- a/extensions/clientprefs/msvc9/clientprefs.vcproj +++ b/extensions/clientprefs/msvc9/clientprefs.vcproj @@ -675,6 +675,10 @@ RelativePath="..\extension.cpp" > + + @@ -697,6 +701,10 @@ RelativePath="..\extension.h" > + + diff --git a/extensions/clientprefs/natives.cpp b/extensions/clientprefs/natives.cpp index 8edab7bd5..d347015cc 100644 --- a/extensions/clientprefs/natives.cpp +++ b/extensions/clientprefs/natives.cpp @@ -31,6 +31,7 @@ #include "extension.h" #include "cookie.h" +#include "menus.h" cell_t RegClientPrefCookie(IPluginContext *pContext, const cell_t *params) { @@ -45,7 +46,7 @@ cell_t RegClientPrefCookie(IPluginContext *pContext, const cell_t *params) char *desc; pContext->LocalToString(params[2], &desc); - Cookie *pCookie = g_CookieManager.CreateCookie(name, desc); + Cookie *pCookie = g_CookieManager.CreateCookie(name, desc, (CookieAccess)params[3]); if (!pCookie) { @@ -153,12 +154,208 @@ cell_t AreClientCookiesCached(IPluginContext *pContext, const cell_t *params) return g_CookieManager.AreClientCookiesCached(client); } +cell_t GetCookieAccess(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = static_cast(params[1]); + HandleError err; + HandleSecurity sec; + + sec.pOwner = NULL; + sec.pIdentity = myself->GetIdentity(); + + Cookie *pCookie; + + if ((err = handlesys->ReadHandle(hndl, g_CookieType, &sec, (void **)&pCookie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Cookie handle %x (error %d)", hndl, err); + } + + return pCookie->access; +} + +static cell_t GetCookieIterator(IPluginContext *pContext, const cell_t *params) +{ + SourceHook::List::iterator *iter = new SourceHook::List::iterator; + *iter = g_CookieManager.cookieList.begin(); + + Handle_t hndl = handlesys->CreateHandle(g_CookieIterator, iter, pContext->GetIdentity(), myself->GetIdentity(), NULL); + if (hndl == BAD_HANDLE) + { + delete iter; + } + + return hndl; +} + +static cell_t ReadCookieIterator(IPluginContext *pContext, const cell_t *params) +{ + SourceHook::List::iterator *iter; + + Handle_t hndl = static_cast(params[1]); + HandleError err; + HandleSecurity sec; + + sec.pOwner = NULL; + sec.pIdentity = myself->GetIdentity(); + + if ((err = handlesys->ReadHandle(hndl, g_CookieIterator, &sec, (void **)&iter)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Cookie iterator handle %x (error %d)", hndl, err); + } + + if (*iter == g_CookieManager.cookieList.end()) + { + return 0; + } + + Cookie *pCookie = (Cookie *)**iter; + + (*iter)++; + + pContext->StringToLocalUTF8(params[2], params[3], pCookie->name, NULL); + pContext->StringToLocalUTF8(params[5], params[6], pCookie->description, NULL); + + cell_t *addr; + pContext->LocalToPhysAddr(params[4], &addr); + *addr = pCookie->access; + + return 1; +} + +cell_t ShowSettingsMenu(IPluginContext *pContext, const cell_t *params) +{ + char message[256]; + Translate(message, sizeof(message), "%T:", 2, NULL, "Client Settings", ¶ms[1]); + + g_CookieManager.clientMenu->SetDefaultTitle(message); + g_CookieManager.clientMenu->Display(params[1], 0, NULL); + + return 0; +} + +cell_t AddSettingsMenuItem(IPluginContext *pContext, const cell_t *params) +{ + char *display; + pContext->LocalToString(params[3], &display); + + /* Register a callback */ + ItemHandler *pItem = new ItemHandler; + pItem->isAutoMenu = false; + pItem->forward = forwards->CreateForwardEx(NULL, ET_Ignore, 5, NULL, Param_Cell, Param_Cell, Param_Cell, Param_String, Param_Cell); + + pItem->forward->AddFunction(pContext, static_cast(params[1])); + + char info[20]; + AutoMenuData *data = new AutoMenuData; + data->datavalue = params[2]; + data->handler = pItem; + UTIL_Format(info, sizeof(info), "%x", data); + + ItemDrawInfo draw(display, 0); + + g_CookieManager.clientMenu->AppendItem(info, draw); + + /* Track this in case the plugin unloads */ + + IPlugin *pPlugin = plsys->FindPluginByContext(pContext->GetContext()); + SourceHook::List *pList = NULL; + + if (!pPlugin->GetProperty("SettingsMenuItems", (void **)&pList, false) || !pList) + { + pList = new SourceHook::List; + pPlugin->SetProperty("SettingsMenuItems", pList); + } + + char *copyarray = new char[strlen(display)+1]; + UTIL_Format(copyarray, strlen(display)+1, "%s", display); + + pList->push_back(copyarray); + + return 0; +} + +cell_t AddSettingsPrefabMenuItem(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = static_cast(params[1]); + HandleError err; + HandleSecurity sec; + + sec.pOwner = NULL; + sec.pIdentity = myself->GetIdentity(); + + Cookie *pCookie; + + if ((err = handlesys->ReadHandle(hndl, g_CookieType, &sec, (void **)&pCookie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Cookie handle %x (error %d)", hndl, err); + } + + /* Register a callback */ + ItemHandler *pItem = new ItemHandler; + pItem->isAutoMenu = true; + pItem->autoMenuType = (CookieMenu)params[2]; + + + /* User passed a function id for a callback */ + if (params[4] != -1) + { + pItem->forward = forwards->CreateForwardEx(NULL, ET_Ignore, 5, NULL, Param_Cell, Param_Cell, Param_Cell, Param_String, Param_Cell); + pItem->forward->AddFunction(pContext, static_cast(params[4])); + } + else + { + pItem->forward = NULL; + } + + char *display; + pContext->LocalToString(params[3], &display); + + ItemDrawInfo draw(display, 0); + + char info[20]; + AutoMenuData *data = new AutoMenuData; + data->datavalue = params[5]; + data->pCookie = pCookie; + data->type = (CookieMenu)params[2]; + data->handler = pItem; + UTIL_Format(info, sizeof(info), "%x", data); + + g_CookieManager.clientMenu->AppendItem(info, draw); + + /* Track this in case the plugin unloads */ + + IPlugin *pPlugin = plsys->FindPluginByContext(pContext->GetContext()); + SourceHook::List *pList = NULL; + + if (!pPlugin->GetProperty("SettingsMenuItems", (void **)&pList, false) || !pList) + { + pList = new SourceHook::List; + pPlugin->SetProperty("SettingsMenuItems", pList); + } + + char *copyarray = new char[strlen(display)+1]; + UTIL_Format(copyarray, strlen(display)+1, "%s", display); + + pList->push_back(copyarray); + + return 0; +} + sp_nativeinfo_t g_ClientPrefNatives[] = { - {"RegClientPrefCookie", RegClientPrefCookie}, - {"FindClientPrefCookie", FindClientPrefCookie}, - {"SetClientPrefCookie", SetClientPrefCookie}, - {"GetClientPrefCookie", GetClientPrefCookie}, + {"RegClientCookie", RegClientPrefCookie}, + {"FindClientCookie", FindClientPrefCookie}, + {"SetClientCookie", SetClientPrefCookie}, + {"GetClientCookie", GetClientPrefCookie}, {"AreClientCookiesCached", AreClientCookiesCached}, + {"GetCookieAccess", GetCookieAccess}, + {"ReadCookieIterator", ReadCookieIterator}, + {"GetCookieIterator", GetCookieIterator}, + {"ShowCookieMenu", ShowSettingsMenu}, + {"SetCookieMenuItem", AddSettingsMenuItem}, + {"SetCookiePrefabMenu", AddSettingsPrefabMenuItem}, {NULL, NULL} }; diff --git a/extensions/clientprefs/query.cpp b/extensions/clientprefs/query.cpp index e9fb1eadf..a927b8eba 100644 --- a/extensions/clientprefs/query.cpp +++ b/extensions/clientprefs/query.cpp @@ -47,28 +47,34 @@ void TQueryOp::RunThinkPart() g_CookieManager.ClientConnectCallback(m_client, m_pQuery); break; case Query_InsertData: - g_pSM->LogMessage(myself, "Inserted data into table"); //No specific handling break; + case Query_SelectId: + g_CookieManager.SelectIdCallback(pCookie, m_pQuery); + break; default: break; } + + m_pQuery->Destroy(); } else { - g_pSM->LogError(myself,"Failed SQL Query Error: \"%s\"- Ref Id: %i, Client num: %i",error ,m_type, m_client); + g_pSM->LogError(myself,"Failed SQL Query, Error: \"%s\" (Query id %i - client %i)", error, m_type, m_client); } } void TQueryOp::RunThreadPart() { m_pDatabase->LockForFullAtomicOperation(); - if (!m_pQuery->Execute()) + m_pQuery = m_pDatabase->DoQuery(m_Query.c_str()); + + if (!m_pQuery) { - g_pSM->LogError(myself, m_pQuery->GetError()); + g_pSM->LogError(myself, "Failed SQL Query, Error: \"%s\" (Query id %i - client %i)", m_pDatabase->GetError(), m_type, m_client); } - m_insertId = m_pQuery->GetInsertID(); + m_insertId = g_ClientPrefs.Database->GetInsertID(); m_pDatabase->UnlockFromFullAtomicOperation(); } @@ -86,20 +92,20 @@ void TQueryOp::Destroy() delete this; } -TQueryOp::TQueryOp(IDatabase *db, IPreparedQuery *query, enum querytype type, int client) +TQueryOp::TQueryOp(IDatabase *db, const char *query, enum querytype type, int client) { m_pDatabase = db; - m_pQuery = query; + m_Query = query; m_type = type; m_client = client; m_pDatabase->IncReferenceCount(); } -TQueryOp::TQueryOp(IDatabase *db, IPreparedQuery *query, enum querytype type, Cookie *cookie) +TQueryOp::TQueryOp(IDatabase *db, const char *query, enum querytype type, Cookie *cookie) { m_pDatabase = db; - m_pQuery = query; + m_Query = query; m_type = type; pCookie = cookie; diff --git a/extensions/clientprefs/query.h b/extensions/clientprefs/query.h index 2aa87b48d..3dc733a54 100644 --- a/extensions/clientprefs/query.h +++ b/extensions/clientprefs/query.h @@ -41,13 +41,14 @@ enum querytype Query_InsertCookie = 0, Query_SelectData, Query_InsertData, + Query_SelectId, }; class TQueryOp : public IDBThreadOperation { public: - TQueryOp(IDatabase *db, IPreparedQuery *query, enum querytype type, int client); - TQueryOp(IDatabase *db, IPreparedQuery *query, enum querytype type, Cookie *cookie); + TQueryOp(IDatabase *db, const char *query, enum querytype type, int client); + TQueryOp(IDatabase *db, const char *query, enum querytype type, Cookie *cookie); ~TQueryOp() {} IDBDriver *GetDriver(); @@ -64,7 +65,8 @@ public: private: IDatabase *m_pDatabase; - IPreparedQuery *m_pQuery; + IQuery *m_pQuery; + SourceHook::String m_Query; char error[255]; enum querytype m_type; int m_client; diff --git a/extensions/clientprefs/sdk/smsdk_config.h b/extensions/clientprefs/sdk/smsdk_config.h index d85a467ea..e46ba2c70 100644 --- a/extensions/clientprefs/sdk/smsdk_config.h +++ b/extensions/clientprefs/sdk/smsdk_config.h @@ -40,7 +40,7 @@ #include "svn_version.h" /* Basic information exposed publicly */ -#define SMEXT_CONF_NAME "Client Preferences Extension" +#define SMEXT_CONF_NAME "Client Preferences" #define SMEXT_CONF_DESCRIPTION "Saves client preference settings" #define SMEXT_CONF_VERSION SVN_FULL_VERSION #define SMEXT_CONF_AUTHOR "AlliedModders" @@ -67,15 +67,16 @@ #define SMEXT_ENABLE_DBMANAGER //#define SMEXT_ENABLE_GAMECONF //#define SMEXT_ENABLE_MEMUTILS -//#define SMEXT_ENABLE_GAMEHELPERS +#define SMEXT_ENABLE_GAMEHELPERS //#define SMEXT_ENABLE_TIMERSYS //#define SMEXT_ENABLE_THREADER //#define SMEXT_ENABLE_LIBSYS -//#define SMEXT_ENABLE_MENUS +#define SMEXT_ENABLE_MENUS //#define SMEXT_ENABLE_ADTFACTORY -//#define SMEXT_ENABLE_PLUGINSYS +#define SMEXT_ENABLE_PLUGINSYS //#define SMEXT_ENABLE_ADMINSYS //#define SMEXT_ENABLE_TEXTPARSERS //#define SMEXT_ENABLE_USERMSGS +#define SMEXT_ENABLE_TRANSLATOR #endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ diff --git a/extensions/clientprefs/sdk/smsdk_ext.cpp b/extensions/clientprefs/sdk/smsdk_ext.cpp index 674e04e43..865f536ed 100644 --- a/extensions/clientprefs/sdk/smsdk_ext.cpp +++ b/extensions/clientprefs/sdk/smsdk_ext.cpp @@ -94,6 +94,9 @@ ITextParsers *textparsers = NULL; #if defined SMEXT_ENABLE_USERMSGS IUserMessages *usermsgs = NULL; #endif +#if defined SMEXT_ENABLE_TRANSLATOR +ITranslator *translator = NULL; +#endif /** Exports the main interface */ PLATFORM_EXTERN_C IExtensionInterface *GetSMExtAPI() @@ -179,6 +182,9 @@ bool SDKExtension::OnExtensionLoad(IExtension *me, IShareSys *sys, char *error, #if defined SMEXT_ENABLE_USERMSGS SM_GET_IFACE(USERMSGS, usermsgs); #endif +#if defined SMEXT_ENABLE_TRANSLATOR + SM_GET_IFACE(TRANSLATOR, translator); +#endif if (SDK_OnLoad(error, maxlength, late)) { diff --git a/extensions/clientprefs/sdk/smsdk_ext.h b/extensions/clientprefs/sdk/smsdk_ext.h index 0ee0dfa2f..115fc8008 100644 --- a/extensions/clientprefs/sdk/smsdk_ext.h +++ b/extensions/clientprefs/sdk/smsdk_ext.h @@ -88,6 +88,9 @@ #if defined SMEXT_ENABLE_USERMSGS #include #endif +#if defined SMEXT_ENABLE_TRANSLATOR +#include +#endif #if defined SMEXT_CONF_METAMOD #include @@ -283,6 +286,9 @@ extern IAdminSystem *adminsys; #if defined SMEXT_ENABLE_USERMSGS extern IUserMessages *usermsgs; #endif +#if defined SMEXT_ENABLE_TRANSLATOR +extern ITranslator *translator; +#endif #if defined SMEXT_CONF_METAMOD PLUGIN_GLOBALVARS(); diff --git a/plugins/clientprefs.sp b/plugins/clientprefs.sp new file mode 100644 index 000000000..a2d6574ae --- /dev/null +++ b/plugins/clientprefs.sp @@ -0,0 +1,150 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Map Management Plugin + * Provides all map related functionality, including map changing, map voting, + * and nextmap. + * + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; 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, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + + +#pragma semicolon 1 +#include +#include + +public Plugin:myinfo = +{ + name = "Client Preferences", + author = "AlliedModders LLC", + description = "Client peferences and settings menu", + version = SOURCEMOD_VERSION, + url = "http://www.sourcemod.net/" +}; + +public OnPluginStart() +{ + LoadTranslations("clientprefs.phrases"); + + RegConsoleCmd("sm_cookie", Command_Cookie, "sm_cookie [value]"); + RegConsoleCmd("sm_settings", Command_Settings); +} + +public Action:Command_Cookie(client, args) +{ + if (args == 0) + { + ReplyToCommand(client, "[SM] Usage: sm_cookie [value]"); + ReplyToCommand(client, "[SM] %t", "Printing Cookie List"); + + /* Show list of cookies */ + new Handle:iter = GetCookieIterator(); + + decl String:name[30]; + name[0] = '\0'; + decl String:description[255]; + description[0] = '\0'; + + PrintToConsole(client, "%t:", "Cookie List"); + + new CookieAccess:access; + + while (ReadCookieIterator(iter, + name, + sizeof(name), + access, + description, + sizeof(description)) != false) + { + if (access < CookieAccess_Private) + { + PrintToConsole(client, "%s - %s", name, description); + } + } + + return Plugin_Handled; + } + + decl String:name[30]; + name[0] = '\0'; + GetCmdArg(1, name, sizeof(name)); + + new Handle:cookie = FindClientCookie(name); + + if (cookie == INVALID_HANDLE) + { + ReplyToCommand(client, "[SM] %t", "Cookie not Found", name); + return Plugin_Handled; + } + + new CookieAccess:access = GetCookieAccess(cookie); + + if (access == CookieAccess_Private) + { + ReplyToCommand(client, "[SM] %t", "Cookie not Found", name); + return Plugin_Handled; + } + + decl String:value[100]; + value[0] = '\0'; + + if (args == 1) + { + GetClientCookie(client, cookie, value, sizeof(value)); + ReplyToCommand(client, "[SM] %t", "Cookie Value", name, value); + + return Plugin_Handled; + } + + if (access == CookieAccess_Protected) + { + ReplyToCommand(client, "[SM] %t", "Protected Cookie", name); + return Plugin_Handled; + } + + /* Set the new value of the cookie */ + + GetCmdArg(2, value, sizeof(value)); + + SetClientCookie(client, cookie, value); + ReplyToCommand(client, "[SM] %t", "Cookie Changed Value", name, value); + + return Plugin_Handled; +} + +public Action:Command_Settings(client, args) +{ + if (client == 0) + { + PrintToServer("%T", "No Console", LANG_SERVER); + return Plugin_Handled; + } + + ShowCookieMenu(client); + + return Plugin_Handled; +} \ No newline at end of file diff --git a/plugins/include/clientprefs.inc b/plugins/include/clientprefs.inc index 332a1a5e5..516eea1ba 100644 --- a/plugins/include/clientprefs.inc +++ b/plugins/include/clientprefs.inc @@ -35,15 +35,55 @@ #endif #define _clientprefs_included +/** + * Cookie access types for client viewing + */ +enum CookieAccess +{ + CookieAccess_Public, /**< Visible and Changeable by users */ + CookieAccess_Protected, /**< Read only to users */ + CookieAccess_Private, /**< Completely hidden cookie */ +}; + +/** + * Cookie Prefab menu types + */ +enum CookieMenu +{ + CookieMenu_YesNo, /**< Yes/No menu with "yes"/"no" results saved into the cookie */ + CookieMenu_YesNo_Int, /**< Yes/No menu with 1/0 saved into the cookie */ + CookieMenu_OnOff, /**< On/Off menu with "on"/"off" results saved into the cookie */ + CookieMenu_OnOff_Int, /**< On/Off menu with 1/0 saved into the cookie */ +}; + +enum CookieMenuAction +{ + /** + * An option is being drawn for a menu. + * + * INPUT : Client index and data if available. + * OUTPUT: Buffer for rendering, maxlength of buffer. + */ + CookieMenuAction_DisplayOption = 0, + + /** + * A menu option has been selected. + * + * INPUT : Client index and any data if available. + */ + CookieMenuAction_SelectOption = 1, +}; + /** * Creates a new Client preference cookie. * * @param name Name of the new preference cookie. * @param description Optional description of the preference cookie. + * @param access What CookieAccess level to assign to this cookie. * @return A handle to the newly created cookie. If the cookie already exists, a handle to it will still be returned. * @error Cookie name is blank. */ -native Handle:RegClientPrefCookie(const String:name[], const String:description[]); +native Handle:RegClientCookie(const String:name[], const String:description[], CookieAccess:access); /** * Searches for a Client preference cookie. @@ -51,7 +91,7 @@ native Handle:RegClientPrefCookie(const String:name[], const String:description[ * @param name Name of cookie to find. * @return A handle to the cookie if it is found. INVALID_HANDLE otherwise. */ -native Handle:FindClientPrefCookie(const String:name[]); +native Handle:FindClientCookie(const String:name[]); /** * Set the value of a Client preference cookie. @@ -62,7 +102,7 @@ native Handle:FindClientPrefCookie(const String:name[]); * @noreturn * @error Invalid cookie handle or invalid client index. */ -native SetClientPrefCookie(client, Handle:cookie, const String:value[]); +native SetClientCookie(client, Handle:cookie, const String:value[]); /** * Retrieve the value of a Client preference cookie. @@ -71,9 +111,10 @@ native SetClientPrefCookie(client, Handle:cookie, const String:value[]); * @param cookie Client preference cookie handle. * @param buffer Copyback buffer for value. * @param maxlen Maximum length of the buffer. + * @noreturn * @error Invalid cookie handle or invalid client index. */ -native GetClientPrefCookie(client, Handle:cookie, String:buffer[], maxlen); +native GetClientCookie(client, Handle:cookie, String:buffer[], maxlen); /** * Checks if a clients cookies have been loaded from the database. @@ -89,8 +130,89 @@ native bool:AreClientCookiesCached(client); * * @param client Client index. */ -forward OnClientCookiesLoaded(client); +forward OnClientCookiesCached(client); +/** + * Cookie Menu Callback prototype + * + * @param client Client index. + * @param action CookeMenuAction being performed. + * @param data Info data passed. + * @param buffer Outbut buffer. + * @param maxlen Max length of the output buffer. + */ +functag CookieMenuHandler public(client, CookieMenuAction:action, any:info, String:buffer[], maxlen); + +/** + * Add a new prefab item to the client cookie settings menu. + * + * Note: This handles everything automatically and does not require a callback + * + * @param cookie Client preference cookie handle. + * @param type A CookieMenu prefab menu type. + * @param display Text to show on the menu. + * @param handler Optional handler callback for translations and output on selection + * @param info Info data to pass to the callback. + * @noreturn + * @error Invalid cookie handle. + */ +native SetCookiePrefabMenu(Handle:cookie, CookieMenu:type, const String:display[], CookieMenuHandler:handler=CookieMenuHandler:-1, info=0); + +/** + * Adds a new item to the client cookie settings menu. + * + * Note: This only adds the top level menu item. You need to handle any submenus from the callback. + * + * @param handler A MenuHandler callback function. + * @param info Data to pass to the callback. + * @param display Text to show on the menu. + * @noreturn + * @error Invalid cookie handle. + */ +native SetCookieMenuItem(CookieMenuHandler:handler, any:info, const String:display[]); + +/** + * Displays the settings menu to a client. + * + * @param client Client index. + * @noreturn + */ +native ShowCookieMenu(client); + +/** + * Gets a cookie iterator. Must be freed with CloseHandle(). + * + * @return A new cookie iterator. + */ +native Handle:GetCookieIterator(); + +/** + * Reads a cookie iterator, then advances to the next cookie if any. + * + * @param iter Cookie iterator Handle. + * @param name Name buffer. + * @param nameLen Name buffer size. + * @param access Access level of the cookie. + * @param desc Cookie description buffer. + * @param descLen Cookie description buffer size. + * @param + * @return True on success, false if there are no more commands. + */ +native bool:ReadCookieIterator(Handle:iter, + String:name[], + nameLen, + &CookieAccess:access, + String:desc[]="", + descLen=0); + +/** + * Returns the access level of a cookie + * + * @param cookie Client preference cookie handle. + * @return CookieAccess access level. + * @error Invalid cookie handle. + */ +native CookieAccess:GetCookieAccess(Handle:cookie); /** * Do not edit below this line! diff --git a/plugins/testsuite/clientprefstest.sp b/plugins/testsuite/clientprefstest.sp index 14ada077c..f5554d66f 100644 --- a/plugins/testsuite/clientprefstest.sp +++ b/plugins/testsuite/clientprefstest.sp @@ -1,26 +1,59 @@ #include -#include "../include/clientprefs.inc" +#include new Handle:g_Cookie; +new Handle:g_Cookie2; +new Handle:g_Cookie3; +new Handle:g_Cookie4; +new Handle:g_Cookie5; public OnPluginStart() { - g_Cookie = RegClientPrefCookie("test-cookie", "A basic testing cookie"); + g_Cookie = RegClientCookie("test-cookie", "A basic testing cookie", CookieAccess_Public); + g_Cookie2 = RegClientCookie("test-cookie2", "A basic testing cookie", CookieAccess_Protected); + g_Cookie3 = RegClientCookie("test-cookie3", "A basic testing cookie", CookieAccess_Public); + g_Cookie4 = RegClientCookie("test-cookie4", "A basic testing cookie", CookieAccess_Private); + + g_Cookie5 = RegClientCookie("test-cookie5", "A basic testing cookie", CookieAccess_Public); + + SetCookiePrefabMenu(g_Cookie, CookieMenu_YesNo, "Cookie 1", CookieSelected, any:g_Cookie); + SetCookiePrefabMenu(g_Cookie2, CookieMenu_YesNo_Int, "Cookie 2"); + SetCookiePrefabMenu(g_Cookie3, CookieMenu_OnOff, "Cookie 3"); + SetCookiePrefabMenu(g_Cookie4, CookieMenu_OnOff_Int, "Cookie 4"); + + SetCookieMenuItem(CookieSelected, g_Cookie5, "Get Cookie 5 value"); } -public bool:OnClientConnect(client, String:rejectmsg[], maxlen) +public CookieSelected(client, CookieMenuAction:action, any:info, String:buffer[], maxlen) +{ + if (action == CookieMenuAction_DisplayOption) + { + PrintToChat(client, "About to draw item. Current text is : %s", buffer); + Format(buffer, maxlen, "HELLLLLLLLLLO"); + } + else + { + LogMessage("SELECTED!"); + + new String:value[100]; + GetClientCookie(client, info, value, sizeof(value)); + PrintToChat(client, "Value is : %s", value); + } +} + +public bool:OnClientConnect(client, String:rejectmsg[], maxlen) { LogMessage("Connect Cookie state: %s", AreClientCookiesCached(client) ? "YES" : "NO"); } -public OnClientCookiesLoaded(client) +public OnClientCookiesCached(client) { LogMessage("Loaded Cookie state: %s", AreClientCookiesCached(client) ? "YES" : "NO"); new String:hi[100]; - GetClientPrefCookie(client, g_Cookie, hi, sizeof(hi)); + GetClientCookie(client, g_Cookie, hi, sizeof(hi)); LogMessage("Test: %s",hi); - SetClientPrefCookie(client, g_Cookie, "somethingsomething"); - GetClientPrefCookie(client, g_Cookie, hi, sizeof(hi)); + SetClientCookie(client, g_Cookie, "somethingsomething"); + GetClientCookie(client, g_Cookie, hi, sizeof(hi)); LogMessage("Test: %s",hi); -} \ No newline at end of file +} diff --git a/translations/clientprefs.phrases.txt b/translations/clientprefs.phrases.txt new file mode 100644 index 000000000..edad910fc --- /dev/null +++ b/translations/clientprefs.phrases.txt @@ -0,0 +1,43 @@ +"Phrases" +{ + "Cookie List" + { + "en" "Sourcemod Cookie List" + } + "Printing Cookie List" + { + "en" "Printing cookie list to console" + } + "Cookie not Found" + { + "#format" "{1:s}" + "en" "Could not find cookie \"{1}\"" + } + "Cookie Value" + { + "#format" "{1:s},{2:s}" + "en" "{1}'s value is {2}" + } + "Protected Cookie" + { + "#format" "{1:s}" + "en" "Cannot change the value of protected cookie \"{1}\"" + } + "Cookie Changed Value" + { + "#format" "{1:s},{2:s}" + "en" "Changed the value of cookie \"{1}\" to \"{2}\"" + } + "No Console" + { + "en" "Cannot display settings menu to console" + } + "Choose Option" + { + "en" "Choose Option" + } + "Client Settings" + { + "en" "Client Settings" + } +} \ No newline at end of file diff --git a/translations/common.phrases.txt b/translations/common.phrases.txt index 9859db5f7..18aeebd79 100644 --- a/translations/common.phrases.txt +++ b/translations/common.phrases.txt @@ -249,4 +249,12 @@ "#format" "{1:s}" "en" "Kicked player '{1}'" } + "On" + { + "en" "On" + } + "Off" + { + "en" "Off" + } }