From 3f49d292740131df38c6960eb1b5d3ddcf4c9f1b Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 23 Nov 2007 05:10:33 +0000 Subject: [PATCH] merged trunk back into 1.1.0 branch for "safety" --HG-- branch : sourcemod-1.1.0 extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/branches/sourcemod-1.1.0%401721 --- configs/admins_simple.ini | 2 +- configs/cfg/sourcemod.cfg | 7 - core/CellArray.h | 7 + core/CellRecipientFilter.h | 4 +- core/HalfLife2.cpp | 7 +- core/Logger.cpp | 4 +- core/MenuStyle_Radio.cpp | 136 +- core/MenuStyle_Radio.h | 22 +- core/PlayerManager.cpp | 46 +- core/PlayerManager.h | 6 +- core/TimerSys.cpp | 12 +- core/TimerSys.h | 4 +- core/UserMessages.cpp | 2 +- core/UserMessages.h | 2 +- core/sm_srvcmds.cpp | 57 +- core/sm_stringutil.cpp | 210 ++- core/smn_keyvalues.cpp | 59 + core/smn_menus.cpp | 69 + core/smn_player.cpp | 2 +- core/smn_sorting.cpp | 137 ++ core/sourcemod.cpp | 4 +- core/sourcemod.h | 2 +- core/systems/ExtensionSys.cpp | 85 +- core/systems/ExtensionSys.h | 12 + core/systems/HandleSys.cpp | 8 +- core/systems/HandleSys.h | 4 +- core/systems/PluginSys.cpp | 12 + core/systems/PluginSys.h | 1 + core/systems/ShareSys.cpp | 5 + core/systems/ShareSys.h | 1 + extensions/cstrike/Makefile.ep1 | 2 +- extensions/cstrike/Makefile.ep2 | 2 +- extensions/cstrike/Makefile.orig | 2 +- extensions/cstrike/extension.cpp | 6 + extensions/cstrike/extension.h | 2 + extensions/cstrike/natives.cpp | 45 + extensions/cstrike/sdk/smsdk_config.h | 1 + extensions/cstrike/sdk/smsdk_ext.cpp | 6 + extensions/cstrike/sdk/smsdk_ext.h | 6 + extensions/topmenus/TopMenu.cpp | 2224 +++++++++++++------------ extensions/topmenus/TopMenu.h | 357 ++-- extensions/topmenus/smn_topmenus.cpp | 741 ++++---- gamedata/core.games.txt | 26 + plugins/basechat.sp | 22 +- plugins/basecomm/gag.sp | 2 +- plugins/basefuncommands/burn.sp | 2 +- plugins/basefuncommands/slap.sp | 2 +- plugins/basefuncommands/slay.sp | 2 +- plugins/basefunvotes/voteburn.sp | 2 +- plugins/basefunvotes/voteslay.sp | 2 +- plugins/basevotes.sp | 22 +- plugins/basevotes/votemap.sp | 2 +- plugins/include/adt_array.inc | 10 +- plugins/include/keyvalues.inc | 19 + plugins/include/menus.inc | 20 + plugins/include/sorting.inc | 45 + plugins/include/topmenus.inc | 567 ++++--- plugins/mapchooser.sp | 15 +- plugins/reservedslots.sp | 80 +- plugins/rockthevote.sp | 73 +- plugins/testsuite/sorttest.sp | 147 +- public/IAdminSystem.h | 4 +- public/IDBDriver.h | 9 +- public/IExtensionSys.h | 6 +- public/IMemoryUtils.h | 5 + public/IMenuManager.h | 7 +- public/IPlayerHelpers.h | 73 +- public/IShareSys.h | 23 + public/ISourceMod.h | 19 +- public/ITextParsers.h | 3 +- public/IUserMessages.h | 2 +- public/extensions/ITopMenus.h | 10 +- public/sample_ext/sdk/smsdk_config.h | 1 + public/sample_ext/sdk/smsdk_ext.cpp | 6 + public/sample_ext/sdk/smsdk_ext.h | 6 + 75 files changed, 3382 insertions(+), 2175 deletions(-) diff --git a/configs/admins_simple.ini b/configs/admins_simple.ini index 40a9b70d..9c88bbe6 100644 --- a/configs/admins_simple.ini +++ b/configs/admins_simple.ini @@ -38,7 +38,7 @@ // Examples: (do not put // in front of real lines, as // means 'comment') // // "STEAM_0:1:16" "bce" //kick, ban, slay for this steam ID, no immunity -// "127.0.0.1" "99:z" //all permissions for this ip, immunity value is 99 +// "!127.0.0.1" "99:z" //all permissions for this ip, immunity value is 99 // "BAILOPAN" "abc" "Gab3n" //name BAILOPAN, password "Gab3n": gets reservation, kick, ban // //////////////////////////////// diff --git a/configs/cfg/sourcemod.cfg b/configs/cfg/sourcemod.cfg index ea9e61a4..b98133a6 100644 --- a/configs/cfg/sourcemod.cfg +++ b/configs/cfg/sourcemod.cfg @@ -93,13 +93,6 @@ sm_hide_slots 0 // Default: 1 sm_chat_mode 1 -// Specifies whether or not non-admins can send private messages to each other -// using say @@ . Valid values are 0 (disabled) or 1 (enabled) -// -- -// Required: basechat.smx -// Default: 1 -sm_psay_mode 0 - // Specifies whether or not "timeleft" will automaticly be triggered every // x seconds. Valid values are 0 (disabled) to 1800 seconds. // -- diff --git a/core/CellArray.h b/core/CellArray.h index 39e9197b..c9ee2831 100644 --- a/core/CellArray.h +++ b/core/CellArray.h @@ -32,6 +32,8 @@ #include #include +extern HandleType_t htCellArray; + class CellArray { public: @@ -149,6 +151,11 @@ public: return array; } + cell_t *base() + { + return m_Data; + } + private: bool GrowIfNeeded(size_t count) { diff --git a/core/CellRecipientFilter.h b/core/CellRecipientFilter.h index b3a5a1f9..30070553 100644 --- a/core/CellRecipientFilter.h +++ b/core/CellRecipientFilter.h @@ -46,7 +46,7 @@ public: //IRecipientFilter int GetRecipientCount() const; int GetRecipientIndex(int slot) const; public: - void Initialize(cell_t *ptr, size_t count); + void Initialize(const cell_t *ptr, size_t count); void SetToReliable(bool isreliable); void SetToInit(bool isinitmsg); void Reset(); @@ -98,7 +98,7 @@ inline void CellRecipientFilter::SetToReliable(bool isreliable) m_IsReliable = isreliable; } -inline void CellRecipientFilter::Initialize(cell_t *ptr, size_t count) +inline void CellRecipientFilter::Initialize(const cell_t *ptr, size_t count) { memcpy(m_Players, ptr, count * sizeof(cell_t)); m_Size = count; diff --git a/core/HalfLife2.cpp b/core/HalfLife2.cpp index 993a0e98..6aec953e 100644 --- a/core/HalfLife2.cpp +++ b/core/HalfLife2.cpp @@ -35,6 +35,7 @@ #include "UserMessages.h" #include "PlayerManager.h" #include "sm_stringutil.h" +#include "GameConfigs.h" #include CHalfLife2 g_HL2; @@ -308,7 +309,11 @@ bool CHalfLife2::HintTextMsg(int client, const char *msg) return false; } - pBitBuf->WriteByte(1); + const char *pre_byte = g_pGameConf->GetKeyValue("HintTextPreByte"); + if (pre_byte != NULL && strcmp(pre_byte, "yes") == 0) + { + pBitBuf->WriteByte(1); + } pBitBuf->WriteString(msg); g_UserMsgs.EndMessage(); diff --git a/core/Logger.cpp b/core/Logger.cpp index e1c3df40..031e5d96 100644 --- a/core/Logger.cpp +++ b/core/Logger.cpp @@ -380,7 +380,9 @@ void Logger::LogError(const char *vafmt, ...) LogToOpenFileEx(fp, vafmt, ap); va_end(ap); fclose(fp); - } else { + } + else + { char error[255]; g_LibSys.GetPlatformError(error, sizeof(error)); LogFatal("[SM] Unexpected fatal logging error (file \"%s\")", m_NrmFileName.c_str()); diff --git a/core/MenuStyle_Radio.cpp b/core/MenuStyle_Radio.cpp index c891025b..a1eee8ec 100644 --- a/core/MenuStyle_Radio.cpp +++ b/core/MenuStyle_Radio.cpp @@ -42,9 +42,15 @@ extern const char *g_RadioNumTable[]; CRadioStyle g_RadioMenuStyle; int g_ShowMenuId = -1; bool g_bRadioInit = false; +unsigned int g_RadioMenuTimeout = 0; -CRadioStyle::CRadioStyle() : m_players(new CBaseMenuPlayer[256+1]) +CRadioStyle::CRadioStyle() { + m_players = new CRadioMenuPlayer[256+1]; + for (size_t i = 0; i < 256+1; i++) + { + m_players[i].Radio_SetIndex(i); + } } void CRadioStyle::OnSourceModAllInitialized() @@ -73,6 +79,16 @@ void CRadioStyle::OnSourceModLevelChange(const char *mapName) return; } + const char *val = g_pGameConf->GetKeyValue("RadioMenuTimeout"); + if (val != NULL) + { + g_RadioMenuTimeout = atoi(val); + } + else + { + g_RadioMenuTimeout = 0; + } + g_Menus.AddStyle(this); g_Menus.SetDefaultStyle(this); @@ -190,7 +206,9 @@ CRadioDisplay *CRadioStyle::MakeRadioDisplay(CRadioMenu *menu) if (m_FreeDisplays.empty()) { display = new CRadioDisplay(); - } else { + } + else + { display = m_FreeDisplays.front(); m_FreeDisplays.pop(); display->Reset(); @@ -198,11 +216,51 @@ CRadioDisplay *CRadioStyle::MakeRadioDisplay(CRadioMenu *menu) return display; } +IMenuPanel *CRadioStyle::MakeRadioDisplay(const char *str, int keys) +{ + CRadioDisplay *pPanel = MakeRadioDisplay(NULL); + + pPanel->DirectSet(str, keys); + + return pPanel; +} + void CRadioStyle::FreeRadioDisplay(CRadioDisplay *display) { m_FreeDisplays.push(display); } +CRadioMenuPlayer *CRadioStyle::GetRadioMenuPlayer(int client) +{ + return &m_players[client]; +} + +void CRadioStyle::ProcessWatchList() +{ + if (!g_RadioMenuTimeout) + { + BaseMenuStyle::ProcessWatchList(); + return; + } + + BaseMenuStyle::ProcessWatchList(); + + CRadioMenuPlayer *pPlayer; + unsigned int max_clients = g_Players.GetMaxClients(); + for (unsigned int i = 1; i <= max_clients; i++) + { + pPlayer = GetRadioMenuPlayer(i); + if (!pPlayer->bInMenu || pPlayer->bInExternMenu) + { + continue; + } + if (pPlayer->Radio_NeedsRefresh()) + { + pPlayer->Radio_Refresh(); + } + } +} + CRadioDisplay::CRadioDisplay() { Reset(); @@ -228,6 +286,13 @@ void CRadioDisplay::Reset() keys = 0; } +void CRadioDisplay::DirectSet(const char *str, int keymap) +{ + m_Title.clear(); + m_BufferText.assign(str); + keys = keymap; +} + unsigned int CRadioDisplay::GetCurrentKey() { return m_NextPos; @@ -326,17 +391,60 @@ bool CRadioDisplay::CanDrawItem(unsigned int drawFlags) void CRadioDisplay::SendRawDisplay(int client, unsigned int time) { - char buffer[4096]; - size_t len; - - len = UTIL_Format(buffer, sizeof(buffer), "%s\n%s", m_Title.c_str(), m_BufferText.c_str()); - - cell_t players[1] = {client}; - int _sel_keys = (keys == 0) ? (1<<9) : keys; + CRadioMenuPlayer *pPlayer = g_RadioMenuStyle.GetRadioMenuPlayer(client); + pPlayer->Radio_Init(_sel_keys, m_Title.c_str(), m_BufferText.c_str()); + pPlayer->Radio_Refresh(); +} - char *ptr = buffer; +void CRadioMenuPlayer::Radio_SetIndex(unsigned int index) +{ + m_index = index; +} + +bool CRadioMenuPlayer::Radio_NeedsRefresh() +{ + return (gpGlobals->curtime - display_last_refresh >= g_RadioMenuTimeout); +} + +void CRadioMenuPlayer::Radio_Init(int keys, const char *title, const char *text) +{ + if (title[0] != '\0') + { + display_len = UTIL_Format(display_pkt, + sizeof(display_pkt), + "%s\n%s", + title, + text); + } + else + { + display_len = UTIL_Format(display_pkt, + sizeof(display_pkt), + "%s", + text); + } + display_keys = keys; +} + +void CRadioMenuPlayer::Radio_Refresh() +{ + cell_t players[1] = {m_index}; + char *ptr = display_pkt; char save = 0; + size_t len = display_len; + unsigned int time; + + /* Compute the new time */ + if (menuHoldTime == 0) + { + time = 0; + } + else + { + time = menuHoldTime - (unsigned int)(gpGlobals->curtime - menuStartTime); + } + while (true) { if (len > 240) @@ -345,7 +453,7 @@ void CRadioDisplay::SendRawDisplay(int client, unsigned int time) ptr[240] = '\0'; } bf_write *buffer = g_UserMsgs.StartMessage(g_ShowMenuId, players, 1, USERMSG_BLOCKHOOKS); - buffer->WriteWord(_sel_keys); + buffer->WriteWord(display_keys); buffer->WriteChar(time ? time : -1); buffer->WriteByte( (len > 240) ? 1 : 0 ); buffer->WriteString(ptr); @@ -355,10 +463,14 @@ void CRadioDisplay::SendRawDisplay(int client, unsigned int time) ptr[240] = save; ptr = &ptr[240]; len -= 240; - } else { + } + else + { break; } } + + display_last_refresh = gpGlobals->curtime; } int CRadioDisplay::GetAmountRemaining() diff --git a/core/MenuStyle_Radio.h b/core/MenuStyle_Radio.h index 35f30cf0..e8ba85f5 100644 --- a/core/MenuStyle_Radio.h +++ b/core/MenuStyle_Radio.h @@ -47,6 +47,21 @@ using namespace SourceMod; class CRadioDisplay; class CRadioMenu; +class CRadioMenuPlayer : public CBaseMenuPlayer +{ +public: + void Radio_Init(int keys, const char *title, const char *buffer); + bool Radio_NeedsRefresh(); + void Radio_Refresh(); + void Radio_SetIndex(unsigned int index); +private: + unsigned int m_index; + size_t display_len; + char display_pkt[512]; + int display_keys; + float display_last_refresh; +}; + class CRadioStyle : public BaseMenuStyle, public SMGlobalClass, @@ -61,6 +76,7 @@ public: //SMGlobalClass public: //BaseMenuStyle CBaseMenuPlayer *GetMenuPlayer(int client); void SendDisplay(int client, IMenuPanel *display); + void ProcessWatchList(); public: //IMenuStyle const char *GetStyleName(); IMenuPanel *CreatePanel(); @@ -75,8 +91,10 @@ public: public: CRadioDisplay *MakeRadioDisplay(CRadioMenu *menu=NULL); void FreeRadioDisplay(CRadioDisplay *display); + CRadioMenuPlayer *GetRadioMenuPlayer(int client); + IMenuPanel *MakeRadioDisplay(const char *str, int keys); private: - CBaseMenuPlayer *m_players; + CRadioMenuPlayer *m_players; CStack m_FreeDisplays; }; @@ -101,6 +119,8 @@ public: //IMenuPanel unsigned int GetCurrentKey(); bool SetCurrentKey(unsigned int key); int GetAmountRemaining(); +public: + void DirectSet(const char *str, int keymap); private: String m_BufferText; String m_Title; diff --git a/core/PlayerManager.cpp b/core/PlayerManager.cpp index 5d701c5c..0e54712f 100644 --- a/core/PlayerManager.cpp +++ b/core/PlayerManager.cpp @@ -1295,12 +1295,32 @@ void CPlayer::Authorize_Post() void CPlayer::DoPostConnectAuthorization() { + bool delay = false; + + List::iterator iter; + for (iter = g_Players.m_hooks.begin(); + iter != g_Players.m_hooks.end(); + iter++) + { + IClientListener *pListener = (*iter); +#if defined MIN_API_FOR_ADMINCALLS + if (pListener->GetClientListenerVersion() < MIN_API_FOR_ADMINCALLS) + { + continue; + } +#endif + if (!pListener->OnClientPreAdminCheck(m_iIndex)) + { + delay = true; + } + } + cell_t result = 0; PreAdminCheck->PushCell(m_iIndex); PreAdminCheck->Execute(&result); /* Defer, for better or worse */ - if ((ResultType)result >= Pl_Handled) + if (delay || (ResultType)result >= Pl_Handled) { return; } @@ -1318,6 +1338,15 @@ void CPlayer::DoPostConnectAuthorization() NotifyPostAdminChecks(); } +bool CPlayer::RunAdminCacheChecks() +{ + AdminId old_id = GetAdminId(); + + DoBasicAdminChecks(); + + return (GetAdminId() != old_id); +} + void CPlayer::NotifyPostAdminChecks() { if (m_bAdminCheckSignalled) @@ -1328,6 +1357,21 @@ void CPlayer::NotifyPostAdminChecks() /* Block beforehand so they can't double-call */ m_bAdminCheckSignalled = true; + List::iterator iter; + for (iter = g_Players.m_hooks.begin(); + iter != g_Players.m_hooks.end(); + iter++) + { + IClientListener *pListener = (*iter); +#if defined MIN_API_FOR_ADMINCALLS + if (pListener->GetClientListenerVersion() < MIN_API_FOR_ADMINCALLS) + { + continue; + } +#endif + pListener->OnClientPostAdminCheck(m_iIndex); + } + PostAdminCheck->PushCell(m_iIndex); PostAdminCheck->Execute(NULL); } diff --git a/core/PlayerManager.h b/core/PlayerManager.h index 4e1a65ed..767a90dd 100644 --- a/core/PlayerManager.h +++ b/core/PlayerManager.h @@ -48,6 +48,8 @@ using namespace SourceHook; #define PLAYER_LIFE_ALIVE 1 #define PLAYER_LIFE_DEAD 2 +#define MIN_API_FOR_ADMINCALLS 7 + class CPlayer : public IGamePlayer { friend class PlayerManager; @@ -69,8 +71,9 @@ public: IPlayerInfo *GetPlayerInfo(); unsigned int GetLanguageId(); int GetUserId(); -public: + bool RunAdminCacheChecks(); void NotifyPostAdminChecks(); +public: void DoBasicAdminChecks(); bool IsInKickQueue(); void MarkAsBeingKicked(); @@ -108,6 +111,7 @@ class PlayerManager : public SMGlobalClass, public IPlayerManager { + friend class CPlayer; public: PlayerManager(); ~PlayerManager(); diff --git a/core/TimerSys.cpp b/core/TimerSys.cpp index d4de6231..a86945ae 100644 --- a/core/TimerSys.cpp +++ b/core/TimerSys.cpp @@ -48,16 +48,16 @@ SH_DECL_HOOK2_void(ICvar, CallGlobalChangeCallbacks, SH_NOATTRIB, false, ConVar #endif TimerSystem g_Timers; -float g_fUniversalTime = 0.0f; +double g_fUniversalTime = 0.0f; float g_fGameStartTime = 0.0f; /* Game game start time, non-universal */ -float g_fTimerThink = 0.0f; /* Timer's next think time */ -const float *g_pUniversalTime = &g_fUniversalTime; +double g_fTimerThink = 0.0f; /* Timer's next think time */ +const double *g_pUniversalTime = &g_fUniversalTime; ConVar *mp_timelimit = NULL; int g_TimeLeftMode = 0; ConVar sm_time_adjustment("sm_time_adjustment", "0", 0, "Adjusts the server time in seconds"); -inline float GetSimulatedTime() +inline double GetSimulatedTime() { return g_fUniversalTime; } @@ -159,7 +159,7 @@ private: * that a drastic jump in time will continue acting normally. Users * may not expect this, but... I think it is the best solution. */ -inline float CalcNextThink(float last, float interval) +inline double CalcNextThink(double last, float interval) { if (g_fUniversalTime - last - interval <= TIMER_MIN_ACCURACY) { @@ -274,7 +274,7 @@ void TimerSystem::RunFrame() ITimer *pTimer; TimerIter iter; - float curtime = GetSimulatedTime(); + double curtime = GetSimulatedTime(); for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); ) { pTimer = (*iter); diff --git a/core/TimerSys.h b/core/TimerSys.h index 3b66e778..3cf0b066 100644 --- a/core/TimerSys.h +++ b/core/TimerSys.h @@ -51,7 +51,7 @@ public: ITimedEvent *m_Listener; void *m_pData; float m_Interval; - float m_ToExec; + double m_ToExec; int m_Flags; bool m_InExec; bool m_KillMe; @@ -103,7 +103,7 @@ private: time_t GetAdjustedTime(time_t *buf = NULL); -extern const float *g_pUniversalTime; +extern const double *g_pUniversalTime; extern TimerSystem g_Timers; extern int g_TimeLeftMode; diff --git a/core/UserMessages.cpp b/core/UserMessages.cpp index 7384b741..0413286f 100644 --- a/core/UserMessages.cpp +++ b/core/UserMessages.cpp @@ -135,7 +135,7 @@ bool UserMessages::GetMessageName(int msgid, char *buffer, size_t maxlength) con return false; } -bf_write *UserMessages::StartMessage(int msg_id, cell_t players[], unsigned int playersNum, int flags) +bf_write *UserMessages::StartMessage(int msg_id, const cell_t players[], unsigned int playersNum, int flags) { bf_write *buffer; diff --git a/core/UserMessages.h b/core/UserMessages.h index 9736b0fc..01969fcf 100644 --- a/core/UserMessages.h +++ b/core/UserMessages.h @@ -69,7 +69,7 @@ public: //IUserMessages bool GetMessageName(int msgid, char *buffer, size_t maxlength) const; bool HookUserMessage(int msg_id, IUserMessageListener *pListener, bool intercept=false); bool UnhookUserMessage(int msg_id, IUserMessageListener *pListener, bool intercept=false); - bf_write *StartMessage(int msg_id, cell_t players[], unsigned int playersNum, int flags); + bf_write *StartMessage(int msg_id, const cell_t players[], unsigned int playersNum, int flags); bool EndMessage(); public: bf_write *OnStartMessage_Pre(IRecipientFilter *filter, int msg_type); diff --git a/core/sm_srvcmds.cpp b/core/sm_srvcmds.cpp index 9e0e9f60..cbacadd0 100644 --- a/core/sm_srvcmds.cpp +++ b/core/sm_srvcmds.cpp @@ -290,6 +290,34 @@ CON_COMMAND(sm, "SourceMod Menu") g_RootMenu.GotRootCmd(args); } +FILE *g_pHndlLog = NULL; + +void write_handles_to_log(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(g_pHndlLog, fmt, ap); + fprintf(g_pHndlLog, "\n"); + va_end(ap); +} + +void write_handles_to_game(const char *fmt, ...) +{ + size_t len; + va_list ap; + char buffer[1024]; + + va_start(ap, fmt); + len = UTIL_FormatArgs(buffer, sizeof(buffer)-2, fmt, ap); + va_end(ap); + + buffer[len] = '\n'; + buffer[len+1] = '\0'; + + engine->LogPrint(buffer); +} + CON_COMMAND(sm_dump_handles, "Dumps Handle usage to a file for finding Handle leaks") { #if !defined ORANGEBOX_BUILD @@ -297,19 +325,28 @@ CON_COMMAND(sm_dump_handles, "Dumps Handle usage to a file for finding Handle le #endif if (args.ArgC() < 2) { - g_RootMenu.ConsolePrint("Usage: sm_dump_handles "); + g_RootMenu.ConsolePrint("Usage: sm_dump_handles or for game logs"); return; } - const char *arg = args.Arg(1); - FILE *fp = fopen(arg, "wt"); - if (!fp) + if (strcmp(args.Arg(1), "log") != 0) { - g_RootMenu.ConsolePrint("Could not find file \"%s\"", arg); - return; + const char *arg = args.Arg(1); + FILE *fp = fopen(arg, "wt"); + if (!fp) + { + g_RootMenu.ConsolePrint("Could not find file \"%s\"", arg); + return; + } + + g_pHndlLog = fp; + g_HandleSys.Dump(write_handles_to_log); + g_pHndlLog = NULL; + + fclose(fp); + } + else + { + g_HandleSys.Dump(write_handles_to_game); } - - g_HandleSys.Dump(fp); - - fclose(fp); } diff --git a/core/sm_stringutil.cpp b/core/sm_stringutil.cpp index 35e36b0b..e94ff44e 100644 --- a/core/sm_stringutil.cpp +++ b/core/sm_stringutil.cpp @@ -207,7 +207,9 @@ void AddString(char **buf_p, size_t &maxlen, const char *string, int width, int break; } } - } else { + } + else + { while (string[size++]); size--; } @@ -234,78 +236,125 @@ void AddString(char **buf_p, size_t &maxlen, const char *string, int width, int *buf_p = buf; } -void AddFloat(char **buf_p, size_t &maxlen, double fval, int width, int prec) +void AddFloat(char **buf_p, size_t &maxlen, double fval, int width, int prec, int flags) { - char text[32]; - int digits; - double signedVal; - char *buf; - int val; - - // get the sign - signedVal = fval; - if (fval < 0) - { - fval = -fval; - } - - // write the float number - digits = 0; - val = (int)fval; - do - { - text[digits++] = '0' + val % 10; - val /= 10; - } while (val); - - if (signedVal < 0) - { - text[digits++] = '-'; - } - - buf = *buf_p; - - while ((digits < width) && maxlen) - { - *buf++ = ' '; - width--; - maxlen--; - } - - while ((digits--) && maxlen) - { - *buf++ = text[digits]; - maxlen--; - } - - *buf_p = buf; + int digits; // non-fraction part digits + double tmp; // temporary + char *buf = *buf_p; // output buffer pointer + int val; // temporary + int sign = 0; // 0: positive, 1: negative + int fieldlength; // for padding + int significant_digits = 0; // number of significant digits written + const int MAX_SIGNIFICANT_DIGITS = 16; + // default precision if (prec < 0) { prec = 6; } - // write the fraction - digits = 0; - while (digits < prec) + + // get the sign + if (fval < 0) { - fval -= (int)fval; - fval *= 10.0; - val = (int)fval; - text[digits++] = '0' + val % 10; + fval = -fval; + sign = 1; } - if ((digits > 0) && maxlen) + // compute whole-part digits count + digits = (int)log10(fval) + 1; + + // Only print 0.something if 0 < fval < 1 + if (digits < 1) { - buf = *buf_p; - *buf++ = '.'; + digits = 1; + } + + // compute the field length + fieldlength = digits + prec + ((prec > 0) ? 1 : 0) + sign; + + // minus sign BEFORE left padding if padding with zeros + if (sign && maxlen && (flags & ZEROPAD)) + { + *buf++ = '-'; maxlen--; - for (prec = 0; maxlen && (prec < digits); prec++) + } + + // right justify if required + if ((flags & LADJUST) == 0) + { + while ((fieldlength < width) && maxlen) { - *buf++ = text[prec]; + *buf++ = (flags & ZEROPAD) ? '0' : ' '; + width--; maxlen--; } - *buf_p = buf; } + + // minus sign AFTER left padding if padding with spaces + if (sign && maxlen && !(flags & ZEROPAD)) + { + *buf++ = '-'; + maxlen--; + } + + // write the whole part + tmp = pow(10.0, digits-1); + while ((digits--) && maxlen) + { + if (++significant_digits > MAX_SIGNIFICANT_DIGITS) + { + *buf++ = '0'; + } + else + { + val = (int)(fval / tmp); + *buf++ = '0' + val; + fval -= val * tmp; + tmp *= 0.1; + } + maxlen--; + } + + // write the fraction part + if (maxlen) + { + *buf++ = '.'; + maxlen--; + } + + tmp = pow(10.0, prec); + + fval *= tmp; + while (prec-- && maxlen) + { + if (++significant_digits > MAX_SIGNIFICANT_DIGITS) + { + *buf++ = '0'; + } + else + { + tmp *= 0.1; + val = (int)(fval / tmp); + *buf++ = '0' + val; + fval -= val * tmp; + } + maxlen--; + } + + // left justify if required + if (flags & LADJUST) + { + while ((fieldlength < width) && maxlen) + { + // right-padding only with spaces, ZEROPAD is ignored + *buf++ = ' '; + width--; + maxlen--; + } + } + + // update parent's buffer pointer + *buf_p = buf; } void AddUInt(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags) @@ -366,9 +415,12 @@ void AddInt(char **buf_p, size_t &maxlen, int val, int width, int flags) { /* we want the unsigned version */ unsignedVal = abs(val); - } else { + } + else + { unsignedVal = val; } + do { text[digits++] = '0' + unsignedVal % 10; @@ -422,7 +474,9 @@ void AddHex(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags if (flags & UPPERDIGITS) { hexadjust = 'A' - '9' - 1; - } else { + } + else + { hexadjust = 'a' - '9' - 1; } @@ -587,7 +641,7 @@ reswitch: case 'f': { float *value = (float *)args[arg]; - AddFloat(&buf_p, llen, *value, width, prec); + AddFloat(&buf_p, llen, *value, width, prec, flags); arg++; break; } @@ -779,7 +833,7 @@ reswitch: CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); - AddFloat(&buf_p, llen, sp_ctof(*value), width, prec); + AddFloat(&buf_p, llen, sp_ctof(*value), width, prec, flags); arg++; break; } @@ -808,7 +862,9 @@ reswitch: player->GetName(), userid, auth); - } else { + } + else + { UTIL_Format(buffer, sizeof(buffer), "Console<0>"); @@ -964,7 +1020,9 @@ const char *stristr(const char *str, const char *substr) { return prevloc; } - } else { + } + else + { haystack = ++prevloc; needle = (char *)substr; } @@ -1001,7 +1059,9 @@ size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...) { buffer[maxlength - 1] = '\0'; return (maxlength - 1); - } else { + } + else + { return len; } } @@ -1014,7 +1074,9 @@ size_t UTIL_FormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list { buffer[maxlength - 1] = '\0'; return (maxlength - 1); - } else { + } + else + { return len; } } @@ -1117,7 +1179,9 @@ char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t se replaceLen = maxLen - browsed; /* Note, we add one to the final result for the null terminator */ strncopy(ptr, replace, replaceLen+1); - } else { + } + else + { /* EXAMPLE CASE: * Subject: AABBBCCC * Buffer : 12 bytes @@ -1138,7 +1202,9 @@ char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t se /* Now, do our replacement. */ memcpy(ptr, replace, replaceLen); } - } else { + } + else + { /* EXAMPLE CASE: * Subject: AABBBCCC * Buffer : 12 bytes @@ -1158,7 +1224,9 @@ char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t se /* Now do our replacement. */ memcpy(ptr, replace, replaceLen); } - } else if (replaceLen < searchLen) { + } + else if (replaceLen < searchLen) + { /* EXAMPLE CASE: * Subject: AABBBCCC * Buffer : 12 bytes @@ -1184,7 +1252,9 @@ char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t se /* Move the rest of the string down */ memmove(moveTo, moveFrom, bytesToCopy); - } else { + } + else + { /* EXAMPLE CASE: * Subject: AABBBCCC * Buffer : 12 bytes diff --git a/core/smn_keyvalues.cpp b/core/smn_keyvalues.cpp index 3ffad068..d0313947 100644 --- a/core/smn_keyvalues.cpp +++ b/core/smn_keyvalues.cpp @@ -406,6 +406,33 @@ static cell_t smn_KvJumpToKey(IPluginContext *pCtx, const cell_t *params) return 1; } +static cell_t smn_KvJumpToKeySymbol(IPluginContext *pCtx, const cell_t *params) +{ + Handle_t hndl = static_cast(params[1]); + HandleError herr; + HandleSecurity sec; + KeyValueStack *pStk; + + sec.pOwner = NULL; + sec.pIdentity = g_pCoreIdent; + + if ((herr=g_HandleSys.ReadHandle(hndl, g_KeyValueType, &sec, (void **)&pStk)) + != HandleError_None) + { + return pCtx->ThrowNativeError("Invalid key value handle %x (error %d)", hndl, herr); + } + + KeyValues *pSubKey = pStk->pCurRoot.front(); + pSubKey = pSubKey->FindKey(params[2]); + if (!pSubKey) + { + return 0; + } + pStk->pCurRoot.push(pSubKey); + + return 1; +} + static cell_t smn_KvGotoFirstSubKey(IPluginContext *pCtx, const cell_t *params) { Handle_t hndl = static_cast(params[1]); @@ -921,6 +948,36 @@ static cell_t smn_FindKeyById(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t smn_KvGetSectionSymbol(IPluginContext *pCtx, const cell_t *params) +{ + Handle_t hndl = static_cast(params[1]); + HandleError herr; + HandleSecurity sec; + KeyValueStack *pStk; + cell_t *val; + + sec.pOwner = NULL; + sec.pIdentity = g_pCoreIdent; + + if ((herr=g_HandleSys.ReadHandle(hndl, g_KeyValueType, &sec, (void **)&pStk)) + != HandleError_None) + { + return pCtx->ThrowNativeError("Invalid key value handle %x (error %d)", hndl, herr); + } + + KeyValues *pSection = pStk->pCurRoot.front(); + + pCtx->LocalToPhysAddr(params[2], &val); + *val = pSection->GetNameSymbol(); + + if (!*val) + { + return 0; + } + + return 1; +} + static KeyValueNatives s_KeyValueNatives; REGISTER_NATIVES(keyvaluenatives) @@ -937,6 +994,7 @@ REGISTER_NATIVES(keyvaluenatives) {"KvGetUInt64", smn_KvGetUInt64}, {"CreateKeyValues", smn_CreateKeyValues}, {"KvJumpToKey", smn_KvJumpToKey}, + {"KvJumpToKeySymbol", smn_KvJumpToKeySymbol}, {"KvGotoNextKey", smn_KvGotoNextKey}, {"KvJumpFirstSubKey", smn_KvGotoFirstSubKey}, /* BACKWARDS COMPAT SHIM */ {"KvGotoFirstSubKey", smn_KvGotoFirstSubKey}, @@ -956,5 +1014,6 @@ REGISTER_NATIVES(keyvaluenatives) {"KvCopySubkeys", smn_CopySubkeys}, {"KvFindKeyById", smn_FindKeyById}, {"KvGetNameSymbol", smn_GetNameSymbol}, + {"KvGetSectionSymbol", smn_KvGetSectionSymbol}, {NULL, NULL} }; diff --git a/core/smn_menus.cpp b/core/smn_menus.cpp index 4074c8df..22534c7e 100644 --- a/core/smn_menus.cpp +++ b/core/smn_menus.cpp @@ -37,6 +37,7 @@ #include "MenuStyle_Radio.h" #include "HandleSys.h" #include "PluginSys.h" +#include "PlayerManager.h" #include "sm_stringutil.h" #include "sourcemm_api.h" #if defined MENU_DEBUG @@ -1398,6 +1399,73 @@ static cell_t GetMenuSelectionPosition(IPluginContext *pContext, const cell_t *p return *s_CurSelectPosition; } +class EmptyMenuHandler : public IMenuHandler +{ +public: +} s_EmptyMenuHandler; + +static cell_t InternalShowMenu(IPluginContext *pContext, const cell_t *params) +{ + int client = params[1]; + CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); + + if (pPlayer == NULL) + { + return pContext->ThrowNativeError("Invalid client index %d", client); + } + else if (!pPlayer->IsInGame()) + { + return pContext->ThrowNativeError("Client %d is not in game", client); + } + + if (!g_RadioMenuStyle.IsSupported()) + { + return pContext->ThrowNativeError("Radio menus are not supported on this mod"); + } + + char *str; + pContext->LocalToString(params[2], &str); + + IMenuPanel *pPanel = g_RadioMenuStyle.MakeRadioDisplay(str, params[4]); + + if (pPanel == NULL) + { + return 0; + } + + IMenuHandler *pHandler; + CPanelHandler *pActualHandler = NULL; + if (params[5] != -1) + { + IPluginFunction *pFunction = pContext->GetFunctionById(params[5]); + if (pFunction == NULL) + { + return pContext->ThrowNativeError("Invalid function index %x", params[5]); + } + pActualHandler = g_MenuHelpers.GetPanelHandler(pFunction); + } + + if (pActualHandler == NULL) + { + pHandler = &s_EmptyMenuHandler; + } + else + { + pHandler = pActualHandler; + } + + bool bSuccess = pPanel->SendDisplay(client, pHandler, params[3]); + + pPanel->DeleteThis(); + + if (!bSuccess && pActualHandler != NULL) + { + g_MenuHelpers.FreePanelHandler(pActualHandler); + } + + return bSuccess ? 1 : 0; +} + REGISTER_NATIVES(menuNatives) { {"AddMenuItem", AddMenuItem}, @@ -1430,6 +1498,7 @@ REGISTER_NATIVES(menuNatives) {"GetPanelCurrentKey", GetPanelCurrentKey}, {"GetPanelStyle", GetPanelStyle}, {"InsertMenuItem", InsertMenuItem}, + {"InternalShowMenu", InternalShowMenu}, {"IsVoteInProgress", IsVoteInProgress}, {"RedrawMenuItem", RedrawMenuItem}, {"RemoveAllMenuItems", RemoveAllMenuItems}, diff --git a/core/smn_player.cpp b/core/smn_player.cpp index 78646639..03f504b0 100644 --- a/core/smn_player.cpp +++ b/core/smn_player.cpp @@ -259,7 +259,7 @@ static cell_t SetUserAdmin(IPluginContext *pContext, const cell_t *params) { return pContext->ThrowNativeError("Client %d is not connected", client); } - if (!g_Admins.IsValidAdmin(params[2])) + if (!g_Admins.IsValidAdmin(params[2]) && params[2] != INVALID_ADMIN_ID) { return pContext->ThrowNativeError("AdminId %x is invalid", params[2]); } diff --git a/core/smn_sorting.cpp b/core/smn_sorting.cpp index f78bff74..e300e282 100644 --- a/core/smn_sorting.cpp +++ b/core/smn_sorting.cpp @@ -33,6 +33,8 @@ #include #include #include +#include "HandleSys.h" +#include "CellArray.h" /*********************************** * About the double array hack * @@ -373,6 +375,139 @@ static cell_t sm_SortCustom2D(IPluginContext *pContext, const cell_t *params) return 1; } +enum SortType +{ + Sort_Integer = 0, + Sort_Float, + Sort_String, +}; + +int sort_adtarray_strings_asc(const void *str1, const void *str2) +{ + return strcmp((char *) str1, (char *) str2); +} + +int sort_adtarray_strings_desc(const void *str1, const void *str2) +{ + return strcmp((char *) str2, (char *) str1); +} + +static cell_t sm_SortADTArray(IPluginContext *pContext, const cell_t *params) +{ + CellArray *cArray; + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), g_pCoreIdent); + + if ((err = g_HandleSys.ReadHandle(params[1], htCellArray, &sec, (void **)&cArray)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); + } + + cell_t order = params[2]; + cell_t type = params[3]; + size_t arraysize = cArray->size(); + size_t blocksize = cArray->blocksize(); + cell_t *array = cArray->base(); + + if (type == Sort_Integer) + { + if (order == Sort_Ascending) + { + qsort(array, arraysize, blocksize * sizeof(cell_t), sort_ints_asc); + } + else + { + qsort(array, arraysize, blocksize * sizeof(cell_t), sort_ints_desc); + } + } + else if (type == Sort_Float) + { + if (order == Sort_Ascending) + { + qsort(array, arraysize, blocksize * sizeof(cell_t), sort_floats_asc); + } + else + { + qsort(array, arraysize, blocksize * sizeof(cell_t), sort_floats_desc); + } + } + else if (type == Sort_String) + { + if (order == Sort_Ascending) + { + qsort(array, arraysize, blocksize * sizeof(cell_t), sort_adtarray_strings_asc); + } + else + { + qsort(array, arraysize, blocksize * sizeof(cell_t), sort_adtarray_strings_desc); + } + } + + return 1; +} + +struct sort_infoADT +{ + IPluginFunction *pFunc; + cell_t *array_base; + cell_t array_bsize; + Handle_t array_hndl; + Handle_t hndl; +}; + +sort_infoADT g_SortInfoADT; + +int sort_adtarray_custom(const void *elem1, const void *elem2) +{ + cell_t result = 0; + IPluginFunction *pf = g_SortInfoADT.pFunc; + pf->PushCell(((cell_t) ((cell_t *) elem1 - g_SortInfoADT.array_base)) / g_SortInfoADT.array_bsize); + pf->PushCell(((cell_t) ((cell_t *) elem2 - g_SortInfoADT.array_base)) / g_SortInfoADT.array_bsize); + pf->PushCell(g_SortInfoADT.array_hndl); + pf->PushCell(g_SortInfoADT.hndl); + pf->Execute(&result); + + return result; +} + +static cell_t sm_SortADTArrayCustom(IPluginContext *pContext, const cell_t *params) +{ + CellArray *cArray; + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), g_pCoreIdent); + + if ((err = g_HandleSys.ReadHandle(params[1], htCellArray, &sec, (void **)&cArray)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); + } + + IPluginFunction *pFunction = pContext->GetFunctionById(params[2]); + if (!pFunction) + { + return pContext->ThrowNativeError("Function %x is not a valid function", params[2]); + } + + size_t arraysize = cArray->size(); + size_t blocksize = cArray->blocksize(); + cell_t *array = cArray->base(); + + sort_infoADT oldinfo = g_SortInfoADT; + + g_SortInfoADT.pFunc = pFunction; + g_SortInfoADT.array_base = array; + g_SortInfoADT.array_bsize = (cell_t) blocksize; + g_SortInfoADT.array_hndl = params[1]; + g_SortInfoADT.hndl = params[3]; + + qsort(array, arraysize, blocksize * sizeof(cell_t), sort_adtarray_custom); + + g_SortInfoADT = oldinfo; + + return 1; +} + REGISTER_NATIVES(sortNatives) { {"SortIntegers", sm_SortIntegers}, @@ -380,5 +515,7 @@ REGISTER_NATIVES(sortNatives) {"SortStrings", sm_SortStrings}, {"SortCustom1D", sm_SortCustom1D}, {"SortCustom2D", sm_SortCustom2D}, + {"SortADTArray", sm_SortADTArray}, + {"SortADTArrayCustom", sm_SortADTArrayCustom}, {NULL, NULL}, }; diff --git a/core/sourcemod.cpp b/core/sourcemod.cpp index e59afd31..e94ed402 100644 --- a/core/sourcemod.cpp +++ b/core/sourcemod.cpp @@ -586,9 +586,11 @@ const char *SourceModBase::GetGamePath() const return g_BaseDir.c_str(); } -void SourceModBase::SetGlobalTarget(unsigned int index) +unsigned int SourceModBase::SetGlobalTarget(unsigned int index) { + unsigned int old = m_target; m_target = index; + return old; } unsigned int SourceModBase::GetGlobalTarget() const diff --git a/core/sourcemod.h b/core/sourcemod.h index 9a804a43..1b222c80 100644 --- a/core/sourcemod.h +++ b/core/sourcemod.h @@ -83,7 +83,7 @@ public: /** * @brief Stores the global target index. */ - void SetGlobalTarget(unsigned int index); + unsigned int SetGlobalTarget(unsigned int index); /** * @brief Returns the global target index. diff --git a/core/systems/ExtensionSys.cpp b/core/systems/ExtensionSys.cpp index 78df64ec..8209b3ce 100644 --- a/core/systems/ExtensionSys.cpp +++ b/core/systems/ExtensionSys.cpp @@ -285,7 +285,22 @@ void CExtension::RemovePlugin(IPlugin *pPlugin) if ((*iter).pl == pPlugin) { iter = m_WeakNatives.erase(iter); - } else { + } + else + { + iter++; + } + } + + iter = m_ReplacedNatives.begin(); + while (iter != m_ReplacedNatives.end()) + { + if ((*iter).pl == pPlugin) + { + iter = m_ReplacedNatives.erase(iter); + } + else + { iter++; } } @@ -703,6 +718,7 @@ void CExtensionManager::BindAllNativesToPlugin(IPlugin *pPlugin) uint32_t natives = pContext->GetNativesNum(); sp_native_t *native; sm_extnative_t *x_native; + sm_repnative_t *r_native; for (uint32_t i=0; istatus == SP_NATIVE_BOUND) { + /* If it is bound, see if there is a replacement. */ + if ((r_native = m_RepNatives.retrieve(native->name)) == NULL) + { + continue; + } + + /* Rewrite the address. Whee! */ + native->pfn = r_native->info.func; + + /* Make sure this will unload safely */ + WeakNative wn((CPlugin *)pPlugin, i); + r_native->owner->m_ReplacedNatives.push_back(wn); + continue; } + /* See if we've got this native in our cache */ if ((x_native = m_ExtNatives.retrieve(native->name)) == NULL) { @@ -789,7 +820,7 @@ bool CExtensionManager::UnloadExtension(IExtension *_pExt) g_PluginSys.OnLibraryAction((*s_iter).c_str(), false, true); } - /* Unbound weak natives */ + /* Unbind weak natives */ List::iterator wkn_iter; for (wkn_iter=pExt->m_WeakNatives.begin(); wkn_iter!=pExt->m_WeakNatives.end(); wkn_iter++) { @@ -798,6 +829,21 @@ bool CExtensionManager::UnloadExtension(IExtension *_pExt) ctx->natives[wkn.idx].status = SP_NATIVE_UNBOUND; } + /* Unbind replacement natives, link them back to their originals */ + for (wkn_iter = pExt->m_ReplacedNatives.begin(); + wkn_iter != pExt->m_ReplacedNatives.end(); + wkn_iter++) + { + WeakNative & wkn = (*wkn_iter); + sp_context_t *ctx = wkn.pl->GetContext(); + sm_repnative_t *r_native = m_RepNatives.retrieve(ctx->natives[wkn.idx].name); + if (r_native == NULL || ctx->natives[wkn.idx].pfn != r_native->info.func) + { + continue; + } + ctx->natives[wkn.idx].pfn = r_native->original; + } + /* Notify and/or unload all dependencies */ List::iterator c_iter; CExtension *pDep; @@ -871,6 +917,22 @@ bool CExtensionManager::UnloadExtension(IExtension *_pExt) } } + /* Unbind our replacement natives */ + List::iterator rep_iter = m_RepNativeList.begin(); + while (rep_iter != m_RepNativeList.end()) + { + sm_repnative_t & r_native = (*rep_iter); + if (r_native.owner == pExt) + { + m_RepNatives.remove(r_native.info.name); + rep_iter = m_RepNativeList.erase(rep_iter); + } + else + { + rep_iter++; + } + } + /* Tell it to unload */ pAPI = pExt->GetAPI(); pAPI->OnExtensionUnload(); @@ -919,6 +981,25 @@ void CExtensionManager::AddNatives(IExtension *pOwner, const sp_nativeinfo_t *na } } +void CExtensionManager::OverrideNatives(IExtension *myself, const sp_nativeinfo_t *natives) +{ + SPVM_NATIVE_FUNC orig; + + for (unsigned int i = 0; natives[i].func != NULL && natives[i].name != NULL; i++) + { + if ((orig = g_PluginSys.FindCoreNative(natives[i].name)) == NULL) + { + continue; + } + sm_repnative_t rep; + rep.info = natives[i]; + rep.owner = (CExtension *)myself; + rep.original = orig; + m_RepNativeList.push_back(rep); + m_RepNatives.insert(natives[i].name, rep); + } +} + void CExtensionManager::MarkAllLoaded() { List::iterator iter; diff --git a/core/systems/ExtensionSys.h b/core/systems/ExtensionSys.h index 1fe29f1c..dbba34e6 100644 --- a/core/systems/ExtensionSys.h +++ b/core/systems/ExtensionSys.h @@ -55,6 +55,14 @@ struct sm_extnative_t const sp_nativeinfo_t *info; }; +/* Replacement native */ +struct sm_repnative_t +{ + CExtension *owner; + sp_nativeinfo_t info; + SPVM_NATIVE_FUNC original; +}; + class CExtension : public IExtension { friend class CExtensionManager; @@ -98,6 +106,7 @@ protected: List m_Plugins; List m_Natives; List m_WeakNatives; + List m_ReplacedNatives; List m_Libraries; unsigned int unload_code; bool m_bFullyLoaded; @@ -168,6 +177,7 @@ public: void TryAutoload(); void AddLibrary(IExtension *pSource, const char *library); bool LibraryExists(const char *library); + void OverrideNatives(IExtension *myself, const sp_nativeinfo_t *natives); public: CExtension *GetExtensionFromIdent(IdentityToken_t *ptr); void Shutdown(); @@ -175,6 +185,8 @@ private: CExtension *FindByOrder(unsigned int num); private: List m_Libs; + List m_RepNativeList; + KTrie m_RepNatives; KTrie m_ExtNatives; }; diff --git a/core/systems/HandleSys.cpp b/core/systems/HandleSys.cpp index 880b5f14..5d0e9885 100644 --- a/core/systems/HandleSys.cpp +++ b/core/systems/HandleSys.cpp @@ -994,10 +994,10 @@ bool HandleSystem::TryAndFreeSomeHandles() return g_PluginSys.UnloadPlugin(highest_owner); } -void HandleSystem::Dump(FILE *fp) +void HandleSystem::Dump(HANDLE_REPORTER rep) { - fprintf(fp, "%-10.10s\t%-20.20s\t%-20.20s\n", "Handle", "Owner", "Type"); - fprintf(fp, "---------------------------------------------\n"); + rep("%-10.10s\t%-20.20s\t%-20.20s", "Handle", "Owner", "Type"); + rep("---------------------------------------------"); for (unsigned int i = 1; i <= m_HandleTail; i++) { if (m_Handles[i].set != HandleSet_Used) @@ -1046,7 +1046,7 @@ void HandleSystem::Dump(FILE *fp) { type = m_strtab->GetString(pType->nameIdx); } - fprintf(fp, "0x%08x\t%-20.20s\t%-20.20s\n", index, owner, type); + rep("0x%08x\t%-20.20s\t%-20.20s", index, owner, type); } } diff --git a/core/systems/HandleSys.h b/core/systems/HandleSys.h index e0ef22b4..c49b1510 100644 --- a/core/systems/HandleSys.h +++ b/core/systems/HandleSys.h @@ -105,6 +105,8 @@ struct QHandleType int nameIdx; }; +typedef void (HANDLE_REPORTER)(const char *str, ...); + class HandleSystem : public IHandleSys { @@ -155,7 +157,7 @@ public: //IHandleSystem const HandleAccess *pAccess, HandleError *err); - void Dump(FILE *fp); + void Dump(HANDLE_REPORTER rep); protected: /** * Decodes a handle with sanity and security checking. diff --git a/core/systems/PluginSys.cpp b/core/systems/PluginSys.cpp index a0bdcce4..d18aa689 100644 --- a/core/systems/PluginSys.cpp +++ b/core/systems/PluginSys.cpp @@ -1440,6 +1440,18 @@ void CPluginManager::AddCoreNativesToPlugin(CPlugin *pPlugin) } } +SPVM_NATIVE_FUNC CPluginManager::FindCoreNative(const char *name) +{ + SPVM_NATIVE_FUNC pfn; + + if (!sm_trie_retrieve(m_pCoreNatives, name, (void **)&pfn)) + { + return NULL; + } + + return pfn; +} + void CPluginManager::TryRefreshDependencies(CPlugin *pPlugin) { assert(pPlugin->GetBaseContext() != NULL); diff --git a/core/systems/PluginSys.h b/core/systems/PluginSys.h index cc71c4ea..ce70a16f 100644 --- a/core/systems/PluginSys.h +++ b/core/systems/PluginSys.h @@ -489,6 +489,7 @@ public: } public: bool AddFakeNative(IPluginFunction *pFunction, const char *name, SPVM_FAKENATIVE_FUNC func); + SPVM_NATIVE_FUNC FindCoreNative(const char *name); private: void AddFakeNativesToPlugin(CPlugin *pPlugin); void TryRefreshDependencies(CPlugin *pOther); diff --git a/core/systems/ShareSys.cpp b/core/systems/ShareSys.cpp index 1154e4d4..c3bf2c2d 100644 --- a/core/systems/ShareSys.cpp +++ b/core/systems/ShareSys.cpp @@ -243,3 +243,8 @@ void ShareSystem::RegisterLibrary(IExtension *myself, const char *name) { g_Extensions.AddLibrary(myself, name); } + +void ShareSystem::OverrideNatives(IExtension *myself, const sp_nativeinfo_t *natives) +{ + g_Extensions.OverrideNatives(myself, natives); +} diff --git a/core/systems/ShareSys.h b/core/systems/ShareSys.h index 6c34f618..60e99c21 100644 --- a/core/systems/ShareSys.h +++ b/core/systems/ShareSys.h @@ -81,6 +81,7 @@ public: //IShareSys void DestroyIdentity(IdentityToken_t *identity); void AddDependency(IExtension *myself, const char *filename, bool require, bool autoload); void RegisterLibrary(IExtension *myself, const char *name); + void OverrideNatives(IExtension *myself, const sp_nativeinfo_t *natives); public: //SMGlobalClass /* Pre-empt in case anything tries to register idents early */ void OnSourceModStartup(bool late); diff --git a/extensions/cstrike/Makefile.ep1 b/extensions/cstrike/Makefile.ep1 index d3687b56..6bfeafe5 100644 --- a/extensions/cstrike/Makefile.ep1 +++ b/extensions/cstrike/Makefile.ep1 @@ -13,7 +13,7 @@ HL2SDK = ../../../hl2sdk PROJECT = game.cstrike #Uncomment for SourceMM-enabled extensions -#LINK_HL2 = $(HL2LIB)/tier1_i486.a vstdlib_i486.so tier0_i486.so +LINK_HL2 = $(HL2LIB)/tier1_i486.a vstdlib_i486.so tier0_i486.so OBJECTS = sdk/smsdk_ext.cpp extension.cpp natives.cpp RegNatives.cpp timeleft.cpp diff --git a/extensions/cstrike/Makefile.ep2 b/extensions/cstrike/Makefile.ep2 index e936e671..5852266e 100644 --- a/extensions/cstrike/Makefile.ep2 +++ b/extensions/cstrike/Makefile.ep2 @@ -13,7 +13,7 @@ HL2SDK = ../../../hl2sdk-ob PROJECT = game.cstrike #Uncomment for SourceMM-enabled extensions -#LINK_HL2 = $(HL2LIB)/tier1_i486.a vstdlib_i486.so tier0_i486.so +LINK_HL2 = $(HL2LIB)/tier1_i486.a vstdlib_i486.so tier0_i486.so OBJECTS = sdk/smsdk_ext.cpp extension.cpp natives.cpp RegNatives.cpp timeleft.cpp diff --git a/extensions/cstrike/Makefile.orig b/extensions/cstrike/Makefile.orig index d21ea095..4ae96fd1 100644 --- a/extensions/cstrike/Makefile.orig +++ b/extensions/cstrike/Makefile.orig @@ -13,7 +13,7 @@ HL2SDK = ../../../hl2sdk PROJECT = game.cstrike #Uncomment for SourceMM-enabled extensions -#LINK_HL2 = $(HL2LIB)/tier1_i486.a vstdlib_i486.so tier0_i486.so +LINK_HL2 = $(HL2LIB)/tier1_i486.a vstdlib_i486.so tier0_i486.so OBJECTS = sdk/smsdk_ext.cpp extension.cpp natives.cpp RegNatives.cpp timeleft.cpp diff --git a/extensions/cstrike/extension.cpp b/extensions/cstrike/extension.cpp index 182ae75e..d0b8daa4 100644 --- a/extensions/cstrike/extension.cpp +++ b/extensions/cstrike/extension.cpp @@ -45,6 +45,7 @@ IBinTools *g_pBinTools = NULL; IGameConfig *g_pGameConf = NULL; IGameEventManager2 *gameevents = NULL; bool hooked_everything = false; +int g_msgHintText = -1; SMEXT_LINK(&g_CStrike); @@ -67,6 +68,11 @@ bool CStrike::SDK_OnLoad(char *error, size_t maxlength, bool late) sharesys->AddNatives(myself, g_CSNatives); sharesys->RegisterLibrary(myself, "cstrike"); + if ((g_msgHintText = usermsgs->GetMessageIndex("HintText")) != -1) + { + sharesys->OverrideNatives(myself, g_CS_PrintHintText); + } + return true; } diff --git a/extensions/cstrike/extension.h b/extensions/cstrike/extension.h index 916bb2d2..acf301f3 100644 --- a/extensions/cstrike/extension.h +++ b/extensions/cstrike/extension.h @@ -122,5 +122,7 @@ public: /* Interfaces from SourceMod */ extern IBinTools *g_pBinTools; extern IGameConfig *g_pGameConf; +extern int g_msgHintText; +extern sp_nativeinfo_t g_CS_PrintHintText[]; #endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ diff --git a/extensions/cstrike/natives.cpp b/extensions/cstrike/natives.cpp index e5f085c4..ce711e68 100644 --- a/extensions/cstrike/natives.cpp +++ b/extensions/cstrike/natives.cpp @@ -69,6 +69,45 @@ inline CBaseEntity *GetCBaseEntity(int num, bool isplayer) return pUnk->GetBaseEntity(); } +static cell_t CS_PrintHintText(IPluginContext *pContext, const cell_t *params) +{ + int client = params[1]; + IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(params[1]); + + if (!pPlayer) + { + return pContext->ThrowNativeError("Client index %d is invalid", client); + } + + if (!pPlayer->IsInGame()) + { + return pContext->ThrowNativeError("Client %d is not in game", client); + } + + g_pSM->SetGlobalTarget(client); + + char buffer[192]; + g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2); + + /* Check for an error before printing to the client */ + if (pContext->GetContext()->n_err != SP_ERROR_NONE) + { + return 0; + } + + bf_write *pBitBuf = usermsgs->StartMessage(g_msgHintText, ¶ms[1], 1, USERMSG_RELIABLE); + if (pBitBuf == NULL) + { + return pContext->ThrowNativeError("Could not send a usermessage"); + } + pBitBuf->WriteByte(1); + pBitBuf->WriteString(buffer); + usermsgs->EndMessage(); + + return 1; +} + + static cell_t CS_RespawnPlayer(IPluginContext *pContext, const cell_t *params) { static ICallWrapper *pWrapper = NULL; @@ -124,3 +163,9 @@ sp_nativeinfo_t g_CSNatives[] = {"CS_SwitchTeam", CS_SwitchTeam}, {NULL, NULL} }; + +sp_nativeinfo_t g_CS_PrintHintText[] = +{ + {"PrintHintText", CS_PrintHintText}, + {NULL, NULL}, +}; diff --git a/extensions/cstrike/sdk/smsdk_config.h b/extensions/cstrike/sdk/smsdk_config.h index fc402227..a070e6b3 100644 --- a/extensions/cstrike/sdk/smsdk_config.h +++ b/extensions/cstrike/sdk/smsdk_config.h @@ -71,5 +71,6 @@ #define SMEXT_ENABLE_TIMERSYS //#define SMEXT_ENABLE_THREADER //#define SMEXT_ENABLE_LIBSYS +#define SMEXT_ENABLE_USERMSGS #endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ diff --git a/extensions/cstrike/sdk/smsdk_ext.cpp b/extensions/cstrike/sdk/smsdk_ext.cpp index 60480b22..f5e6f3fc 100644 --- a/extensions/cstrike/sdk/smsdk_ext.cpp +++ b/extensions/cstrike/sdk/smsdk_ext.cpp @@ -79,6 +79,9 @@ IThreader *threader = NULL; #if defined SMEXT_ENABLE_LIBSYS ILibrarySys *libsys = NULL; #endif +#if defined SMEXT_ENABLE_USERMSGS +IUserMessages *usermsgs = NULL; +#endif /** Exports the main interface */ PLATFORM_EXTERN_C IExtensionInterface *GetSMExtAPI() @@ -149,6 +152,9 @@ bool SDKExtension::OnExtensionLoad(IExtension *me, IShareSys *sys, char *error, #if defined SMEXT_ENABLE_LIBSYS SM_GET_IFACE(LIBRARYSYS, libsys); #endif +#if defined SMEXT_ENABLE_USERMSGS + SM_GET_IFACE(USERMSGS, usermsgs); +#endif if (SDK_OnLoad(error, maxlength, late)) { diff --git a/extensions/cstrike/sdk/smsdk_ext.h b/extensions/cstrike/sdk/smsdk_ext.h index 905e13a6..c137616b 100644 --- a/extensions/cstrike/sdk/smsdk_ext.h +++ b/extensions/cstrike/sdk/smsdk_ext.h @@ -73,6 +73,9 @@ #if defined SMEXT_ENABLE_LIBSYS #include #endif +#if defined SMEXT_ENABLE_USERMSGS +#include +#endif #if defined SMEXT_CONF_METAMOD #include @@ -260,6 +263,9 @@ extern IThreader *threader; #if defined SMEXT_ENABLE_LIBSYS extern ILibrarySys *libsys; #endif +#if defined SMEXT_ENABLE_USERMSGS +extern IUserMessages *usermsgs; +#endif #if defined SMEXT_CONF_METAMOD PLUGIN_GLOBALVARS(); diff --git a/extensions/topmenus/TopMenu.cpp b/extensions/topmenus/TopMenu.cpp index bdb048a5..53b266ec 100644 --- a/extensions/topmenus/TopMenu.cpp +++ b/extensions/topmenus/TopMenu.cpp @@ -1,1105 +1,1119 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * SourceMod Sample Extension - * Copyright (C) 2004-2007 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 -#include -#include "TopMenu.h" - -struct obj_by_name_t -{ - unsigned int obj_index; - char name[64]; -}; - -int _SortObjectNamesDescending(const void *ptr1, const void *ptr2); -unsigned int strncopy(char *dest, const char *src, size_t count); -size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...); - -TopMenu::TopMenu(ITopMenuObjectCallbacks *callbacks) -{ - m_clients = NULL; - m_SerialNo = 1; - m_pTitle = callbacks; - m_max_clients = 0; - - if (playerhelpers->IsServerActivated()) - { - CreatePlayers(playerhelpers->GetMaxClients()); - } -} - -TopMenu::~TopMenu() -{ - /* Delete all categories */ - while (m_Categories.size()) - { - RemoveFromMenu(m_Categories[0]->obj->object_id); - } - - /* Remove all objects */ - for (size_t i = 0; i < m_Objects.size(); i++) - { - assert(m_Objects[i]->is_free == true); - delete m_Objects[i]; - } - - m_pTitle->OnTopMenuObjectRemoved(this, 0); - - /* Delete all cached config entries */ - for (size_t i = 0; i < m_Config.cats.size(); i++) - { - delete m_Config.cats[i]; - } - - /* Sweep players */ - for (size_t i = 0; i <= (size_t)m_max_clients; i++) - { - TearDownClient(&m_clients[i]); - } - delete [] m_clients; -} - -void TopMenu::OnClientConnected(int client) -{ - if (m_clients == NULL) - { - return; - } - - topmenu_player_t *player = &m_clients[client]; - TearDownClient(player); -} - -void TopMenu::OnClientDisconnected(int client) -{ - if (m_clients == NULL) - { - return; - } - - topmenu_player_t *player = &m_clients[client]; - TearDownClient(player); -} - -void TopMenu::OnServerActivated(int max_clients) -{ - if (m_clients == NULL) - { - CreatePlayers(max_clients); - } -} - -unsigned int TopMenu::AddToMenu(const char *name, - TopMenuObjectType type, - ITopMenuObjectCallbacks *callbacks, - IdentityToken_t *owner, - const char *cmdname, - FlagBits flags, - unsigned int parent) -{ - return AddToMenu2(name, type, callbacks, owner, cmdname, flags, parent, NULL); -} - -unsigned int TopMenu::AddToMenu2(const char *name, - TopMenuObjectType type, - ITopMenuObjectCallbacks *callbacks, - IdentityToken_t *owner, - const char *cmdname, - FlagBits flags, - unsigned int parent, - const char *info_string) -{ - /* Sanity checks */ - if (type == TopMenuObject_Category && parent != 0) - { - return 0; - } - else if (type == TopMenuObject_Item && parent == 0) - { - return 0; - } - else if (m_ObjLookup.retrieve(name) != NULL) - { - return 0; - } - else if (type != TopMenuObject_Item && type != TopMenuObject_Category) - { - return 0; - } - - /* If we're adding an item, make sure the parent is valid, - * and that the parent is a category. - */ - topmenu_object_t *parent_obj = NULL; - topmenu_category_t *parent_cat = NULL; - if (type == TopMenuObject_Item) - { - /* Check parent index. Note it will be >= 1 here. */ - if (parent > m_Objects.size() || m_Objects[parent - 1]->is_free) - { - return 0; - } - parent_obj = m_Objects[parent - 1]; - - /* Find an equivalent pointer in the category array. */ - for (size_t i = 0; i < m_Categories.size(); i++) - { - if (m_Categories[i]->obj == parent_obj) - { - parent_cat = m_Categories[i]; - break; - } - } - - /* If none was found, leave. */ - if (parent_cat == NULL) - { - return 0; - } - } - - /* Re-use an old object pointer if we can. */ - topmenu_object_t *obj = NULL; - for (size_t i = 0; i < m_Objects.size(); i++) - { - if (m_Objects[i]->is_free == true) - { - obj = m_Objects[i]; - break; - } - } - - /* Otherwise, allocate a new one. */ - if (obj == NULL) - { - obj = new topmenu_object_t; - obj->object_id = ((unsigned int)m_Objects.size()) + 1; - m_Objects.push_back(obj); - } - - /* Initialize the object's properties. */ - obj->callbacks = callbacks; - obj->flags = flags; - obj->owner = owner; - obj->type = type; - obj->is_free = false; - obj->parent = parent_obj; - strncopy(obj->name, name, sizeof(obj->name)); - strncopy(obj->cmdname, cmdname ? cmdname : "", sizeof(obj->cmdname)); - strncopy(obj->info, info_string ? info_string : "", sizeof(obj->info)); - - if (obj->type == TopMenuObject_Category) - { - /* Create a new category entry */ - topmenu_category_t *cat = new topmenu_category_t; - cat->obj = obj; - cat->reorder = false; - cat->serial = 1; - - /* Add it, then update our serial change number. */ - m_Categories.push_back(cat); - m_SerialNo++; - - /* Updating sorting info */ - m_bCatsNeedResort = true; - } - else if (obj->type == TopMenuObject_Item) - { - /* Update the category, mark it as needing changes */ - parent_cat->obj_list.push_back(obj); - parent_cat->reorder = true; - parent_cat->serial++; - - /* If the category just went from 0 to 1 items, mark it as - * changed, so clients get the category drawn. - */ - if (parent_cat->obj_list.size() == 1) - { - m_SerialNo++; - } - } - - m_ObjLookup.insert(name, obj); - - return obj->object_id; -} - -const char *TopMenu::GetObjectInfoString(unsigned int object_id) -{ - if (object_id == 0 - || object_id > m_Objects.size() - || m_Objects[object_id - 1]->is_free) - { - return NULL; - } - - topmenu_object_t *obj = m_Objects[object_id - 1]; - - return obj->info; -} - -void TopMenu::RemoveFromMenu(unsigned int object_id) -{ - if (object_id == 0 - || object_id > m_Objects.size() - || m_Objects[object_id - 1]->is_free) - { - return; - } - - topmenu_object_t *obj = m_Objects[object_id - 1]; - - m_ObjLookup.remove(obj->name); - - if (obj->type == TopMenuObject_Category) - { - /* Find it in the category list. */ - for (size_t i = 0; i < m_Categories.size(); i++) - { - if (m_Categories[i]->obj == obj) - { - /* Mark all children as removed + free. Note we could - * call into RemoveMenuItem() for this, but it'd be very - * inefficient! - */ - topmenu_category_t *cat = m_Categories[i]; - for (size_t j = 0; j < m_Categories[i]->obj_list.size(); j++) - { - cat->obj_list[j]->callbacks->OnTopMenuObjectRemoved(this, cat->obj_list[j]->object_id); - cat->obj_list[j]->is_free = true; - } - - /* Remove the category from the list, then delete it. */ - m_Categories.erase(m_Categories.iterAt(i)); - delete cat; - break; - } - } - - /* Update the root as changed. */ - m_SerialNo++; - m_bCatsNeedResort = true; - } - else if (obj->type == TopMenuObject_Item) - { - /* Find the category this item is in. */ - topmenu_category_t *parent_cat = NULL; - for (size_t i = 0; i < m_Categories.size(); i++) - { - if (m_Categories[i]->obj == obj->parent) - { - parent_cat = m_Categories[i]; - break; - } - } - - /* Erase it from the category's lists. */ - if (parent_cat) - { - for (size_t i = 0; i < parent_cat->obj_list.size(); i++) - { - if (parent_cat->obj_list[i] == obj) - { - parent_cat->obj_list.erase(parent_cat->obj_list.iterAt(i)); - - /* If this category now has no items, mark root as changed - * so clients won't get the category drawn anymore. - */ - if (parent_cat->obj_list.size() == 0) - { - m_SerialNo++; - } - break; - } - } - - /* Update the category as changed. */ - parent_cat->reorder = true; - parent_cat->serial++; - } - } - - /* The callbacks pointer is still valid, so fire away! */ - obj->callbacks->OnTopMenuObjectRemoved(this, object_id); - - /* Finally, mark the object as free. */ - obj->is_free = true; -} - -bool TopMenu::DisplayMenu(int client, unsigned int hold_time, TopMenuPosition position) -{ - if (m_clients == NULL) - { - return false; - } - - IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(client); - if (!pPlayer->IsInGame()) - { - return false; - } - - UpdateClientRoot(client, pPlayer); - - topmenu_player_t *pClient = &m_clients[client]; - if (pClient->root == NULL) - { - return false; - } - - bool return_value = false; - - if (position == TopMenuPosition_LastCategory && - pClient->last_category < m_Categories.size()) - { - return_value = DisplayCategory(client, pClient->last_category, hold_time, true); - if (!return_value) - { - return_value = pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos); - } - } - else if (position == TopMenuPosition_LastRoot) - { - pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos); - } - else if (position == TopMenuPosition_Start) - { - pClient->last_position = 0; - pClient->last_category = 0; - return_value = pClient->root->Display(client, hold_time); - } - - return return_value; -} - -bool TopMenu::DisplayCategory(int client, unsigned int category, unsigned int hold_time, bool last_position) -{ - UpdateClientCategory(client, category); - - topmenu_player_t *pClient = &m_clients[client]; - if (category >= pClient->cat_count - || pClient->cats[category].menu == NULL) - { - return false; - } - - bool return_value = false; - - topmenu_player_category_t *player_cat = &(pClient->cats[category]); - - pClient->last_category = category; - if (last_position) - { - return_value = player_cat->menu->DisplayAtItem(client, hold_time, pClient->last_position); - } - else - { - return_value = player_cat->menu->Display(client, hold_time); - } - - return return_value; -} - -void TopMenu::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page) -{ - const char *item_name = menu->GetItemInfo(item, NULL); - if (!item_name) - { - return; - } - - topmenu_object_t *obj; - topmenu_player_t *pClient = &m_clients[client]; - topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name); - if (pObject == NULL) - { - return; - } - - obj = *pObject; - - /* We now have the object... what do we do with it? */ - if (obj->type == TopMenuObject_Category) - { - /* If it's a category, the user wants to view it.. */ - for (size_t i = 0; i < m_Categories.size(); i++) - { - if (m_Categories[i]->obj == obj) - { - pClient->last_root_pos = item_on_page; - if (!DisplayCategory(client, (unsigned int)i, MENU_TIME_FOREVER, false)) - { - /* If we can't display the category, re-display the root menu */ - DisplayMenu(client, MENU_TIME_FOREVER, TopMenuPosition_LastRoot); - } - break; - } - } - } - else - { - pClient->last_position = item_on_page; - - /* Re-check access in case this user had their credentials revoked */ - if (obj->cmdname[0] != '\0' && !adminsys->CheckAccess(client, obj->cmdname, obj->flags, false)) - { - DisplayMenu(client, 0, TopMenuPosition_LastCategory); - return; - } - - /* Pass the information on to the callback */ - obj->callbacks->OnTopMenuSelectOption(this, client, obj->object_id); - } -} - -void TopMenu::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style) -{ - const char *item_name = menu->GetItemInfo(item, NULL); - if (!item_name) - { - return; - } - - topmenu_object_t *obj; - topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name); - if (pObject == NULL) - { - return; - } - - obj = *pObject; - - style = obj->callbacks->OnTopMenuDrawOption(this, client, obj->object_id); - if (style != ITEMDRAW_DEFAULT) - { - return; - } - - if (obj->cmdname[0] == '\0') - { - return; - } - - if (!adminsys->CheckAccess(client, obj->cmdname, obj->flags, false)) - { - style = ITEMDRAW_IGNORE; - } -} - -unsigned int TopMenu::OnMenuDisplayItem(IBaseMenu *menu, - int client, - IMenuPanel *panel, - unsigned int item, - const ItemDrawInfo &dr) -{ - const char *item_name = menu->GetItemInfo(item, NULL); - if (!item_name) - { - return 0; - } - - topmenu_object_t *obj; - topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name); - if (pObject == NULL) - { - return 0; - } - - obj = *pObject; - - /* Ask the object to render the text for this client */ - char renderbuf[64]; - obj->callbacks->OnTopMenuDisplayOption(this, client, obj->object_id, renderbuf, sizeof(renderbuf)); - - /* Build the new draw info */ - ItemDrawInfo new_dr = dr; - new_dr.display = renderbuf; - - /* Man I love the menu API. Ask the panel to draw the item and give the position - * back to Core's renderer. This way we don't have to worry about keeping the - * render buffer static! - */ - return panel->DrawItem(new_dr); -} - -void TopMenu::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) -{ - if (reason == MenuCancel_ExitBack) - { - /* If the client chose exit back, they were on a category menu, and we can - * now display the root menu from the last known position. - */ - DisplayMenu(client, 0, TopMenuPosition_LastRoot); - } -} - -void TopMenu::UpdateClientRoot(int client, IGamePlayer *pGamePlayer) -{ - topmenu_player_t *pClient = &m_clients[client]; - IGamePlayer *pPlayer = pGamePlayer ? pGamePlayer : playerhelpers->GetGamePlayer(client); - - /* Determine if an update is necessary */ - bool is_update_needed = false; - if (pClient->menu_serial != m_SerialNo) - { - is_update_needed = true; - } - else if (pPlayer->GetUserId() != pClient->user_id) - { - is_update_needed = true; - } - - /* If no update is needed at the root level, just leave now */ - if (!is_update_needed) - { - return; - } - - /* First we need to flush the cache... */ - TearDownClient(pClient); - - /* Now, rebuild the category list, but don't create menus */ - if (m_Categories.size() == 0) - { - pClient->cat_count = 0; - pClient->cats = NULL; - } - else - { - pClient->cat_count = (unsigned int)m_Categories.size(); - pClient->cats = new topmenu_player_category_t[pClient->cat_count]; - memset(pClient->cats, 0, sizeof(topmenu_player_category_t) * pClient->cat_count); - } - - /* Re-sort the root categories if needed */ - SortCategoriesIfNeeded(); - - /* Build the root menu */ - IBaseMenu *root_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity()); - - /* Add the sorted items */ - for (size_t i = 0; i < m_SortedCats.size(); i++) - { - if (m_Categories[m_SortedCats[i]]->obj_list.size() == 0) - { - continue; - } - root_menu->AppendItem(m_Categories[m_SortedCats[i]]->obj->name, ItemDrawInfo("")); - } - - /* Now we need to handle un-sorted items. This is slightly trickier, as we need to - * pre-render each category name, and cache those names. Phew! - */ - if (m_UnsortedCats.size()) - { - obj_by_name_t *item_list = new obj_by_name_t[m_UnsortedCats.size()]; - for (size_t i = 0; i < m_UnsortedCats.size(); i++) - { - obj_by_name_t *temp_obj = &item_list[i]; - topmenu_object_t *obj = m_Categories[m_UnsortedCats[i]]->obj; - obj->callbacks->OnTopMenuDisplayOption(this, - client, - obj->object_id, - temp_obj->name, - sizeof(temp_obj->name)); - temp_obj->obj_index = m_UnsortedCats[i]; - } - - /* Sort our temp list */ - qsort(item_list, m_UnsortedCats.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending); - - /* Add the new sorted categories */ - for (size_t i = 0; i < m_UnsortedCats.size(); i++) - { - if (m_Categories[item_list[i].obj_index]->obj_list.size() == 0) - { - continue; - } - root_menu->AppendItem(m_Categories[item_list[i].obj_index]->obj->name, ItemDrawInfo("")); - } - - delete [] item_list; - } - - /* Set the menu's title */ - char renderbuf[128]; - m_pTitle->OnTopMenuDisplayTitle(this, client, 0, renderbuf, sizeof(renderbuf)); - root_menu->SetDefaultTitle(renderbuf); - - /* The client is now fully updated */ - pClient->root = root_menu; - pClient->user_id = pPlayer->GetUserId(); - pClient->menu_serial = m_SerialNo; - pClient->last_position = 0; - pClient->last_category = 0; - pClient->last_root_pos = 0; -} - -void TopMenu::UpdateClientCategory(int client, unsigned int category) -{ - /* Update the client's root menu just in case it needs it. This - * will validate that we have both a valid client and a valid - * category structure for that client. - */ - UpdateClientRoot(client); - - /* Now it's guaranteed that our category tables will be usable */ - topmenu_player_t *pClient = &m_clients[client]; - topmenu_category_t *cat = m_Categories[category]; - topmenu_player_category_t *player_cat = &(pClient->cats[category]); - - /* Does the category actually need updating? */ - if (player_cat->serial == cat->serial) - { - return; - } - - /* Destroy any existing menu */ - if (player_cat->menu) - { - player_cat->menu->Destroy(); - player_cat->menu = NULL; - } - - if (pClient->last_category == category) - { - pClient->last_position = 0; - } - - IBaseMenu *cat_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity()); - - /* Categories get an "exit back" button */ - cat_menu->SetMenuOptionFlags(cat_menu->GetMenuOptionFlags() | MENUFLAG_BUTTON_EXITBACK); - - /* Re-sort the category if needed */ - SortCategoryIfNeeded(category); - - /* Build the menu with the sorted items first */ - for (size_t i = 0; i < cat->sorted.size(); i++) - { - cat_menu->AppendItem(cat->sorted[i]->name, ItemDrawInfo("")); - } - - /* Now handle unsorted items */ - if (cat->unsorted.size()) - { - /* Build a list of the item names */ - obj_by_name_t *item_list = new obj_by_name_t[cat->unsorted.size()]; - for (size_t i = 0; i < cat->unsorted.size(); i++) - { - obj_by_name_t *item = &item_list[i]; - topmenu_object_t *obj = cat->unsorted[i]; - obj->callbacks->OnTopMenuDisplayOption(this, - client, - obj->object_id, - item->name, - sizeof(item->name)); - item->obj_index = (unsigned int)i; - } - - /* Sort the names */ - qsort(item_list, cat->unsorted.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending); - - /* Add to the menu */ - for (size_t i = 0; i < cat->unsorted.size(); i++) - { - cat_menu->AppendItem(cat->unsorted[item_list[i].obj_index]->name, ItemDrawInfo("")); - } - - delete [] item_list; - } - - /* Set the menu's title */ - char renderbuf[128]; - cat->obj->callbacks->OnTopMenuDisplayTitle(this, - client, - cat->obj->object_id, - renderbuf, - sizeof(renderbuf)); - cat_menu->SetDefaultTitle(renderbuf); - - /* We're done! */ - player_cat->menu = cat_menu; - player_cat->serial = cat->serial; -} - -void TopMenu::SortCategoryIfNeeded(unsigned int category) -{ - topmenu_category_t *cat = m_Categories[category]; - if (!cat->reorder) - { - return; - } - - cat->sorted.clear(); - cat->unsorted.clear(); - - if (cat->obj_list.size() == 0) - { - cat->reorder = false; - return; - } - - CVector to_sort; - for (size_t i = 0; i < cat->obj_list.size(); i++) - { - to_sort.push_back(i); - } - - /* Find a matching category in the configs */ - config_category_t *config_cat = NULL; - for (size_t i = 0; i < m_Config.cats.size(); i++) - { - if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), cat->obj->name) == 0) - { - config_cat = m_Config.cats[i]; - break; - } - } - - /* If there is a matching category, build a pre-sorted item list */ - if (config_cat != NULL) - { - /* Find matching objects in this category */ - for (size_t i = 0; i < config_cat->commands.size(); i++) - { - const char *config_name = m_Config.strings.GetString(config_cat->commands[i]); - for (size_t j = 0; j < to_sort.size(); j++) - { - if (strcmp(config_name, cat->obj_list[to_sort[j]]->name) == 0) - { - /* Place in the final list, then remove from the temporary list */ - cat->sorted.push_back(cat->obj_list[to_sort[j]]); - to_sort.erase(to_sort.iterAt(j)); - break; - } - } - } - } - - /* Push any remaining items onto the unsorted list */ - for (size_t i = 0; i < to_sort.size(); i++) - { - cat->unsorted.push_back(cat->obj_list[to_sort[i]]); - } - - cat->reorder = false; -} - -void TopMenu::SortCategoriesIfNeeded() -{ - if (!m_bCatsNeedResort) - { - return; - } - - /* Clear sort results */ - m_SortedCats.clear(); - m_UnsortedCats.clear(); - - if (m_Categories.size() == 0) - { - m_bCatsNeedResort = false; - return; - } - - CVector to_sort; - for (unsigned int i = 0; i < (unsigned int)m_Categories.size(); i++) - { - to_sort.push_back(i); - } - - /* If we have any predefined categories, add them in as they appear. */ - for (size_t i= 0; i < m_Config.cats.size(); i++) - { - /* Find this category and map it in if we can */ - for (size_t j = 0; j < to_sort.size(); j++) - { - if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), - m_Categories[to_sort[j]]->obj->name) == 0) - { - /* Add to the real list and remove from the temporary */ - m_SortedCats.push_back(to_sort[j]); - to_sort.erase(to_sort.iterAt(j)); - break; - } - } - } - - /* Push any remaining items onto the unsorted list */ - for (size_t i = 0; i < to_sort.size(); i++) - { - m_UnsortedCats.push_back(to_sort[i]); - } - - m_bCatsNeedResort = false; -} - -void TopMenu::CreatePlayers(int max_clients) -{ - m_max_clients = max_clients; - m_clients = (topmenu_player_t *)malloc(sizeof(topmenu_player_t) * (max_clients + 1)); - memset(m_clients, 0, sizeof(topmenu_player_t) * (max_clients + 1)); -} - -void TopMenu::TearDownClient(topmenu_player_t *player) -{ - if (player->cats != NULL) - { - for (unsigned int i = 0; i < player->cat_count; i++) - { - topmenu_player_category_t *player_cat = &(player->cats[i]); - if (player_cat->menu != NULL) - { - player_cat->menu->Destroy(); - } - } - delete [] player->cats; - } - - if (player->root != NULL) - { - player->root->Destroy(); - } - - memset(player, 0, sizeof(topmenu_player_t)); -} - -bool TopMenu::LoadConfiguration(const char *file, char *error, size_t maxlength) -{ - SMCError err; - SMCStates states; - - if ((err = textparsers->ParseFile_SMC(file, this, &states)) - != SMCError_Okay) - { - const char *err_string = textparsers->GetSMCErrorString(err); - if (!err_string) - { - err_string = "Unknown"; - } - - UTIL_Format(error, maxlength, "%s", err_string); - - return false; - } - - return true; -} - -bool TopMenu::OnIdentityRemoval(IdentityToken_t *owner) -{ - /* First sweep the categories owned by us */ - CVector obj_list; - for (size_t i = 0; i < m_Categories.size(); i++) - { - if (m_Categories[i]->obj->owner == owner) - { - obj_list.push_back(m_Categories[i]->obj->object_id); - } - } - - for (size_t i = 0; i < obj_list.size(); i++) - { - RemoveFromMenu(obj_list[i]); - } - - /* Now we can look for actual items */ - for (size_t i = 0; i < m_Objects.size(); i++) - { - if (m_Objects[i]->is_free) - { - continue; - } - if (m_Objects[i]->owner == owner) - { - assert(m_Objects[i]->type != TopMenuObject_Category); - RemoveFromMenu(m_Objects[i]->object_id); - } - } - - return true; -} - -#define PARSE_STATE_NONE 0 -#define PARSE_STATE_MAIN 1 -#define PARSE_STATE_CATEGORY 2 -unsigned int ignore_parse_level = 0; -unsigned int current_parse_state = 0; -config_category_t *cur_cat = NULL; - -void TopMenu::ReadSMC_ParseStart() -{ - current_parse_state = PARSE_STATE_NONE; - ignore_parse_level = 0; - cur_cat = NULL; - - /* Reset the old config */ - m_Config.strings.Reset(); - for (size_t i = 0; i < m_Config.cats.size(); i++) - { - delete m_Config.cats[i]; - } - m_Config.cats.clear(); -} - -SMCResult TopMenu::ReadSMC_NewSection(const SMCStates *states, const char *name) -{ - if (ignore_parse_level) - { - ignore_parse_level++; - } - else - { - if (current_parse_state == PARSE_STATE_NONE) - { - if (strcmp(name, "Menu") == 0) - { - current_parse_state = PARSE_STATE_MAIN; - } - else - { - ignore_parse_level = 1; - } - } - else if (current_parse_state == PARSE_STATE_MAIN) - { - cur_cat = new config_category_t; - cur_cat->name = m_Config.strings.AddString(name); - m_Config.cats.push_back(cur_cat); - current_parse_state = PARSE_STATE_CATEGORY; - } - else - { - ignore_parse_level = 1; - } - } - - return SMCResult_Continue; -} - -SMCResult TopMenu::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value) -{ - if (ignore_parse_level > 0 - || current_parse_state != PARSE_STATE_CATEGORY - || cur_cat == NULL) - { - return SMCResult_Continue; - } - - if (strcmp(key, "item") == 0) - { - cur_cat->commands.push_back(m_Config.strings.AddString(value)); - } - - return SMCResult_Continue; -} - -SMCResult TopMenu::ReadSMC_LeavingSection(const SMCStates *states) -{ - if (ignore_parse_level) - { - ignore_parse_level--; - } - else - { - if (current_parse_state == PARSE_STATE_CATEGORY) - { - cur_cat = NULL; - current_parse_state = PARSE_STATE_MAIN; - } - else if (current_parse_state == PARSE_STATE_MAIN) - { - current_parse_state = PARSE_STATE_NONE; - } - } - - return SMCResult_Continue; -} - -unsigned int TopMenu::FindCategory(const char *name) -{ - topmenu_object_t **p_obj = m_ObjLookup.retrieve(name); - if (!p_obj) - { - return 0; - } - - topmenu_object_t *obj = *p_obj; - if (obj->type != TopMenuObject_Category) - { - return 0; - } - - return obj->object_id; -} - -int _SortObjectNamesDescending(const void *ptr1, const void *ptr2) -{ - obj_by_name_t *obj1 = (obj_by_name_t *)ptr1; - obj_by_name_t *obj2 = (obj_by_name_t *)ptr2; - return strcmp(obj1->name, obj2->name); -} - -unsigned int strncopy(char *dest, const char *src, size_t count) -{ - if (!count) - { - return 0; - } - - char *start = dest; - while ((*src) && (--count)) - { - *dest++ = *src++; - } - *dest = '\0'; - - return (dest - start); -} - -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; - } -} +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Sample Extension + * Copyright (C) 2004-2007 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 +#include +#include "TopMenu.h" + +struct obj_by_name_t +{ + unsigned int obj_index; + char name[64]; +}; + +int _SortObjectNamesDescending(const void *ptr1, const void *ptr2); +unsigned int strncopy(char *dest, const char *src, size_t count); +size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...); + +TopMenu::TopMenu(ITopMenuObjectCallbacks *callbacks) +{ + m_clients = NULL; + m_SerialNo = 1; + m_pTitle = callbacks; + m_max_clients = 0; + + if (playerhelpers->IsServerActivated()) + { + CreatePlayers(playerhelpers->GetMaxClients()); + } +} + +TopMenu::~TopMenu() +{ + /* Delete all categories */ + while (m_Categories.size()) + { + RemoveFromMenu(m_Categories[0]->obj->object_id); + } + + /* Remove all objects */ + for (size_t i = 0; i < m_Objects.size(); i++) + { + assert(m_Objects[i]->is_free == true); + delete m_Objects[i]; + } + + m_pTitle->OnTopMenuObjectRemoved(this, 0); + + /* Delete all cached config entries */ + for (size_t i = 0; i < m_Config.cats.size(); i++) + { + delete m_Config.cats[i]; + } + + /* Sweep players */ + for (size_t i = 0; i <= (size_t)m_max_clients; i++) + { + TearDownClient(&m_clients[i]); + } + delete [] m_clients; +} + +void TopMenu::OnClientConnected(int client) +{ + if (m_clients == NULL) + { + return; + } + + topmenu_player_t *player = &m_clients[client]; + TearDownClient(player); +} + +void TopMenu::OnClientDisconnected(int client) +{ + if (m_clients == NULL) + { + return; + } + + topmenu_player_t *player = &m_clients[client]; + TearDownClient(player); +} + +void TopMenu::OnServerActivated(int max_clients) +{ + if (m_clients == NULL) + { + CreatePlayers(max_clients); + } +} + +unsigned int TopMenu::AddToMenu(const char *name, + TopMenuObjectType type, + ITopMenuObjectCallbacks *callbacks, + IdentityToken_t *owner, + const char *cmdname, + FlagBits flags, + unsigned int parent) +{ + return AddToMenu2(name, type, callbacks, owner, cmdname, flags, parent, NULL); +} + +unsigned int TopMenu::AddToMenu2(const char *name, + TopMenuObjectType type, + ITopMenuObjectCallbacks *callbacks, + IdentityToken_t *owner, + const char *cmdname, + FlagBits flags, + unsigned int parent, + const char *info_string) +{ + /* Sanity checks */ + if (type == TopMenuObject_Category && parent != 0) + { + return 0; + } + else if (type == TopMenuObject_Item && parent == 0) + { + return 0; + } + else if (m_ObjLookup.retrieve(name) != NULL) + { + return 0; + } + else if (type != TopMenuObject_Item && type != TopMenuObject_Category) + { + return 0; + } + + /* If we're adding an item, make sure the parent is valid, + * and that the parent is a category. + */ + topmenu_object_t *parent_obj = NULL; + topmenu_category_t *parent_cat = NULL; + if (type == TopMenuObject_Item) + { + /* Check parent index. Note it will be >= 1 here. */ + if (parent > m_Objects.size() || m_Objects[parent - 1]->is_free) + { + return 0; + } + parent_obj = m_Objects[parent - 1]; + + /* Find an equivalent pointer in the category array. */ + for (size_t i = 0; i < m_Categories.size(); i++) + { + if (m_Categories[i]->obj == parent_obj) + { + parent_cat = m_Categories[i]; + break; + } + } + + /* If none was found, leave. */ + if (parent_cat == NULL) + { + return 0; + } + } + + /* Re-use an old object pointer if we can. */ + topmenu_object_t *obj = NULL; + for (size_t i = 0; i < m_Objects.size(); i++) + { + if (m_Objects[i]->is_free == true) + { + obj = m_Objects[i]; + break; + } + } + + /* Otherwise, allocate a new one. */ + if (obj == NULL) + { + obj = new topmenu_object_t; + obj->object_id = ((unsigned int)m_Objects.size()) + 1; + m_Objects.push_back(obj); + } + + /* Initialize the object's properties. */ + obj->callbacks = callbacks; + obj->flags = flags; + obj->owner = owner; + obj->type = type; + obj->is_free = false; + obj->parent = parent_obj; + strncopy(obj->name, name, sizeof(obj->name)); + strncopy(obj->cmdname, cmdname ? cmdname : "", sizeof(obj->cmdname)); + strncopy(obj->info, info_string ? info_string : "", sizeof(obj->info)); + + if (obj->type == TopMenuObject_Category) + { + /* Create a new category entry */ + topmenu_category_t *cat = new topmenu_category_t; + cat->obj = obj; + cat->reorder = false; + cat->serial = 1; + + /* Add it, then update our serial change number. */ + m_Categories.push_back(cat); + m_SerialNo++; + + /* Updating sorting info */ + m_bCatsNeedResort = true; + } + else if (obj->type == TopMenuObject_Item) + { + /* Update the category, mark it as needing changes */ + parent_cat->obj_list.push_back(obj); + parent_cat->reorder = true; + parent_cat->serial++; + + /* If the category just went from 0 to 1 items, mark it as + * changed, so clients get the category drawn. + */ + if (parent_cat->obj_list.size() == 1) + { + m_SerialNo++; + } + } + + m_ObjLookup.insert(name, obj); + + return obj->object_id; +} + +const char *TopMenu::GetObjectInfoString(unsigned int object_id) +{ + if (object_id == 0 + || object_id > m_Objects.size() + || m_Objects[object_id - 1]->is_free) + { + return NULL; + } + + topmenu_object_t *obj = m_Objects[object_id - 1]; + + return obj->info; +} + +const char *TopMenu::GetObjectName(unsigned int object_id) +{ + if (object_id == 0 + || object_id > m_Objects.size() + || m_Objects[object_id - 1]->is_free) + { + return NULL; + } + + topmenu_object_t *obj = m_Objects[object_id - 1]; + + return obj->name; +} + +void TopMenu::RemoveFromMenu(unsigned int object_id) +{ + if (object_id == 0 + || object_id > m_Objects.size() + || m_Objects[object_id - 1]->is_free) + { + return; + } + + topmenu_object_t *obj = m_Objects[object_id - 1]; + + m_ObjLookup.remove(obj->name); + + if (obj->type == TopMenuObject_Category) + { + /* Find it in the category list. */ + for (size_t i = 0; i < m_Categories.size(); i++) + { + if (m_Categories[i]->obj == obj) + { + /* Mark all children as removed + free. Note we could + * call into RemoveMenuItem() for this, but it'd be very + * inefficient! + */ + topmenu_category_t *cat = m_Categories[i]; + for (size_t j = 0; j < m_Categories[i]->obj_list.size(); j++) + { + cat->obj_list[j]->callbacks->OnTopMenuObjectRemoved(this, cat->obj_list[j]->object_id); + cat->obj_list[j]->is_free = true; + } + + /* Remove the category from the list, then delete it. */ + m_Categories.erase(m_Categories.iterAt(i)); + delete cat; + break; + } + } + + /* Update the root as changed. */ + m_SerialNo++; + m_bCatsNeedResort = true; + } + else if (obj->type == TopMenuObject_Item) + { + /* Find the category this item is in. */ + topmenu_category_t *parent_cat = NULL; + for (size_t i = 0; i < m_Categories.size(); i++) + { + if (m_Categories[i]->obj == obj->parent) + { + parent_cat = m_Categories[i]; + break; + } + } + + /* Erase it from the category's lists. */ + if (parent_cat) + { + for (size_t i = 0; i < parent_cat->obj_list.size(); i++) + { + if (parent_cat->obj_list[i] == obj) + { + parent_cat->obj_list.erase(parent_cat->obj_list.iterAt(i)); + + /* If this category now has no items, mark root as changed + * so clients won't get the category drawn anymore. + */ + if (parent_cat->obj_list.size() == 0) + { + m_SerialNo++; + } + break; + } + } + + /* Update the category as changed. */ + parent_cat->reorder = true; + parent_cat->serial++; + } + } + + /* The callbacks pointer is still valid, so fire away! */ + obj->callbacks->OnTopMenuObjectRemoved(this, object_id); + + /* Finally, mark the object as free. */ + obj->is_free = true; +} + +bool TopMenu::DisplayMenu(int client, unsigned int hold_time, TopMenuPosition position) +{ + if (m_clients == NULL) + { + return false; + } + + IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(client); + if (!pPlayer->IsInGame()) + { + return false; + } + + UpdateClientRoot(client, pPlayer); + + topmenu_player_t *pClient = &m_clients[client]; + if (pClient->root == NULL) + { + return false; + } + + bool return_value = false; + + if (position == TopMenuPosition_LastCategory && + pClient->last_category < m_Categories.size()) + { + return_value = DisplayCategory(client, pClient->last_category, hold_time, true); + if (!return_value) + { + return_value = pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos); + } + } + else if (position == TopMenuPosition_LastRoot) + { + pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos); + } + else if (position == TopMenuPosition_Start) + { + pClient->last_position = 0; + pClient->last_category = 0; + return_value = pClient->root->Display(client, hold_time); + } + + return return_value; +} + +bool TopMenu::DisplayCategory(int client, unsigned int category, unsigned int hold_time, bool last_position) +{ + UpdateClientCategory(client, category); + + topmenu_player_t *pClient = &m_clients[client]; + if (category >= pClient->cat_count + || pClient->cats[category].menu == NULL) + { + return false; + } + + bool return_value = false; + + topmenu_player_category_t *player_cat = &(pClient->cats[category]); + + pClient->last_category = category; + if (last_position) + { + return_value = player_cat->menu->DisplayAtItem(client, hold_time, pClient->last_position); + } + else + { + return_value = player_cat->menu->Display(client, hold_time); + } + + return return_value; +} + +void TopMenu::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page) +{ + const char *item_name = menu->GetItemInfo(item, NULL); + if (!item_name) + { + return; + } + + topmenu_object_t *obj; + topmenu_player_t *pClient = &m_clients[client]; + topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name); + if (pObject == NULL) + { + return; + } + + obj = *pObject; + + /* We now have the object... what do we do with it? */ + if (obj->type == TopMenuObject_Category) + { + /* If it's a category, the user wants to view it.. */ + for (size_t i = 0; i < m_Categories.size(); i++) + { + if (m_Categories[i]->obj == obj) + { + pClient->last_root_pos = item_on_page; + if (!DisplayCategory(client, (unsigned int)i, MENU_TIME_FOREVER, false)) + { + /* If we can't display the category, re-display the root menu */ + DisplayMenu(client, MENU_TIME_FOREVER, TopMenuPosition_LastRoot); + } + break; + } + } + } + else + { + pClient->last_position = item_on_page; + + /* Re-check access in case this user had their credentials revoked */ + if (obj->cmdname[0] != '\0' && !adminsys->CheckAccess(client, obj->cmdname, obj->flags, false)) + { + DisplayMenu(client, 0, TopMenuPosition_LastCategory); + return; + } + + /* Pass the information on to the callback */ + obj->callbacks->OnTopMenuSelectOption(this, client, obj->object_id); + } +} + +void TopMenu::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style) +{ + const char *item_name = menu->GetItemInfo(item, NULL); + if (!item_name) + { + return; + } + + topmenu_object_t *obj; + topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name); + if (pObject == NULL) + { + return; + } + + obj = *pObject; + + style = obj->callbacks->OnTopMenuDrawOption(this, client, obj->object_id); + if (style != ITEMDRAW_DEFAULT) + { + return; + } + + if (obj->cmdname[0] == '\0') + { + return; + } + + if (!adminsys->CheckAccess(client, obj->cmdname, obj->flags, false)) + { + style = ITEMDRAW_IGNORE; + } +} + +unsigned int TopMenu::OnMenuDisplayItem(IBaseMenu *menu, + int client, + IMenuPanel *panel, + unsigned int item, + const ItemDrawInfo &dr) +{ + const char *item_name = menu->GetItemInfo(item, NULL); + if (!item_name) + { + return 0; + } + + topmenu_object_t *obj; + topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name); + if (pObject == NULL) + { + return 0; + } + + obj = *pObject; + + /* Ask the object to render the text for this client */ + char renderbuf[64]; + obj->callbacks->OnTopMenuDisplayOption(this, client, obj->object_id, renderbuf, sizeof(renderbuf)); + + /* Build the new draw info */ + ItemDrawInfo new_dr = dr; + new_dr.display = renderbuf; + + /* Man I love the menu API. Ask the panel to draw the item and give the position + * back to Core's renderer. This way we don't have to worry about keeping the + * render buffer static! + */ + return panel->DrawItem(new_dr); +} + +void TopMenu::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) +{ + if (reason == MenuCancel_ExitBack) + { + /* If the client chose exit back, they were on a category menu, and we can + * now display the root menu from the last known position. + */ + DisplayMenu(client, 0, TopMenuPosition_LastRoot); + } +} + +void TopMenu::UpdateClientRoot(int client, IGamePlayer *pGamePlayer) +{ + topmenu_player_t *pClient = &m_clients[client]; + IGamePlayer *pPlayer = pGamePlayer ? pGamePlayer : playerhelpers->GetGamePlayer(client); + + /* Determine if an update is necessary */ + bool is_update_needed = false; + if (pClient->menu_serial != m_SerialNo) + { + is_update_needed = true; + } + else if (pPlayer->GetUserId() != pClient->user_id) + { + is_update_needed = true; + } + + /* If no update is needed at the root level, just leave now */ + if (!is_update_needed) + { + return; + } + + /* First we need to flush the cache... */ + TearDownClient(pClient); + + /* Now, rebuild the category list, but don't create menus */ + if (m_Categories.size() == 0) + { + pClient->cat_count = 0; + pClient->cats = NULL; + } + else + { + pClient->cat_count = (unsigned int)m_Categories.size(); + pClient->cats = new topmenu_player_category_t[pClient->cat_count]; + memset(pClient->cats, 0, sizeof(topmenu_player_category_t) * pClient->cat_count); + } + + /* Re-sort the root categories if needed */ + SortCategoriesIfNeeded(); + + /* Build the root menu */ + IBaseMenu *root_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity()); + + /* Add the sorted items */ + for (size_t i = 0; i < m_SortedCats.size(); i++) + { + if (m_Categories[m_SortedCats[i]]->obj_list.size() == 0) + { + continue; + } + root_menu->AppendItem(m_Categories[m_SortedCats[i]]->obj->name, ItemDrawInfo("")); + } + + /* Now we need to handle un-sorted items. This is slightly trickier, as we need to + * pre-render each category name, and cache those names. Phew! + */ + if (m_UnsortedCats.size()) + { + obj_by_name_t *item_list = new obj_by_name_t[m_UnsortedCats.size()]; + for (size_t i = 0; i < m_UnsortedCats.size(); i++) + { + obj_by_name_t *temp_obj = &item_list[i]; + topmenu_object_t *obj = m_Categories[m_UnsortedCats[i]]->obj; + obj->callbacks->OnTopMenuDisplayOption(this, + client, + obj->object_id, + temp_obj->name, + sizeof(temp_obj->name)); + temp_obj->obj_index = m_UnsortedCats[i]; + } + + /* Sort our temp list */ + qsort(item_list, m_UnsortedCats.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending); + + /* Add the new sorted categories */ + for (size_t i = 0; i < m_UnsortedCats.size(); i++) + { + if (m_Categories[item_list[i].obj_index]->obj_list.size() == 0) + { + continue; + } + root_menu->AppendItem(m_Categories[item_list[i].obj_index]->obj->name, ItemDrawInfo("")); + } + + delete [] item_list; + } + + /* Set the menu's title */ + char renderbuf[128]; + m_pTitle->OnTopMenuDisplayTitle(this, client, 0, renderbuf, sizeof(renderbuf)); + root_menu->SetDefaultTitle(renderbuf); + + /* The client is now fully updated */ + pClient->root = root_menu; + pClient->user_id = pPlayer->GetUserId(); + pClient->menu_serial = m_SerialNo; + pClient->last_position = 0; + pClient->last_category = 0; + pClient->last_root_pos = 0; +} + +void TopMenu::UpdateClientCategory(int client, unsigned int category) +{ + /* Update the client's root menu just in case it needs it. This + * will validate that we have both a valid client and a valid + * category structure for that client. + */ + UpdateClientRoot(client); + + /* Now it's guaranteed that our category tables will be usable */ + topmenu_player_t *pClient = &m_clients[client]; + topmenu_category_t *cat = m_Categories[category]; + topmenu_player_category_t *player_cat = &(pClient->cats[category]); + + /* Does the category actually need updating? */ + if (player_cat->serial == cat->serial) + { + return; + } + + /* Destroy any existing menu */ + if (player_cat->menu) + { + player_cat->menu->Destroy(); + player_cat->menu = NULL; + } + + if (pClient->last_category == category) + { + pClient->last_position = 0; + } + + IBaseMenu *cat_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity()); + + /* Categories get an "exit back" button */ + cat_menu->SetMenuOptionFlags(cat_menu->GetMenuOptionFlags() | MENUFLAG_BUTTON_EXITBACK); + + /* Re-sort the category if needed */ + SortCategoryIfNeeded(category); + + /* Build the menu with the sorted items first */ + for (size_t i = 0; i < cat->sorted.size(); i++) + { + cat_menu->AppendItem(cat->sorted[i]->name, ItemDrawInfo("")); + } + + /* Now handle unsorted items */ + if (cat->unsorted.size()) + { + /* Build a list of the item names */ + obj_by_name_t *item_list = new obj_by_name_t[cat->unsorted.size()]; + for (size_t i = 0; i < cat->unsorted.size(); i++) + { + obj_by_name_t *item = &item_list[i]; + topmenu_object_t *obj = cat->unsorted[i]; + obj->callbacks->OnTopMenuDisplayOption(this, + client, + obj->object_id, + item->name, + sizeof(item->name)); + item->obj_index = (unsigned int)i; + } + + /* Sort the names */ + qsort(item_list, cat->unsorted.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending); + + /* Add to the menu */ + for (size_t i = 0; i < cat->unsorted.size(); i++) + { + cat_menu->AppendItem(cat->unsorted[item_list[i].obj_index]->name, ItemDrawInfo("")); + } + + delete [] item_list; + } + + /* Set the menu's title */ + char renderbuf[128]; + cat->obj->callbacks->OnTopMenuDisplayTitle(this, + client, + cat->obj->object_id, + renderbuf, + sizeof(renderbuf)); + cat_menu->SetDefaultTitle(renderbuf); + + /* We're done! */ + player_cat->menu = cat_menu; + player_cat->serial = cat->serial; +} + +void TopMenu::SortCategoryIfNeeded(unsigned int category) +{ + topmenu_category_t *cat = m_Categories[category]; + if (!cat->reorder) + { + return; + } + + cat->sorted.clear(); + cat->unsorted.clear(); + + if (cat->obj_list.size() == 0) + { + cat->reorder = false; + return; + } + + CVector to_sort; + for (size_t i = 0; i < cat->obj_list.size(); i++) + { + to_sort.push_back(i); + } + + /* Find a matching category in the configs */ + config_category_t *config_cat = NULL; + for (size_t i = 0; i < m_Config.cats.size(); i++) + { + if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), cat->obj->name) == 0) + { + config_cat = m_Config.cats[i]; + break; + } + } + + /* If there is a matching category, build a pre-sorted item list */ + if (config_cat != NULL) + { + /* Find matching objects in this category */ + for (size_t i = 0; i < config_cat->commands.size(); i++) + { + const char *config_name = m_Config.strings.GetString(config_cat->commands[i]); + for (size_t j = 0; j < to_sort.size(); j++) + { + if (strcmp(config_name, cat->obj_list[to_sort[j]]->name) == 0) + { + /* Place in the final list, then remove from the temporary list */ + cat->sorted.push_back(cat->obj_list[to_sort[j]]); + to_sort.erase(to_sort.iterAt(j)); + break; + } + } + } + } + + /* Push any remaining items onto the unsorted list */ + for (size_t i = 0; i < to_sort.size(); i++) + { + cat->unsorted.push_back(cat->obj_list[to_sort[i]]); + } + + cat->reorder = false; +} + +void TopMenu::SortCategoriesIfNeeded() +{ + if (!m_bCatsNeedResort) + { + return; + } + + /* Clear sort results */ + m_SortedCats.clear(); + m_UnsortedCats.clear(); + + if (m_Categories.size() == 0) + { + m_bCatsNeedResort = false; + return; + } + + CVector to_sort; + for (unsigned int i = 0; i < (unsigned int)m_Categories.size(); i++) + { + to_sort.push_back(i); + } + + /* If we have any predefined categories, add them in as they appear. */ + for (size_t i= 0; i < m_Config.cats.size(); i++) + { + /* Find this category and map it in if we can */ + for (size_t j = 0; j < to_sort.size(); j++) + { + if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), + m_Categories[to_sort[j]]->obj->name) == 0) + { + /* Add to the real list and remove from the temporary */ + m_SortedCats.push_back(to_sort[j]); + to_sort.erase(to_sort.iterAt(j)); + break; + } + } + } + + /* Push any remaining items onto the unsorted list */ + for (size_t i = 0; i < to_sort.size(); i++) + { + m_UnsortedCats.push_back(to_sort[i]); + } + + m_bCatsNeedResort = false; +} + +void TopMenu::CreatePlayers(int max_clients) +{ + m_max_clients = max_clients; + m_clients = (topmenu_player_t *)malloc(sizeof(topmenu_player_t) * (max_clients + 1)); + memset(m_clients, 0, sizeof(topmenu_player_t) * (max_clients + 1)); +} + +void TopMenu::TearDownClient(topmenu_player_t *player) +{ + if (player->cats != NULL) + { + for (unsigned int i = 0; i < player->cat_count; i++) + { + topmenu_player_category_t *player_cat = &(player->cats[i]); + if (player_cat->menu != NULL) + { + player_cat->menu->Destroy(); + } + } + delete [] player->cats; + } + + if (player->root != NULL) + { + player->root->Destroy(); + } + + memset(player, 0, sizeof(topmenu_player_t)); +} + +bool TopMenu::LoadConfiguration(const char *file, char *error, size_t maxlength) +{ + SMCError err; + SMCStates states; + + if ((err = textparsers->ParseFile_SMC(file, this, &states)) + != SMCError_Okay) + { + const char *err_string = textparsers->GetSMCErrorString(err); + if (!err_string) + { + err_string = "Unknown"; + } + + UTIL_Format(error, maxlength, "%s", err_string); + + return false; + } + + return true; +} + +bool TopMenu::OnIdentityRemoval(IdentityToken_t *owner) +{ + /* First sweep the categories owned by us */ + CVector obj_list; + for (size_t i = 0; i < m_Categories.size(); i++) + { + if (m_Categories[i]->obj->owner == owner) + { + obj_list.push_back(m_Categories[i]->obj->object_id); + } + } + + for (size_t i = 0; i < obj_list.size(); i++) + { + RemoveFromMenu(obj_list[i]); + } + + /* Now we can look for actual items */ + for (size_t i = 0; i < m_Objects.size(); i++) + { + if (m_Objects[i]->is_free) + { + continue; + } + if (m_Objects[i]->owner == owner) + { + assert(m_Objects[i]->type != TopMenuObject_Category); + RemoveFromMenu(m_Objects[i]->object_id); + } + } + + return true; +} + +#define PARSE_STATE_NONE 0 +#define PARSE_STATE_MAIN 1 +#define PARSE_STATE_CATEGORY 2 +unsigned int ignore_parse_level = 0; +unsigned int current_parse_state = 0; +config_category_t *cur_cat = NULL; + +void TopMenu::ReadSMC_ParseStart() +{ + current_parse_state = PARSE_STATE_NONE; + ignore_parse_level = 0; + cur_cat = NULL; + + /* Reset the old config */ + m_Config.strings.Reset(); + for (size_t i = 0; i < m_Config.cats.size(); i++) + { + delete m_Config.cats[i]; + } + m_Config.cats.clear(); +} + +SMCResult TopMenu::ReadSMC_NewSection(const SMCStates *states, const char *name) +{ + if (ignore_parse_level) + { + ignore_parse_level++; + } + else + { + if (current_parse_state == PARSE_STATE_NONE) + { + if (strcmp(name, "Menu") == 0) + { + current_parse_state = PARSE_STATE_MAIN; + } + else + { + ignore_parse_level = 1; + } + } + else if (current_parse_state == PARSE_STATE_MAIN) + { + cur_cat = new config_category_t; + cur_cat->name = m_Config.strings.AddString(name); + m_Config.cats.push_back(cur_cat); + current_parse_state = PARSE_STATE_CATEGORY; + } + else + { + ignore_parse_level = 1; + } + } + + return SMCResult_Continue; +} + +SMCResult TopMenu::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value) +{ + if (ignore_parse_level > 0 + || current_parse_state != PARSE_STATE_CATEGORY + || cur_cat == NULL) + { + return SMCResult_Continue; + } + + if (strcmp(key, "item") == 0) + { + cur_cat->commands.push_back(m_Config.strings.AddString(value)); + } + + return SMCResult_Continue; +} + +SMCResult TopMenu::ReadSMC_LeavingSection(const SMCStates *states) +{ + if (ignore_parse_level) + { + ignore_parse_level--; + } + else + { + if (current_parse_state == PARSE_STATE_CATEGORY) + { + cur_cat = NULL; + current_parse_state = PARSE_STATE_MAIN; + } + else if (current_parse_state == PARSE_STATE_MAIN) + { + current_parse_state = PARSE_STATE_NONE; + } + } + + return SMCResult_Continue; +} + +unsigned int TopMenu::FindCategory(const char *name) +{ + topmenu_object_t **p_obj = m_ObjLookup.retrieve(name); + if (!p_obj) + { + return 0; + } + + topmenu_object_t *obj = *p_obj; + if (obj->type != TopMenuObject_Category) + { + return 0; + } + + return obj->object_id; +} + +int _SortObjectNamesDescending(const void *ptr1, const void *ptr2) +{ + obj_by_name_t *obj1 = (obj_by_name_t *)ptr1; + obj_by_name_t *obj2 = (obj_by_name_t *)ptr2; + return strcmp(obj1->name, obj2->name); +} + +unsigned int strncopy(char *dest, const char *src, size_t count) +{ + if (!count) + { + return 0; + } + + char *start = dest; + while ((*src) && (--count)) + { + *dest++ = *src++; + } + *dest = '\0'; + + return (dest - start); +} + +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; + } +} diff --git a/extensions/topmenus/TopMenu.h b/extensions/topmenus/TopMenu.h index 1c80f25b..c4c0ec86 100644 --- a/extensions/topmenus/TopMenu.h +++ b/extensions/topmenus/TopMenu.h @@ -1,178 +1,179 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * SourceMod Sample Extension - * Copyright (C) 2004-2007 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_TOP_MENU_H_ -#define _INCLUDE_SOURCEMOD_TOP_MENU_H_ - -#include -#include -#include -#include -#include "smsdk_ext.h" -#include "sm_memtable.h" - -using namespace SourceHook; -using namespace SourceMod; - -struct config_category_t -{ - int name; - CVector commands; -}; - -struct config_root_t -{ - config_root_t() : strings(1024) - { - } - BaseStringTable strings; - CVector cats; -}; - -struct topmenu_object_t -{ - char name[64]; /** Name */ - char cmdname[64]; /** Command name */ - FlagBits flags; /** Admin flags */ - ITopMenuObjectCallbacks *callbacks; /** Callbacks */ - IdentityToken_t *owner; /** Owner */ - unsigned int object_id; /** Object ID */ - topmenu_object_t *parent; /** Parent, if any */ - TopMenuObjectType type; /** Object Type */ - bool is_free; /** Free or not? */ - char info[255]; /** Info string */ -}; - -struct topmenu_category_t -{ - CVector obj_list; /** Full object list */ - CVector sorted; /** Sorted items */ - CVector unsorted; /** Unsorted items */ - topmenu_object_t *obj; /** Bound object */ - unsigned int serial; /** Serial number */ - bool reorder; /** Whether ordering needs updating */ -}; - -struct topmenu_player_category_t -{ - IBaseMenu *menu; /** menu pointer */ - unsigned int serial; /** last known serial */ -}; - -struct topmenu_player_t -{ - int user_id; /** userid on server */ - unsigned int menu_serial; /** menu serial no */ - IBaseMenu *root; /** root menu display */ - topmenu_player_category_t *cats; /** category display */ - unsigned int cat_count; /** number of categories */ - unsigned int last_category; /** last category they selected */ - unsigned int last_position; /** last position in that category */ - unsigned int last_root_pos; /** last page in the root menu */ -}; - -class TopMenu : - public ITopMenu, - public IMenuHandler, - public ITextListener_SMC -{ - friend class TopMenuManager; -public: - TopMenu(ITopMenuObjectCallbacks *callbacks); - ~TopMenu(); -public: //ITopMenu - virtual unsigned int AddToMenu(const char *name, - TopMenuObjectType type, - ITopMenuObjectCallbacks *callbacks, - IdentityToken_t *owner, - const char *cmdname, - FlagBits flags, - unsigned int parent); - unsigned int AddToMenu2(const char *name, - TopMenuObjectType type, - ITopMenuObjectCallbacks *callbacks, - IdentityToken_t *owner, - const char *cmdname, - FlagBits flags, - unsigned int parent, - const char *info_string); - virtual void RemoveFromMenu(unsigned int object_id); - virtual bool DisplayMenu(int client, - unsigned int hold_time, - TopMenuPosition position); - virtual bool LoadConfiguration(const char *file, char *error, size_t maxlength); - virtual unsigned int FindCategory(const char *name); - const char *GetObjectInfoString(unsigned int object_id); -public: //IMenuHandler - virtual void OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page); - virtual void OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style); - virtual unsigned int OnMenuDisplayItem(IBaseMenu *menu, - int client, - IMenuPanel *panel, - unsigned int item, - const ItemDrawInfo &dr); - virtual void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason); -public: //ITextListener_SMC - void ReadSMC_ParseStart(); - SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name); - SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value); - SMCResult ReadSMC_LeavingSection(const SMCStates *states); -private: - void SortCategoriesIfNeeded(); - void SortCategoryIfNeeded(unsigned int category); -private: - bool DisplayCategory(int client, unsigned int category, unsigned int hold_time, bool last_position); - void CreatePlayers(int max_clients); - void UpdateClientRoot(int client, IGamePlayer *pGamePlayer=NULL); - void UpdateClientCategory(int client, unsigned int category); - void TearDownClient(topmenu_player_t *player); -private: - void OnClientConnected(int client); - void OnClientDisconnected(int client); - void OnServerActivated(int max_clients); - bool OnIdentityRemoval(IdentityToken_t *owner); -private: - config_root_t m_Config; /* Configuration from file */ - topmenu_player_t *m_clients; /* Client array */ - CVector m_SortedCats; /* Sorted categories */ - CVector m_UnsortedCats; /* Un-sorted categories */ - CVector m_Categories; /* Category array */ - CVector m_Objects; /* Object array */ - KTrie m_ObjLookup; /* Object lookup trie */ - unsigned int m_SerialNo; /* Serial number for updating */ - ITopMenuObjectCallbacks *m_pTitle; /* Title callbacks */ - int m_max_clients; /* Maximum number of clients */ - bool m_bCatsNeedResort; /* True if categories need a resort */ -}; - -unsigned int strncopy(char *dest, const char *src, size_t count); - -#endif //_INCLUDE_SOURCEMOD_TOP_MENU_H_ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Sample Extension + * Copyright (C) 2004-2007 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_TOP_MENU_H_ +#define _INCLUDE_SOURCEMOD_TOP_MENU_H_ + +#include +#include +#include +#include +#include "smsdk_ext.h" +#include "sm_memtable.h" + +using namespace SourceHook; +using namespace SourceMod; + +struct config_category_t +{ + int name; + CVector commands; +}; + +struct config_root_t +{ + config_root_t() : strings(1024) + { + } + BaseStringTable strings; + CVector cats; +}; + +struct topmenu_object_t +{ + char name[64]; /** Name */ + char cmdname[64]; /** Command name */ + FlagBits flags; /** Admin flags */ + ITopMenuObjectCallbacks *callbacks; /** Callbacks */ + IdentityToken_t *owner; /** Owner */ + unsigned int object_id; /** Object ID */ + topmenu_object_t *parent; /** Parent, if any */ + TopMenuObjectType type; /** Object Type */ + bool is_free; /** Free or not? */ + char info[255]; /** Info string */ +}; + +struct topmenu_category_t +{ + CVector obj_list; /** Full object list */ + CVector sorted; /** Sorted items */ + CVector unsorted; /** Unsorted items */ + topmenu_object_t *obj; /** Bound object */ + unsigned int serial; /** Serial number */ + bool reorder; /** Whether ordering needs updating */ +}; + +struct topmenu_player_category_t +{ + IBaseMenu *menu; /** menu pointer */ + unsigned int serial; /** last known serial */ +}; + +struct topmenu_player_t +{ + int user_id; /** userid on server */ + unsigned int menu_serial; /** menu serial no */ + IBaseMenu *root; /** root menu display */ + topmenu_player_category_t *cats; /** category display */ + unsigned int cat_count; /** number of categories */ + unsigned int last_category; /** last category they selected */ + unsigned int last_position; /** last position in that category */ + unsigned int last_root_pos; /** last page in the root menu */ +}; + +class TopMenu : + public ITopMenu, + public IMenuHandler, + public ITextListener_SMC +{ + friend class TopMenuManager; +public: + TopMenu(ITopMenuObjectCallbacks *callbacks); + ~TopMenu(); +public: //ITopMenu + virtual unsigned int AddToMenu(const char *name, + TopMenuObjectType type, + ITopMenuObjectCallbacks *callbacks, + IdentityToken_t *owner, + const char *cmdname, + FlagBits flags, + unsigned int parent); + unsigned int AddToMenu2(const char *name, + TopMenuObjectType type, + ITopMenuObjectCallbacks *callbacks, + IdentityToken_t *owner, + const char *cmdname, + FlagBits flags, + unsigned int parent, + const char *info_string); + virtual void RemoveFromMenu(unsigned int object_id); + virtual bool DisplayMenu(int client, + unsigned int hold_time, + TopMenuPosition position); + virtual bool LoadConfiguration(const char *file, char *error, size_t maxlength); + virtual unsigned int FindCategory(const char *name); + const char *GetObjectInfoString(unsigned int object_id); + const char *GetObjectName(unsigned int object_id); +public: //IMenuHandler + virtual void OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page); + virtual void OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style); + virtual unsigned int OnMenuDisplayItem(IBaseMenu *menu, + int client, + IMenuPanel *panel, + unsigned int item, + const ItemDrawInfo &dr); + virtual void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason); +public: //ITextListener_SMC + void ReadSMC_ParseStart(); + SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name); + SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value); + SMCResult ReadSMC_LeavingSection(const SMCStates *states); +private: + void SortCategoriesIfNeeded(); + void SortCategoryIfNeeded(unsigned int category); +private: + bool DisplayCategory(int client, unsigned int category, unsigned int hold_time, bool last_position); + void CreatePlayers(int max_clients); + void UpdateClientRoot(int client, IGamePlayer *pGamePlayer=NULL); + void UpdateClientCategory(int client, unsigned int category); + void TearDownClient(topmenu_player_t *player); +private: + void OnClientConnected(int client); + void OnClientDisconnected(int client); + void OnServerActivated(int max_clients); + bool OnIdentityRemoval(IdentityToken_t *owner); +private: + config_root_t m_Config; /* Configuration from file */ + topmenu_player_t *m_clients; /* Client array */ + CVector m_SortedCats; /* Sorted categories */ + CVector m_UnsortedCats; /* Un-sorted categories */ + CVector m_Categories; /* Category array */ + CVector m_Objects; /* Object array */ + KTrie m_ObjLookup; /* Object lookup trie */ + unsigned int m_SerialNo; /* Serial number for updating */ + ITopMenuObjectCallbacks *m_pTitle; /* Title callbacks */ + int m_max_clients; /* Maximum number of clients */ + bool m_bCatsNeedResort; /* True if categories need a resort */ +}; + +unsigned int strncopy(char *dest, const char *src, size_t count); + +#endif //_INCLUDE_SOURCEMOD_TOP_MENU_H_ diff --git a/extensions/topmenus/smn_topmenus.cpp b/extensions/topmenus/smn_topmenus.cpp index 4799b9fd..98575ec7 100644 --- a/extensions/topmenus/smn_topmenus.cpp +++ b/extensions/topmenus/smn_topmenus.cpp @@ -1,358 +1,383 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * SourceMod Sample Extension - * Copyright (C) 2004-2007 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 "extension.h" -#include "TopMenuManager.h" -#include "TopMenu.h" - -HandleType_t hTopMenuType; - -class TopMenuHandle : public IHandleTypeDispatch -{ -public: - void OnHandleDestroy(HandleType_t type, void *object) - { - ITopMenu *pTopMenu = (ITopMenu *)object; - g_TopMenus.DestroyTopMenu(pTopMenu); - } -} s_TopMenuHandle; - -void Initialize_Natives() -{ - hTopMenuType = handlesys->CreateType("ITopMenu", - &s_TopMenuHandle, - 0, - NULL, - NULL, - myself->GetIdentity(), - NULL); -} - -void Shutdown_Natives() -{ - handlesys->RemoveType(hTopMenuType, myself->GetIdentity()); -} - -enum TopMenuAction -{ - TopMenuAction_DisplayOption = 0, - TopMenuAction_DisplayTitle = 1, - TopMenuAction_SelectOption = 2, - TopMenuAction_DrawOption = 3, - TopMenuAction_RemoveObject = 4, -}; - -class TopMenuCallbacks : public ITopMenuObjectCallbacks -{ -public: - TopMenuCallbacks(IPluginFunction *pFunction) : m_pFunction(pFunction) - { - } - - unsigned int OnTopMenuDrawOption(ITopMenu *menu, - int client, - unsigned int object_id) - { - char buffer[2] = {ITEMDRAW_DEFAULT, 0x0}; - m_pFunction->PushCell(m_hMenuHandle); - m_pFunction->PushCell(TopMenuAction_DrawOption); - m_pFunction->PushCell(object_id); - m_pFunction->PushCell(client); - m_pFunction->PushStringEx(buffer, sizeof(buffer), SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); - m_pFunction->PushCell(sizeof(buffer)); - m_pFunction->Execute(NULL); - return (unsigned int)buffer[0]; - } - - void OnTopMenuDisplayOption(ITopMenu *menu, - int client, - unsigned int object_id, - char buffer[], - size_t maxlength) - { - m_pFunction->PushCell(m_hMenuHandle); - m_pFunction->PushCell(TopMenuAction_DisplayOption); - m_pFunction->PushCell(object_id); - m_pFunction->PushCell(client); - m_pFunction->PushStringEx(buffer, maxlength, 0, SM_PARAM_COPYBACK); - m_pFunction->PushCell(maxlength); - m_pFunction->Execute(NULL); - } - - void OnTopMenuDisplayTitle(ITopMenu *menu, - int client, - unsigned int object_id, - char buffer[], - size_t maxlength) - { - m_pFunction->PushCell(m_hMenuHandle); - m_pFunction->PushCell(TopMenuAction_DisplayTitle); - m_pFunction->PushCell(object_id); - m_pFunction->PushCell(client); - m_pFunction->PushStringEx(buffer, maxlength, 0, SM_PARAM_COPYBACK); - m_pFunction->PushCell(maxlength); - m_pFunction->Execute(NULL); - } - - void OnTopMenuSelectOption(ITopMenu *menu, - int client, - unsigned int object_id) - { - unsigned int old_reply = playerhelpers->SetReplyTo(SM_REPLY_CHAT); - m_pFunction->PushCell(m_hMenuHandle); - m_pFunction->PushCell(TopMenuAction_SelectOption); - m_pFunction->PushCell(object_id); - m_pFunction->PushCell(client); - m_pFunction->PushString(""); - m_pFunction->PushCell(0); - m_pFunction->Execute(NULL); - playerhelpers->SetReplyTo(old_reply); - } - - void OnTopMenuObjectRemoved(ITopMenu *menu, unsigned int object_id) - { - m_pFunction->PushCell(m_hMenuHandle); - m_pFunction->PushCell(TopMenuAction_RemoveObject); - m_pFunction->PushCell(object_id); - m_pFunction->PushCell(0); - m_pFunction->PushString(""); - m_pFunction->PushCell(0); - m_pFunction->Execute(NULL); - - delete this; - } - - Handle_t m_hMenuHandle; - IPluginFunction *m_pFunction; -}; - -static cell_t CreateTopMenu(IPluginContext *pContext, const cell_t *params) -{ - IPluginFunction *func = pContext->GetFunctionById(params[1]); - if (func == NULL) - { - return pContext ->ThrowNativeError("Invalid function specified"); - } - - TopMenuCallbacks *cb = new TopMenuCallbacks(func); - - ITopMenu *pMenu = g_TopMenus.CreateTopMenu(cb); - - if (!pMenu) - { - delete cb; - return BAD_HANDLE; - } - - Handle_t hndl = handlesys->CreateHandle(hTopMenuType, - pMenu, - pContext->GetIdentity(), - myself->GetIdentity(), - NULL); - if (hndl == 0) - { - g_TopMenus.DestroyTopMenu(pMenu); - return BAD_HANDLE; - } - - cb->m_hMenuHandle = hndl; - - return hndl; -} - -static cell_t LoadTopMenuConfig(IPluginContext *pContext, const cell_t *params) -{ - HandleError err; - ITopMenu *pMenu; - HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); - - if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu)) - != HandleError_None) - { - return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); - } - - char *file, *err_buf; - pContext->LocalToString(params[2], &file); - pContext->LocalToString(params[3], &err_buf); - - char path[PLATFORM_MAX_PATH]; - g_pSM->BuildPath(Path_Game, path, sizeof(path), "%s", file); - - return pMenu->LoadConfiguration(path, err_buf, params[4]) ? 1 : 0; -} - -static cell_t AddToTopMenu(IPluginContext *pContext, const cell_t *params) -{ - HandleError err; - ITopMenu *pMenu; - HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); - - if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu)) - != HandleError_None) - { - return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); - } - - IPluginFunction *func = pContext->GetFunctionById(params[4]); - if (func == NULL) - { - return pContext ->ThrowNativeError("Invalid function specified"); - } - - TopMenuCallbacks *cb = new TopMenuCallbacks(func); - - char *name, *cmdname, *info_string = NULL; - pContext->LocalToString(params[2], &name); - pContext->LocalToString(params[6], &cmdname); - - if (params[0] >= 8) - { - pContext->LocalToString(params[8], &info_string); - } - - TopMenuObjectType obj_type = (TopMenuObjectType)params[3]; - - unsigned int object_id; - if ((object_id = pMenu->AddToMenu2(name, - obj_type, - cb, - pContext->GetIdentity(), - cmdname, - params[7], - params[5], - info_string)) == 0) - { - delete cb; - return 0; - } - - cb->m_hMenuHandle = params[1]; - - return object_id; -} - -static cell_t RemoveFromTopMenu(IPluginContext *pContext, const cell_t *params) -{ - HandleError err; - ITopMenu *pMenu; - HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); - - if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu)) - != HandleError_None) - { - return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); - } - - pMenu->RemoveFromMenu(params[2]); - - return 1; -} - -static cell_t FindTopMenuCategory(IPluginContext *pContext, const cell_t *params) -{ - HandleError err; - ITopMenu *pMenu; - HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); - - if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu)) - != HandleError_None) - { - return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); - } - - char *name; - pContext->LocalToString(params[2], &name); - - return pMenu->FindCategory(name); -} - -static cell_t DisplayTopMenu(IPluginContext *pContext, const cell_t *params) -{ - HandleError err; - ITopMenu *pMenu; - HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); - - if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu)) - != HandleError_None) - { - return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); - } - - int client = params[2]; - IGamePlayer *player = playerhelpers->GetGamePlayer(client); - if (!player) - { - return pContext->ThrowNativeError("Invalid client index %d", client); - } - else if (!player->IsInGame()) - { - return pContext->ThrowNativeError("Client %d is not in game", client); - } - - return pMenu->DisplayMenu(client, 0, (TopMenuPosition)params[3]); -} - -static cell_t GetTopMenuInfoString(IPluginContext *pContext, const cell_t *params) -{ - HandleError err; - ITopMenu *pMenu; - HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); - - if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu)) - != HandleError_None) - { - return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); - } - - const char *str; - if ((str = pMenu->GetObjectInfoString(params[2])) == NULL) - { - return pContext->ThrowNativeError("Invalid menu object %d", params[2]); - } - - char *buffer; - pContext->LocalToString(params[3], &buffer); - - return strncopy(buffer, str, params[4]); -} - -sp_nativeinfo_t g_TopMenuNatives[] = -{ - {"AddToTopMenu", AddToTopMenu}, - {"CreateTopMenu", CreateTopMenu}, - {"DisplayTopMenu", DisplayTopMenu}, - {"LoadTopMenuConfig", LoadTopMenuConfig}, - {"RemoveFromTopMenu", RemoveFromTopMenu}, - {"FindTopMenuCategory", FindTopMenuCategory}, - {"GetTopMenuInfoString", GetTopMenuInfoString}, - {NULL, NULL}, -}; +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Sample Extension + * Copyright (C) 2004-2007 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 "extension.h" +#include "TopMenuManager.h" +#include "TopMenu.h" + +HandleType_t hTopMenuType; + +class TopMenuHandle : public IHandleTypeDispatch +{ +public: + void OnHandleDestroy(HandleType_t type, void *object) + { + ITopMenu *pTopMenu = (ITopMenu *)object; + g_TopMenus.DestroyTopMenu(pTopMenu); + } +} s_TopMenuHandle; + +void Initialize_Natives() +{ + hTopMenuType = handlesys->CreateType("ITopMenu", + &s_TopMenuHandle, + 0, + NULL, + NULL, + myself->GetIdentity(), + NULL); +} + +void Shutdown_Natives() +{ + handlesys->RemoveType(hTopMenuType, myself->GetIdentity()); +} + +enum TopMenuAction +{ + TopMenuAction_DisplayOption = 0, + TopMenuAction_DisplayTitle = 1, + TopMenuAction_SelectOption = 2, + TopMenuAction_DrawOption = 3, + TopMenuAction_RemoveObject = 4, +}; + +class TopMenuCallbacks : public ITopMenuObjectCallbacks +{ +public: + TopMenuCallbacks(IPluginFunction *pFunction) : m_pFunction(pFunction) + { + } + + unsigned int OnTopMenuDrawOption(ITopMenu *menu, + int client, + unsigned int object_id) + { + char buffer[2] = {ITEMDRAW_DEFAULT, 0x0}; + m_pFunction->PushCell(m_hMenuHandle); + m_pFunction->PushCell(TopMenuAction_DrawOption); + m_pFunction->PushCell(object_id); + m_pFunction->PushCell(client); + m_pFunction->PushStringEx(buffer, sizeof(buffer), SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); + m_pFunction->PushCell(sizeof(buffer)); + m_pFunction->Execute(NULL); + return (unsigned int)buffer[0]; + } + + void OnTopMenuDisplayOption(ITopMenu *menu, + int client, + unsigned int object_id, + char buffer[], + size_t maxlength) + { + m_pFunction->PushCell(m_hMenuHandle); + m_pFunction->PushCell(TopMenuAction_DisplayOption); + m_pFunction->PushCell(object_id); + m_pFunction->PushCell(client); + m_pFunction->PushStringEx(buffer, maxlength, 0, SM_PARAM_COPYBACK); + m_pFunction->PushCell(maxlength); + m_pFunction->Execute(NULL); + } + + void OnTopMenuDisplayTitle(ITopMenu *menu, + int client, + unsigned int object_id, + char buffer[], + size_t maxlength) + { + m_pFunction->PushCell(m_hMenuHandle); + m_pFunction->PushCell(TopMenuAction_DisplayTitle); + m_pFunction->PushCell(object_id); + m_pFunction->PushCell(client); + m_pFunction->PushStringEx(buffer, maxlength, 0, SM_PARAM_COPYBACK); + m_pFunction->PushCell(maxlength); + m_pFunction->Execute(NULL); + } + + void OnTopMenuSelectOption(ITopMenu *menu, + int client, + unsigned int object_id) + { + unsigned int old_reply = playerhelpers->SetReplyTo(SM_REPLY_CHAT); + m_pFunction->PushCell(m_hMenuHandle); + m_pFunction->PushCell(TopMenuAction_SelectOption); + m_pFunction->PushCell(object_id); + m_pFunction->PushCell(client); + m_pFunction->PushString(""); + m_pFunction->PushCell(0); + m_pFunction->Execute(NULL); + playerhelpers->SetReplyTo(old_reply); + } + + void OnTopMenuObjectRemoved(ITopMenu *menu, unsigned int object_id) + { + m_pFunction->PushCell(m_hMenuHandle); + m_pFunction->PushCell(TopMenuAction_RemoveObject); + m_pFunction->PushCell(object_id); + m_pFunction->PushCell(0); + m_pFunction->PushString(""); + m_pFunction->PushCell(0); + m_pFunction->Execute(NULL); + + delete this; + } + + Handle_t m_hMenuHandle; + IPluginFunction *m_pFunction; +}; + +static cell_t CreateTopMenu(IPluginContext *pContext, const cell_t *params) +{ + IPluginFunction *func = pContext->GetFunctionById(params[1]); + if (func == NULL) + { + return pContext ->ThrowNativeError("Invalid function specified"); + } + + TopMenuCallbacks *cb = new TopMenuCallbacks(func); + + ITopMenu *pMenu = g_TopMenus.CreateTopMenu(cb); + + if (!pMenu) + { + delete cb; + return BAD_HANDLE; + } + + Handle_t hndl = handlesys->CreateHandle(hTopMenuType, + pMenu, + pContext->GetIdentity(), + myself->GetIdentity(), + NULL); + if (hndl == 0) + { + g_TopMenus.DestroyTopMenu(pMenu); + return BAD_HANDLE; + } + + cb->m_hMenuHandle = hndl; + + return hndl; +} + +static cell_t LoadTopMenuConfig(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + ITopMenu *pMenu; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); + } + + char *file, *err_buf; + pContext->LocalToString(params[2], &file); + pContext->LocalToString(params[3], &err_buf); + + char path[PLATFORM_MAX_PATH]; + g_pSM->BuildPath(Path_Game, path, sizeof(path), "%s", file); + + return pMenu->LoadConfiguration(path, err_buf, params[4]) ? 1 : 0; +} + +static cell_t AddToTopMenu(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + ITopMenu *pMenu; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); + } + + IPluginFunction *func = pContext->GetFunctionById(params[4]); + if (func == NULL) + { + return pContext ->ThrowNativeError("Invalid function specified"); + } + + TopMenuCallbacks *cb = new TopMenuCallbacks(func); + + char *name, *cmdname, *info_string = NULL; + pContext->LocalToString(params[2], &name); + pContext->LocalToString(params[6], &cmdname); + + if (params[0] >= 8) + { + pContext->LocalToString(params[8], &info_string); + } + + TopMenuObjectType obj_type = (TopMenuObjectType)params[3]; + + unsigned int object_id; + if ((object_id = pMenu->AddToMenu2(name, + obj_type, + cb, + pContext->GetIdentity(), + cmdname, + params[7], + params[5], + info_string)) == 0) + { + delete cb; + return 0; + } + + cb->m_hMenuHandle = params[1]; + + return object_id; +} + +static cell_t RemoveFromTopMenu(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + ITopMenu *pMenu; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); + } + + pMenu->RemoveFromMenu(params[2]); + + return 1; +} + +static cell_t FindTopMenuCategory(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + ITopMenu *pMenu; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); + } + + char *name; + pContext->LocalToString(params[2], &name); + + return pMenu->FindCategory(name); +} + +static cell_t DisplayTopMenu(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + ITopMenu *pMenu; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); + } + + int client = params[2]; + IGamePlayer *player = playerhelpers->GetGamePlayer(client); + if (!player) + { + return pContext->ThrowNativeError("Invalid client index %d", client); + } + else if (!player->IsInGame()) + { + return pContext->ThrowNativeError("Client %d is not in game", client); + } + + return pMenu->DisplayMenu(client, 0, (TopMenuPosition)params[3]); +} + +static cell_t GetTopMenuInfoString(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + ITopMenu *pMenu; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); + } + + const char *str; + if ((str = pMenu->GetObjectInfoString(params[2])) == NULL) + { + return pContext->ThrowNativeError("Invalid menu object %d", params[2]); + } + + char *buffer; + pContext->LocalToString(params[3], &buffer); + + return strncopy(buffer, str, params[4]); +} + +static cell_t GetTopMenuName(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + ITopMenu *pMenu; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); + } + + const char *str; + if ((str = pMenu->GetObjectName(params[2])) == NULL) + { + return pContext->ThrowNativeError("Invalid menu object %d", params[2]); + } + + char *buffer; + pContext->LocalToString(params[3], &buffer); + + return strncopy(buffer, str, params[4]); +} + +sp_nativeinfo_t g_TopMenuNatives[] = +{ + {"AddToTopMenu", AddToTopMenu}, + {"CreateTopMenu", CreateTopMenu}, + {"DisplayTopMenu", DisplayTopMenu}, + {"LoadTopMenuConfig", LoadTopMenuConfig}, + {"RemoveFromTopMenu", RemoveFromTopMenu}, + {"FindTopMenuCategory", FindTopMenuCategory}, + {"GetTopMenuInfoString", GetTopMenuInfoString}, + {"GetTopMenuObjName", GetTopMenuName}, + {NULL, NULL}, +}; diff --git a/gamedata/core.games.txt b/gamedata/core.games.txt index cb9bfd99..0077e39f 100644 --- a/gamedata/core.games.txt +++ b/gamedata/core.games.txt @@ -62,6 +62,24 @@ } } + /** + * Which games use an extra byte in the HintText + * message? Even though it's in the SDK, apparently + * only CS:S does this right now. + */ + "#default" + { + "#supported" + { + "game" "cstrike" + } + + "Keys" + { + "HintTextPreByte" "yes" + } + } + "cstrike" { "Keys" @@ -69,4 +87,12 @@ "GameExtension" "game.cstrike" } } + + "tf" + { + "Keys" + { + "RadioMenuTimeout" "4" + } + } } diff --git a/plugins/basechat.sp b/plugins/basechat.sp index 868c3c45..17c803bf 100644 --- a/plugins/basechat.sp +++ b/plugins/basechat.sp @@ -50,7 +50,6 @@ new String:g_ColorNames[13][10] = {"White", "Red", "Green", "Blue", "Yellow", "P new g_Colors[13][3] = {{255,255,255},{255,0,0},{0,255,0},{0,0,255},{255,255,0},{255,0,255},{0,255,255},{255,128,0},{255,0,128},{128,255,0},{0,255,128},{128,0,255},{0,128,255}}; new Handle:g_Cvar_Chatmode = INVALID_HANDLE; -new Handle:g_Cvar_Psaymode = INVALID_HANDLE; new bool:g_DoColor = true; @@ -59,7 +58,6 @@ public OnPluginStart() LoadTranslations("common.phrases"); g_Cvar_Chatmode = CreateConVar("sm_chat_mode", "1", "Allows player's to send messages to admin chat.", 0, true, 0.0, true, 1.0); - g_Cvar_Psaymode = CreateConVar("sm_psay_mode", "0", "Allows player's to use psay 'say @@' alias.", 0, true, 0.0, true, 1.0); RegConsoleCmd("say", Command_SayChat); RegConsoleCmd("say_team", Command_SayAdmin); @@ -114,17 +112,17 @@ public Action:Command_SayChat(client, args) decl String:name[64]; GetClientName(client, name, sizeof(name)); - if (msgStart == 1 && CheckAdminForChat(client)) // sm_say alias + if (msgStart == 1 && CheckCommandAccess(client, "sm_say", ADMFLAG_CHAT)) // sm_say alias { SendChatToAll(name, message); LogAction(client, -1, "%L triggered sm_say (text %s)", client, message); } - else if (msgStart == 3 && CheckAdminForChat(client)) // sm_csay alias + else if (msgStart == 3 && CheckCommandAccess(client, "sm_csay", ADMFLAG_CHAT)) // sm_csay alias { PrintCenterTextAll("%s: %s", name, message); LogAction(client, -1, "%L triggered sm_csay (text %s)", client, text); } - else if (msgStart == 2 && (CheckAdminForChat(client) || GetConVarBool(g_Cvar_Psaymode))) // sm_psay alias + else if (msgStart == 2 && CheckCommandAccess(client, "sm_psay", ADMFLAG_CHAT)) // sm_psay alias { decl String:arg[64]; @@ -158,7 +156,7 @@ public Action:Command_SayChat(client, args) public Action:Command_SayAdmin(client, args) { - if (!CheckAdminForChat(client) && !GetConVarBool(g_Cvar_Chatmode)) + if (!CheckCommandAccess(client, "sm_chat", ADMFLAG_CHAT) && !GetConVarBool(g_Cvar_Chatmode)) { return Plugin_Continue; } @@ -359,16 +357,6 @@ public Action:Command_SmMsay(client, args) return Plugin_Handled; } -bool:CheckAdminForChat(client) -{ - if (client == 0) - { - return true; - } - - return CheckCommandAccess(client, "sm_chat", ADMFLAG_CHAT); -} - FindColor(String:color[]) { for (new i = 0; i < 13; i++) @@ -400,7 +388,7 @@ SendChatToAdmins(String:name[], String:message[]) { if (IsClientInGame(i)) { - if (CheckAdminForChat(i)) + if (CheckCommandAccess(i, "sm_chat", ADMFLAG_CHAT)) { if (g_DoColor) { diff --git a/plugins/basecomm/gag.sp b/plugins/basecomm/gag.sp index 2777aeb2..e8bd2925 100644 --- a/plugins/basecomm/gag.sp +++ b/plugins/basecomm/gag.sp @@ -58,7 +58,7 @@ DisplayGagPlayerMenu(client) SetMenuTitle(menu, title); SetMenuExitBackButton(menu, true); - AddTargetsToMenu(menu, client, false, false); + AddTargetsToMenu(menu, client, true, false); DisplayMenu(menu, client, MENU_TIME_FOREVER); } diff --git a/plugins/basefuncommands/burn.sp b/plugins/basefuncommands/burn.sp index 4c2fcc75..5c969a72 100644 --- a/plugins/basefuncommands/burn.sp +++ b/plugins/basefuncommands/burn.sp @@ -13,7 +13,7 @@ DisplayBurnMenu(client) SetMenuTitle(menu, title); SetMenuExitBackButton(menu, true); - AddTargetsToMenu(menu, client, false, true); + AddTargetsToMenu(menu, client, true, true); DisplayMenu(menu, client, MENU_TIME_FOREVER); } diff --git a/plugins/basefuncommands/slap.sp b/plugins/basefuncommands/slap.sp index bc024f86..1267381b 100644 --- a/plugins/basefuncommands/slap.sp +++ b/plugins/basefuncommands/slap.sp @@ -33,7 +33,7 @@ DisplaySlapTargetMenu(client) SetMenuTitle(menu, title); SetMenuExitBackButton(menu, true); - AddTargetsToMenu(menu, client, false, true); + AddTargetsToMenu(menu, client, true, true); DisplayMenu(menu, client, MENU_TIME_FOREVER); } diff --git a/plugins/basefuncommands/slay.sp b/plugins/basefuncommands/slay.sp index 9e9def91..e1b0b0bd 100644 --- a/plugins/basefuncommands/slay.sp +++ b/plugins/basefuncommands/slay.sp @@ -14,7 +14,7 @@ DisplaySlayMenu(client) SetMenuTitle(menu, title); SetMenuExitBackButton(menu, true); - AddTargetsToMenu(menu, client, false, true); + AddTargetsToMenu(menu, client, true, true); DisplayMenu(menu, client, MENU_TIME_FOREVER); } diff --git a/plugins/basefunvotes/voteburn.sp b/plugins/basefunvotes/voteburn.sp index 2fbc66a0..4ba326a9 100644 --- a/plugins/basefunvotes/voteburn.sp +++ b/plugins/basefunvotes/voteburn.sp @@ -31,7 +31,7 @@ DisplayBurnTargetMenu(client) SetMenuTitle(menu, title); SetMenuExitBackButton(menu, true); - AddTargetsToMenu(menu, client, false, true); + AddTargetsToMenu(menu, client, true, true); DisplayMenu(menu, client, MENU_TIME_FOREVER); } diff --git a/plugins/basefunvotes/voteslay.sp b/plugins/basefunvotes/voteslay.sp index 94e517da..fd8f80c1 100644 --- a/plugins/basefunvotes/voteslay.sp +++ b/plugins/basefunvotes/voteslay.sp @@ -32,7 +32,7 @@ DisplaySlayTargetMenu(client) SetMenuTitle(menu, title); SetMenuExitBackButton(menu, true); - AddTargetsToMenu(menu, client, false, true); + AddTargetsToMenu(menu, client, true, true); DisplayMenu(menu, client, MENU_TIME_FOREVER); } diff --git a/plugins/basevotes.sp b/plugins/basevotes.sp index 6e8d091f..b93c03a7 100644 --- a/plugins/basevotes.sp +++ b/plugins/basevotes.sp @@ -51,8 +51,6 @@ public Plugin:myinfo = new Handle:g_hVoteMenu = INVALID_HANDLE; -new Handle:g_hBanForward = INVALID_HANDLE; - new Handle:g_Cvar_Limits[3] = {INVALID_HANDLE, ...}; //new Handle:g_Cvar_VoteSay = INVALID_HANDLE; @@ -111,8 +109,6 @@ public OnPluginStart() g_Cvar_Limits[1] = CreateConVar("sm_vote_kick", "0.60", "percent required for successful kick vote.", 0, true, 0.05, true, 1.0); g_Cvar_Limits[2] = CreateConVar("sm_vote_ban", "0.60", "percent required for successful ban vote.", 0, true, 0.05, true, 1.0); - g_hBanForward = CreateGlobalForward("OnClientBanned", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_String); - /* Account for late loading */ new Handle:topmenu; if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != INVALID_HANDLE)) @@ -350,14 +346,6 @@ public Handler_VoteCallback(Handle:menu, MenuAction:action, param1, param2) case (voteType:ban): { - /* Fire the ban forward */ - Call_StartForward(g_hBanForward); - Call_PushCell(0); - Call_PushCell(g_voteClient[VOTE_USERID]); - Call_PushCell(30); - Call_PushString(g_voteArg); - Call_Finish(); - if (g_voteArg[0] == '\0') { strcopy(g_voteArg, sizeof(g_voteArg), "Votebanned"); @@ -365,9 +353,13 @@ public Handler_VoteCallback(Handle:menu, MenuAction:action, param1, param2) PrintToChatAll("[SM] %t", "Banned player", g_voteInfo[VOTE_NAME], 30); LogAction(-1, g_voteClient[VOTE_CLIENTID], "Vote ban successful, banned \"%L\" (minutes \"30\") (reason \"%s\")", g_voteClient[VOTE_CLIENTID], g_voteArg); - - ServerCommand("banid %d %s", 30, g_voteClient[VOTE_AUTHID]); - ServerCommand("kickid %d \"%s\"", g_voteClient[VOTE_USERID], g_voteArg); + + BanClient(g_voteClient[VOTE_CLIENTID], + 30, + BANFLAG_AUTO, + g_voteArg, + "Banned by vote", + "sm_voteban"); } } } diff --git a/plugins/basevotes/votemap.sp b/plugins/basevotes/votemap.sp index fcfd0cb6..c878ce77 100644 --- a/plugins/basevotes/votemap.sp +++ b/plugins/basevotes/votemap.sp @@ -111,7 +111,7 @@ public MenuHandler_Map(Handle:menu, MenuAction:action, param1, param2) GetMenuItem(menu, param2, info, sizeof(info), _, name, sizeof(name)); - if (IsStringInArray(g_SelectedMaps, info)) + if (FindStringInArray(g_SelectedMaps, info) != -1) { return ITEMDRAW_IGNORE; } diff --git a/plugins/include/adt_array.inc b/plugins/include/adt_array.inc index e8e0f621..8af2ed8c 100644 --- a/plugins/include/adt_array.inc +++ b/plugins/include/adt_array.inc @@ -266,8 +266,8 @@ native SwapArrayItems(Handle:array, index1, index2); * * @param array Array Handle. * @param item String to search for - * @return Array index, or -1 on failure - * @error Invalid Handle + * @return Array index, or -1 on failure + * @error Invalid Handle */ native FindStringInArray(Handle:array, const String:item[]); @@ -277,8 +277,8 @@ native FindStringInArray(Handle:array, const String:item[]); * * @param array Array Handle. * @param item Value to search for - * @return Array index, or -1 on failure - * @error Invalid Handle + * @return Array index, or -1 on failure + * @error Invalid Handle */ native FindValueInArray(Handle:array, item); @@ -290,4 +290,4 @@ native FindValueInArray(Handle:array, item); stock bool:IsStringInArray(Handle:array, const String:item[]) { return (FindStringInArray(array, item) != -1); -} \ No newline at end of file +} diff --git a/plugins/include/keyvalues.inc b/plugins/include/keyvalues.inc index 715ddc44..deb21e6c 100644 --- a/plugins/include/keyvalues.inc +++ b/plugins/include/keyvalues.inc @@ -191,6 +191,15 @@ native KvGetUInt64(Handle:kv, const String:key[], value[2], defvalue[2]={0,0}); */ native bool:KvJumpToKey(Handle:kv, const String:key[], bool:create=false); +/** + * Sets the current position in the KeyValues tree to the given key. + * + * @param kv KeyValues Handle. + * @param id KeyValues id. + * @return True if the key exists, false if it does not. + */ +native bool:KvJumpToKeySymbol(Handle:kv, id); + /** * Sets the current position in the KeyValues tree to the first sub key. * This native adds to the internal traversal stack. @@ -385,3 +394,13 @@ native bool:KvFindKeyById(Handle:kv, id, String:name[], maxlength); * @error Invalid Handle. */ native bool:KvGetNameSymbol(Handle:kv, const String:key[], &id); + +/** + * Retrieves the current section id. + * + * @param kv KeyValues Handle. + * @param id Id of the current section. + * @return True on success, false on failure. + * @error Invalid Handle. + */ +native bool:KvGetSectionSymbol(Handle:kv, &id); diff --git a/plugins/include/menus.inc b/plugins/include/menus.inc index 90559b0c..7bac6f76 100644 --- a/plugins/include/menus.inc +++ b/plugins/include/menus.inc @@ -732,6 +732,26 @@ native bool:SetPanelCurrentKey(Handle:panel, key); */ native RedrawMenuItem(const String:text[]); +/** + * This function is provided for legacy code only. Some older plugins may use + * network messages instead of the panel API. This function wraps the panel + * API for eased portability into the SourceMod menu system. + * + * This function is only usable with the Radio Menu style. You do not need to + * split up your menu into multiple packets; SourceMod will break the string + * up internally. + * + * @param client Client index. + * @param str Full menu string as would be passed over the network. + * @param time Time to hold the menu for. + * @param keys Selectable key bitstring. + * @param handler Optional handler function, with the same rules as + * SendPanelToClient(). + * @return True on success, false on failure. + * @error Invalid client index, or radio menus not supported. + */ +native bool:InternalShowMenu(client, const String:str[], time, keys=-1, MenuHandler:handler=MenuHandler:-1); + /** * Retrieves voting information from MenuAction_VoteEnd. * diff --git a/plugins/include/sorting.inc b/plugins/include/sorting.inc index dfdcb53e..6d49a238 100644 --- a/plugins/include/sorting.inc +++ b/plugins/include/sorting.inc @@ -45,6 +45,16 @@ enum SortOrder Sort_Descending = 1, /**< Descending order */ }; +/** + * Data types for ADT Array Sorts + */ + enum SortType + { + Sort_Integer = 0, + Sort_Float, + Sort_String, +}; + /** * Sorts an array of integers. * @@ -128,3 +138,38 @@ funcenum SortFunc2D * @noreturn */ native SortCustom2D(array[][], array_size, SortFunc2D:sortfunc, Handle:hndl=INVALID_HANDLE); + +/** + * Sort an ADT Array. Specify the type as Integer, Float, or String. + * + * @param array Array Handle to sort + * @param order Sort order to use, same as other sorts. + * @param type Data type stored in the ADT Array + * @noreturn + */ +native SortADTArray(Handle:array, SortOrder:order = Sort_Ascending, SortType:type = Sort_Integer); + +/** + * Sort comparison function for ADT Array elements. Function provides you with + * indexes currently being sorted, use ADT Array functions to retrieve the + * index values and compare. + * + * @param index1 First index to compare. + * @param index2 Second index to compare. + * @param array Array that is being sorted (order is undefined). + * @param hndl Handle optionally passed in while sorting. + * @return -1 if first should go before second + * 0 if first is equal to second + * 1 if first should go after second + */ +functag SortFuncADTArray public(index1, index2, Handle:array, Handle:hndl); + +/** + * Custom sorts an ADT Array. You must pass in a comparison function. + * + * @param array Array Handle to sort + * @param sortfunc Sort comparision function to use + * @param hndl Optional Handle to pass through the comparison calls. + * @noreturn + */ +native SortADTArrayCustom(Handle:array, SortFuncADTArray:sortfunc, Handle:hndl=INVALID_HANDLE); \ No newline at end of file diff --git a/plugins/include/topmenus.inc b/plugins/include/topmenus.inc index e3dc823f..a59f2fa0 100644 --- a/plugins/include/topmenus.inc +++ b/plugins/include/topmenus.inc @@ -1,277 +1,290 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. - * ============================================================================= - * - * This file is part of the SourceMod/SourcePawn SDK. - * - * 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$ - */ - -#if defined _topmenus_included - #endinput -#endif -#define _topmenus_included - -#include - -/** - * Actions a top menu will take on an object. - */ -enum TopMenuAction -{ - /** - * An option is being drawn for a menu (or for sorting purposes). - * - * INPUT : TopMenu Handle, object ID, client index. - * OUTPUT: Buffer for rendering, maxlength of buffer. - */ - TopMenuAction_DisplayOption = 0, - - /** - * The title of a menu is being drawn for a given object. - * - * Note: The Object ID will be INVALID_TOPMENUOBJECT if drawing the - * root title. Otherwise, the Object ID is a category. - * - * INPUT : TopMenu Handle, object ID, client index. - * OUTPUT: Buffer for rendering, maxlength of buffer. - */ - TopMenuAction_DisplayTitle = 1, - - /** - * A menu option has been selected. - * - * The Object ID will always be an item (not a category). - * - * INPUT : TopMenu Handle, object ID, client index. - */ - TopMenuAction_SelectOption = 2, - - /** - * A menu option is being drawn and its flags can be overridden. - * - * INPUT : TopMenu Handle, object ID, client index. - * OUTPUT: The first byte of the 'buffer' string should be set - * to the desired flags. By default, it will contain - * ITEMDRAW_DEFAULT. - */ - TopMenuAction_DrawOption = 3, - - /** - * Called when an object is being removed from the menu. - * This can be used to clean up data stored in the info string. - * - * INPUT : TopMenu Handle, object ID. - */ - TopMenuAction_RemoveObject = 4, -}; - -/** - * Top menu object types. - */ -enum TopMenuObjectType -{ - TopMenuObject_Category = 0, /**< Category (sub-menu branching from root) */ - TopMenuObject_Item = 1 /**< Item on a sub-menu */ -}; - -/** - * Top menu starting positions for display. - */ -enum TopMenuPosition -{ - TopMenuPosition_Start = 0, /**< Start/root of the menu */ - TopMenuPosition_LastRoot = 1, /**< Last position in the root menu */ - TopMenuPosition_LastCategory = 3, /**< Last position in their last category */ -}; - -/** - * Top menu object tag for type checking. - */ -enum TopMenuObject -{ - INVALID_TOPMENUOBJECT = 0, -} - -/** - * TopMenu callback prototype. - * - * @param topmenu Handle to the TopMenu. - * @param action TopMenuAction being performed. - * @param object_id The object ID (if used). - * @param param Extra parameter (if used). - * @param buffer Output buffer (if used). - * @param maxlength Output buffer (if used). - * @noreturn - */ -functag TopMenuHandler public(Handle:topmenu, - TopMenuAction:action, - TopMenuObject:object_id, - param, - String:buffer[], - maxlength); - -/** - * Creates a TopMenu. - * - * @param handler Handler to use for drawing the root title. - * @return A new TopMenu Handle, or INVALID_HANDLE on failure. - */ -native Handle:CreateTopMenu(TopMenuHandler:handler); - -/** - * Re-sorts the items in a TopMenu via a configuration file. - * - * The format of the configuration file should be a Valve Key-Values - * formatted file that SourceMod can parse. There should be one root - * section, and one sub-section for each category. Each sub-section's - * name should match the category name. - * - * Each sub-section may only contain key/value pairs in the form of: - * key: "item" - * value: Name of the item as passed to AddToTopMenu(). - * - * The TopMenu will draw items in the order declared in the configuration - * file. If items do not appear in the configuration file, they are sorted - * per-player based on how the handler function renders for that player. - * These items appear after the configuration sorted items. - * - * @param topmenu TopMenu Handle. - * @param file File path. - * @param error Error buffer. - * @param maxlength Maximum size of the error buffer. - * Error buffer will be filled with a - * zero-terminated string if false is - * returned. - * @return True on success, false on failure. - * @error Invalid TopMenu Handle. - */ -native bool:LoadTopMenuConfig(Handle:topmenu, const String:file[], String:error[], maxlength); - -/** - * Adds an object to a TopMenu. - * - * @param topmenu TopMenu Handle. - * @param name Object name (MUST be unique). - * @param type Object type. - * @param handler Handler for object. - * @param cmdname Command name (for access overrides). - * @param flags Default access flags. - * @param parent Parent object ID, or INVALID_TOPMENUOBJECT for none. - * Items must have a category parent. - * Categories must not have a parent. - * @param info_string Arbitrary storage (max 255 bytes). - * @return A new TopMenuObject ID, or INVALID_TOPMENUOBJECT on - * failure. - * @error Invalid TopMenu Handle. - */ -native TopMenuObject:AddToTopMenu(Handle:topmenu, - const String:name[], - TopMenuObjectType:type, - TopMenuHandler:handler, - TopMenuObject:parent, - const String:cmdname[]="", - flags=0, - const String:info_string[]=""); - -/** - * Retrieves the info string of a top menu item. - * - * @param topmenu TopMenu Handle. - * @param object TopMenuObject ID. - * @param buffer Buffer to store info string. - * @param maxlength Maximum size of info string. - * @return Number of bytes written, not including the - * null terminator. - * @error Invalid TopMenu Handle or TopMenuObject ID. - */ -native GetTopMenuInfoString(Handle:topmenu, TopMenuObject:parent, String:buffer[], maxlength); - -/** - * Removes an object from a TopMenu. - * - * Plugins' objects are automatically removed all TopMenus when the given - * plugin unloads or pauses. In the case of unpausing, all items are restored. - * - * @param topmenu TopMenu Handle. - * @param object TopMenuObject ID. - * @noreturn - * @error Invalid TopMenu Handle. - */ -native RemoveFromTopMenu(Handle:topmenu, TopMenuObject:object); - -/** - * Displays a TopMenu to a client. - * - * @param topmenu TopMenu Handle. - * @param client Client index. - * @param position Position to display from. - * @return True on success, false on failure. - * @error Invalid TopMenu Handle or client not in game. - */ -native bool:DisplayTopMenu(Handle:topmenu, client, TopMenuPosition:position); - -/** - * Finds a category's object ID in a TopMenu. - * - * @param topmenu TopMenu Handle. - * @param name Object's unique name. - * @return TopMenuObject ID on success, or - * INVALID_TOPMENUOBJECT on failure. - * @error Invalid TopMenu Handle. - */ -native TopMenuObject:FindTopMenuCategory(Handle:topmenu, const String:name[]); - -/** - * Do not edit below this line! - */ -public Extension:__ext_topmenus = -{ - name = "TopMenus", - file = "topmenus.ext", -#if defined AUTOLOAD_EXTENSIONS - autoload = 1, -#else - autoload = 0, -#endif -#if defined REQUIRE_EXTENSIONS - required = 1, -#else - required = 0, -#endif -}; - -#if !defined REQUIRE_EXTENSIONS -public __ext_topmenus_SetNTVOptional() -{ - MarkNativeAsOptional("CreateTopMenu"); - MarkNativeAsOptional("LoadTopMenuConfig"); - MarkNativeAsOptional("AddToTopMenu"); - MarkNativeAsOptional("RemoveFromTopMenu"); - MarkNativeAsOptional("DisplayTopMenu"); - MarkNativeAsOptional("FindTopMenuCategory"); -} -#endif +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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$ + */ + +#if defined _topmenus_included + #endinput +#endif +#define _topmenus_included + +#include + +/** + * Actions a top menu will take on an object. + */ +enum TopMenuAction +{ + /** + * An option is being drawn for a menu (or for sorting purposes). + * + * INPUT : TopMenu Handle, object ID, client index. + * OUTPUT: Buffer for rendering, maxlength of buffer. + */ + TopMenuAction_DisplayOption = 0, + + /** + * The title of a menu is being drawn for a given object. + * + * Note: The Object ID will be INVALID_TOPMENUOBJECT if drawing the + * root title. Otherwise, the Object ID is a category. + * + * INPUT : TopMenu Handle, object ID, client index. + * OUTPUT: Buffer for rendering, maxlength of buffer. + */ + TopMenuAction_DisplayTitle = 1, + + /** + * A menu option has been selected. + * + * The Object ID will always be an item (not a category). + * + * INPUT : TopMenu Handle, object ID, client index. + */ + TopMenuAction_SelectOption = 2, + + /** + * A menu option is being drawn and its flags can be overridden. + * + * INPUT : TopMenu Handle, object ID, client index. + * OUTPUT: The first byte of the 'buffer' string should be set + * to the desired flags. By default, it will contain + * ITEMDRAW_DEFAULT. + */ + TopMenuAction_DrawOption = 3, + + /** + * Called when an object is being removed from the menu. + * This can be used to clean up data stored in the info string. + * + * INPUT : TopMenu Handle, object ID. + */ + TopMenuAction_RemoveObject = 4, +}; + +/** + * Top menu object types. + */ +enum TopMenuObjectType +{ + TopMenuObject_Category = 0, /**< Category (sub-menu branching from root) */ + TopMenuObject_Item = 1 /**< Item on a sub-menu */ +}; + +/** + * Top menu starting positions for display. + */ +enum TopMenuPosition +{ + TopMenuPosition_Start = 0, /**< Start/root of the menu */ + TopMenuPosition_LastRoot = 1, /**< Last position in the root menu */ + TopMenuPosition_LastCategory = 3, /**< Last position in their last category */ +}; + +/** + * Top menu object tag for type checking. + */ +enum TopMenuObject +{ + INVALID_TOPMENUOBJECT = 0, +} + +/** + * TopMenu callback prototype. + * + * @param topmenu Handle to the TopMenu. + * @param action TopMenuAction being performed. + * @param object_id The object ID (if used). + * @param param Extra parameter (if used). + * @param buffer Output buffer (if used). + * @param maxlength Output buffer (if used). + * @noreturn + */ +functag TopMenuHandler public(Handle:topmenu, + TopMenuAction:action, + TopMenuObject:object_id, + param, + String:buffer[], + maxlength); + +/** + * Creates a TopMenu. + * + * @param handler Handler to use for drawing the root title. + * @return A new TopMenu Handle, or INVALID_HANDLE on failure. + */ +native Handle:CreateTopMenu(TopMenuHandler:handler); + +/** + * Re-sorts the items in a TopMenu via a configuration file. + * + * The format of the configuration file should be a Valve Key-Values + * formatted file that SourceMod can parse. There should be one root + * section, and one sub-section for each category. Each sub-section's + * name should match the category name. + * + * Each sub-section may only contain key/value pairs in the form of: + * key: "item" + * value: Name of the item as passed to AddToTopMenu(). + * + * The TopMenu will draw items in the order declared in the configuration + * file. If items do not appear in the configuration file, they are sorted + * per-player based on how the handler function renders for that player. + * These items appear after the configuration sorted items. + * + * @param topmenu TopMenu Handle. + * @param file File path. + * @param error Error buffer. + * @param maxlength Maximum size of the error buffer. + * Error buffer will be filled with a + * zero-terminated string if false is + * returned. + * @return True on success, false on failure. + * @error Invalid TopMenu Handle. + */ +native bool:LoadTopMenuConfig(Handle:topmenu, const String:file[], String:error[], maxlength); + +/** + * Adds an object to a TopMenu. + * + * @param topmenu TopMenu Handle. + * @param name Object name (MUST be unique). + * @param type Object type. + * @param handler Handler for object. + * @param cmdname Command name (for access overrides). + * @param flags Default access flags. + * @param parent Parent object ID, or INVALID_TOPMENUOBJECT for none. + * Items must have a category parent. + * Categories must not have a parent. + * @param info_string Arbitrary storage (max 255 bytes). + * @return A new TopMenuObject ID, or INVALID_TOPMENUOBJECT on + * failure. + * @error Invalid TopMenu Handle. + */ +native TopMenuObject:AddToTopMenu(Handle:topmenu, + const String:name[], + TopMenuObjectType:type, + TopMenuHandler:handler, + TopMenuObject:parent, + const String:cmdname[]="", + flags=0, + const String:info_string[]=""); + +/** + * Retrieves the info string of a top menu item. + * + * @param topmenu TopMenu Handle. + * @param object TopMenuObject ID. + * @param buffer Buffer to store info string. + * @param maxlength Maximum size of info string. + * @return Number of bytes written, not including the + * null terminator. + * @error Invalid TopMenu Handle or TopMenuObject ID. + */ +native GetTopMenuInfoString(Handle:topmenu, TopMenuObject:parent, String:buffer[], maxlength); + +/** + * Retrieves the name string of a top menu item. + * + * @param topmenu TopMenu Handle. + * @param object TopMenuObject ID. + * @param buffer Buffer to store info string. + * @param maxlength Maximum size of info string. + * @return Number of bytes written, not including the + * null terminator. + * @error Invalid TopMenu Handle or TopMenuObject ID. + */ +native GetTopMenuObjName(Handle:topmenu, TopMenuObject:object, String:buffer[], maxlength); + +/** + * Removes an object from a TopMenu. + * + * Plugins' objects are automatically removed all TopMenus when the given + * plugin unloads or pauses. In the case of unpausing, all items are restored. + * + * @param topmenu TopMenu Handle. + * @param object TopMenuObject ID. + * @noreturn + * @error Invalid TopMenu Handle. + */ +native RemoveFromTopMenu(Handle:topmenu, TopMenuObject:object); + +/** + * Displays a TopMenu to a client. + * + * @param topmenu TopMenu Handle. + * @param client Client index. + * @param position Position to display from. + * @return True on success, false on failure. + * @error Invalid TopMenu Handle or client not in game. + */ +native bool:DisplayTopMenu(Handle:topmenu, client, TopMenuPosition:position); + +/** + * Finds a category's object ID in a TopMenu. + * + * @param topmenu TopMenu Handle. + * @param name Object's unique name. + * @return TopMenuObject ID on success, or + * INVALID_TOPMENUOBJECT on failure. + * @error Invalid TopMenu Handle. + */ +native TopMenuObject:FindTopMenuCategory(Handle:topmenu, const String:name[]); + +/** + * Do not edit below this line! + */ +public Extension:__ext_topmenus = +{ + name = "TopMenus", + file = "topmenus.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_EXTENSIONS +public __ext_topmenus_SetNTVOptional() +{ + MarkNativeAsOptional("CreateTopMenu"); + MarkNativeAsOptional("LoadTopMenuConfig"); + MarkNativeAsOptional("AddToTopMenu"); + MarkNativeAsOptional("RemoveFromTopMenu"); + MarkNativeAsOptional("DisplayTopMenu"); + MarkNativeAsOptional("FindTopMenuCategory"); +} +#endif diff --git a/plugins/mapchooser.sp b/plugins/mapchooser.sp index 4508c18b..2daed5f2 100644 --- a/plugins/mapchooser.sp +++ b/plugins/mapchooser.sp @@ -74,6 +74,7 @@ new Handle:g_NextMapList = INVALID_HANDLE; new Handle:g_TeamScores = INVALID_HANDLE; new Handle:g_VoteMenu = INVALID_HANDLE; +new g_TotalRounds; new bool:g_HasVoteStarted; new g_mapFileTime; @@ -87,7 +88,6 @@ public OnPluginStart() g_MapList = CreateArray(arraySize); g_OldMapList = CreateArray(arraySize); g_NextMapList = CreateArray(arraySize); - g_TeamScores = CreateArray(2); g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0); g_Cvar_StartRounds = CreateConVar("sm_mapvote_startround", "2.0", "Specifies when to start the vote based on rounds remaining.", _, true, 1.0); @@ -139,6 +139,14 @@ public OnConfigsExecuted() SetupTimeleftTimer(); SetConVarString(g_Cvar_Nextmap, "Pending Vote"); } + + if (g_TeamScores != INVALID_HANDLE) + { + CloseHandle(g_TeamScores); + } + + g_TeamScores = CreateArray(2); + g_TotalRounds = 0; } public OnMapEnd() @@ -225,8 +233,7 @@ public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast) return; } - static total; - total++; + g_TotalRounds++; new team[2], teamPos = -1; for (new i; i < GetArraySize(g_TeamScores); i++) @@ -268,7 +275,7 @@ public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast) new maxrounds = GetConVarInt(g_Cvar_Maxrounds); if (maxrounds) { - if (total >= (maxrounds - GetConVarInt(g_Cvar_StartRounds))) + if (g_TotalRounds >= (maxrounds - GetConVarInt(g_Cvar_StartRounds))) { InitiateVote(); } diff --git a/plugins/reservedslots.sp b/plugins/reservedslots.sp index e4ebe293..defdb900 100644 --- a/plugins/reservedslots.sp +++ b/plugins/reservedslots.sp @@ -90,10 +90,8 @@ public OnConfigsExecuted() } } -public Action:OnTimedKick(Handle:timer, any:value) -{ - new client = GetClientOfUserId(value); - +public Action:OnTimedKick(Handle:timer, any:client) +{ if (!client || !IsClientInGame(client)) { return Plugin_Handled; @@ -101,6 +99,11 @@ public Action:OnTimedKick(Handle:timer, any:value) KickClient(client, "%T", "Slot reserved", client); + if (GetConVarBool(sm_hide_slots)) + { + SetVisibleMaxSlots(GetClientCount(false), g_MaxClients - GetConVarInt(sm_reserved_slots)); + } + return Plugin_Handled; } @@ -113,32 +116,45 @@ public OnClientPostAdminCheck(client) new clients = GetClientCount(false); new limit = g_MaxClients - reserved; new flags = GetUserFlagBits(client); - - if (clients <= limit || IsFakeClient(client) || flags & ADMFLAG_ROOT || flags & ADMFLAG_RESERVATION) + + new type = GetConVarInt(sm_reserve_type); + + if (type == 0) { - if (GetConVarBool(sm_hide_slots)) + if (clients <= limit || IsFakeClient(client) || flags & ADMFLAG_ROOT || flags & ADMFLAG_RESERVATION) { - SetVisibleMaxSlots(clients, limit); + if (GetConVarBool(sm_hide_slots)) + { + SetVisibleMaxSlots(clients, limit); + } + + return; } - new type = GetConVarInt(sm_reserve_type); - - if (type == 1) - { - new target = SelectKickClient(); - - if (target) + /* Kick player because there are no public slots left */ + CreateTimer(0.1, OnTimedKick, client); + } + else + { + if (clients > limit) + { + if (flags & ADMFLAG_ROOT || flags & ADMFLAG_RESERVATION) { - /* Kick public player to free the reserved slot again */ - CreateTimer(0.1, OnTimedKick, GetClientUserId(target)); + new target = SelectKickClient(); + + if (target) + { + /* Kick public player to free the reserved slot again */ + CreateTimer(0.1, OnTimedKick, target); + } + } + else + { + /* Kick player because there are no public slots left */ + CreateTimer(0.1, OnTimedKick, client); } } - - return; } - - /* Kick player because there are no public slots left */ - CreateTimer(0.1, OnTimedKick, GetClientUserId(client)); } } @@ -161,7 +177,7 @@ public SlotsChanged(Handle:convar, const String:oldValue[], const String:newValu SetVisibleMaxSlots(clients, limit) { - new num = clients + 1; + new num = clients; if (clients == g_MaxClients) { @@ -186,7 +202,7 @@ SelectKickClient() new Float:latency; for (new i=1; i<=g_MaxClients; i++) - { + { if (!IsClientConnected(i)) { continue; @@ -198,13 +214,17 @@ SelectKickClient() { continue; } + + latency = 0.0; if (IsClientInGame(i)) - { - latency = GetClientAvgLatency(i, NetFlow_Both); + { + latency = GetClientAvgLatency(i, NetFlow_Outgoing); + + LogMessage("Latency : %f",latency); if (IsClientObserver(i)) - { + { specFound = true; if (latency > highestSpecLatency) @@ -214,12 +234,8 @@ SelectKickClient() } } } - else - { - latency = 0.0; - } - if (latency > highestLatency) + if (latency >= highestLatency) { highestLatency = latency; highestLatencyId = i; diff --git a/plugins/rockthevote.sp b/plugins/rockthevote.sp index 1e0d9ee0..cc4b5b50 100644 --- a/plugins/rockthevote.sp +++ b/plugins/rockthevote.sp @@ -88,27 +88,15 @@ public OnPluginStart() AutoExecConfig(true, "rtv"); } - -public OnConfigsExecuted() -{ - if (g_RTVMapList != INVALID_HANDLE) - { - ClearArray(g_RTVMapList); - } - - g_Voters = 0; - g_Votes = 0; - g_VotesNeeded = 0; - g_RTVStarted = false; - g_RTVEnded = false; - - if (LoadMaps(g_MapList, g_mapFileTime, g_Cvar_File)) - { - BuildMapMenu(); - g_CanRTV = true; - CreateTimer(30.0, Timer_DelayRTV); - } -} + +public OnMapStart() +{ + g_Voters = 0; + g_Votes = 0; + g_VotesNeeded = 0; + g_RTVStarted = false; + g_RTVEnded = false; +} public OnMapEnd() { @@ -116,10 +104,24 @@ public OnMapEnd() g_RTVAllowed = false; } +public OnConfigsExecuted() +{ + if (g_RTVMapList != INVALID_HANDLE) + { + ClearArray(g_RTVMapList); + } + + if (LoadMaps(g_MapList, g_mapFileTime, g_Cvar_File)) + { + BuildMapMenu(); + g_CanRTV = true; + CreateTimer(30.0, Timer_DelayRTV); + } +} public bool:OnClientConnect(client, String:rejectmsg[], maxlen) { - if(IsFakeClient(client)) + if(!g_CanRTV || IsFakeClient(client)) return true; g_Voted[client] = false; @@ -133,7 +135,7 @@ public bool:OnClientConnect(client, String:rejectmsg[], maxlen) public OnClientDisconnect(client) { - if(IsFakeClient(client)) + if(!g_CanRTV || IsFakeClient(client)) return; if(g_Voted[client]) @@ -145,8 +147,9 @@ public OnClientDisconnect(client) g_VotesNeeded = RoundToFloor(float(g_Voters) * GetConVarFloat(g_Cvar_Needed)); - if (g_Votes && g_Voters && g_Votes >= g_VotesNeeded && g_RTVAllowed) - { + if (g_Votes && g_Voters && g_Votes >= g_VotesNeeded && g_RTVAllowed && !g_RTVStarted) + { + g_RTVStarted = true; CreateTimer(2.0, Timer_StartRTV, TIMER_FLAG_NO_MAPCHANGE); } } @@ -157,6 +160,12 @@ public Action:Command_Addmap(client, args) { ReplyToCommand(client, "[SM] Usage: sm_rtv_addmap "); return Plugin_Handled; + } + + if (!g_CanRTV) + { + ReplyToCommand(client, "[SM] RockTheVote is not available."); + return Plugin_Handled; } decl String:mapname[64]; @@ -208,8 +217,10 @@ public Action:Command_Addmap(client, args) public Action:Command_Say(client, args) { - if (!g_CanRTV || !client) - return Plugin_Continue; + if (!g_CanRTV || !client) + { + return Plugin_Continue; + } decl String:text[192]; if (!GetCmdArgString(text, sizeof(text))) @@ -265,7 +276,8 @@ public Action:Command_Say(client, args) PrintToChatAll("[SM] %t", "RTV Requested", name, g_Votes, g_VotesNeeded); if (g_Votes >= g_VotesNeeded) - { + { + g_RTVStarted = true; CreateTimer(2.0, Timer_StartRTV, TIMER_FLAG_NO_MAPCHANGE); } } @@ -318,8 +330,6 @@ public Action:Timer_StartRTV(Handle:timer) return; } - g_RTVStarted = true; - if (IsVoteInProgress()) { // Can't start a vote, try again in 5 seconds. @@ -429,7 +439,8 @@ public Handler_MapMapVoteMenu(Handle:menu, MenuAction:action, param1, param2) { if (param1 == VoteCancel_NoVotes) { - PrintToChatAll("[SM] %t", "No Votes"); + PrintToChatAll("[SM] %t", "No Votes"); + g_RTVEnded = true; } } diff --git a/plugins/testsuite/sorttest.sp b/plugins/testsuite/sorttest.sp index 1398a7d5..bd9d4d87 100644 --- a/plugins/testsuite/sorttest.sp +++ b/plugins/testsuite/sorttest.sp @@ -9,13 +9,17 @@ public Plugin:myinfo = url = "http://www.sourcemod.net/" }; -public OnPluginStart(Handle:myself) +public OnPluginStart() { RegServerCmd("test_sort_ints", Command_TestSortInts) RegServerCmd("test_sort_floats", Command_TestSortFloats) RegServerCmd("test_sort_strings", Command_TestSortStrings) RegServerCmd("test_sort_1d", Command_TestSort1D) RegServerCmd("test_sort_2d", Command_TestSort2D) + RegServerCmd("test_adtsort_ints", Command_TestSortADTInts) + RegServerCmd("test_adtsort_floats", Command_TestSortADTFloats) + RegServerCmd("test_adtsort_strings", Command_TestSortADTStrings) + RegServerCmd("test_adtsort_custom", Command_TestSortADTCustom) } /***************** @@ -138,7 +142,7 @@ public Action:Command_TestSortStrings(args) public Custom2DSort(String:elem1[], String:elem2[], String:array[][], Handle:hndl) { - return StrCompare(elem1, elem2) + return strcmp(elem1, elem2) } public Action:Command_TestSort2D(args) @@ -162,3 +166,142 @@ public Action:Command_TestSort2D(args) return Plugin_Handled } + +/******************* + * ADT ARRAY TESTS * + *******************/ +// Int and floats work the same as normal comparisions. Strings are direct +// comparisions with no hacky memory stuff like Pawn arrays. + +PrintADTArrayIntegers(Handle:array) +{ + new size = GetArraySize(array); + for (new i=0; i #include +/** + * @file IDBDriver.h + * @brief Defines interfaces for interacting with relational databases. + */ + #define SMINTERFACE_DBI_NAME "IDBI" #define SMINTERFACE_DBI_VERSION 5 @@ -784,7 +789,7 @@ namespace SourceMod * @param type A DBHandleType value. * @param ptr A pointer corrresponding to a DBHandleType * object. - * @param token Identity pointer of the owning identity. + * @param pToken Identity pointer of the owning identity. * @return A new Handle_t handle, or 0 on failure. */ virtual Handle_t CreateHandle(DBHandleType type, void *ptr, IdentityToken_t *pToken) =0; @@ -814,7 +819,7 @@ namespace SourceMod * @brief Given a driver name, attempts to find it. If it is not found, SourceMod * will attempt to load it. This function is not thread safe. * - * @param name Driver identifier name. + * @param driver Driver identifier name. * @return IDBDriver pointer on success, NULL otherwise. */ virtual IDBDriver *FindOrLoadDriver(const char *driver) =0; diff --git a/public/IExtensionSys.h b/public/IExtensionSys.h index ec0b4211..3f4989a2 100644 --- a/public/IExtensionSys.h +++ b/public/IExtensionSys.h @@ -127,8 +127,11 @@ namespace SourceMod /** * @brief Version code of the IExtensionInterface API itself. + * + * Note: This is bumped when IShareSys is changed, because IShareSys + * itself is not versioned. */ - #define SMINTERFACE_EXTENSIONAPI_VERSION 2 + #define SMINTERFACE_EXTENSIONAPI_VERSION 3 /** * @brief The interface an extension must expose. @@ -337,7 +340,6 @@ namespace SourceMod * * @param path Path to extension file, relative to the * extensions folder. - * @param lifetime Lifetime of the extension (currently ignored). * @param error Error buffer. * @param maxlength Maximum error buffer length. * @return New IExtension on success, NULL on failure. diff --git a/public/IMemoryUtils.h b/public/IMemoryUtils.h index 1e90925c..1d6ba093 100644 --- a/public/IMemoryUtils.h +++ b/public/IMemoryUtils.h @@ -37,6 +37,11 @@ #define SMINTERFACE_MEMORYUTILS_NAME "IMemoryUtils" #define SMINTERFACE_MEMORYUTILS_VERSION 1 +/** + * @file IMemoryUtils.h + * @brief Interface for finding patterns in memory. + */ + namespace SourceMod { class IMemoryUtils : public SMInterface diff --git a/public/IMenuManager.h b/public/IMenuManager.h index 60a07393..cb97a2f4 100644 --- a/public/IMenuManager.h +++ b/public/IMenuManager.h @@ -265,7 +265,7 @@ namespace SourceMod * @brief Returns whether the display is capable of rendering an item * with the given flags. * - * @param flags ITEMDRAW flags. + * @param drawFlags ITEMDRAW flags. * @return True if renderable, false otherwise. */ virtual bool CanDrawItem(unsigned int drawFlags) =0; @@ -289,7 +289,7 @@ namespace SourceMod * @brief Sets the selectable key map. Returns false if the function * is not supported. * - * @param keys A bit string where each bit N-1 specifies + * @param keymap A bit string where each bit N-1 specifies * that key N is selectable (key 0 is bit 9). * If the selectable key map is 0, it will be * automatically set to allow 0. @@ -662,6 +662,7 @@ namespace SourceMod * @brief A display/selection cycle has ended. * * @param menu Menu pointer. + * @param reason MenuEndReason reason. */ virtual void OnMenuEnd(IBaseMenu *menu, MenuEndReason reason) { @@ -740,6 +741,7 @@ namespace SourceMod * always be called. * * @param menu Menu pointer. + * @param reason VoteCancelReason reason. */ virtual void OnMenuVoteCancel(IBaseMenu *menu, VoteCancelReason reason) { @@ -823,6 +825,7 @@ namespace SourceMod * * @param client Client index. * @param states Menu states. + * @param order Order to search for items. * @return IMenuPanel pointer, or NULL if no items could be * found in the IBaseMenu pointer, or NULL if any * other error occurred. Any valid pointer must diff --git a/public/IPlayerHelpers.h b/public/IPlayerHelpers.h index 34e3aa8e..18efba8b 100644 --- a/public/IPlayerHelpers.h +++ b/public/IPlayerHelpers.h @@ -41,7 +41,7 @@ #include #define SMINTERFACE_PLAYERMANAGER_NAME "IPlayerManager" -#define SMINTERFACE_PLAYERMANAGER_VERSION 6 +#define SMINTERFACE_PLAYERMANAGER_VERSION 7 struct edict_t; class IPlayerInfo; @@ -147,6 +147,42 @@ namespace SourceMod * @return IPlayerInfo pointer, or NULL if none. */ virtual IPlayerInfo *GetPlayerInfo() =0; + + /** + * @brief Runs through Core's admin authorization checks. If the + * client is already an admin, no checks are performed. + * + * Note that this function operates solely against the in-memory admin + * cache. It will check steamids, IPs, names, and verify a password + * if one exists. To implement other authentication schemes, simply + * don't call this function and use IGamePlayer::SetAdminId() instead. + * + * @return True if access changed, false otherwise. + */ + virtual bool RunAdminCacheChecks() =0; + + /** + * @brief Notifies all listeners that the client has completed + * all of your post-connection (in-game, auth, admin) checks. + * + * If you returned "false" from OnClientPreAdminCheck(), you must + * ALWAYS manually invoke this function, even if RunAdminCacheChecks() + * failed or you did not assign an AdminId. Failure to call this + * function could result in plugins (such as reservedslots) not + * working properly. + * + * If you are implementing asynchronous fetches, and the client + * disconnects during your fetching process, you should make sure to + * recognize that case and not call this function. That is, do not + * call this function on mismatched PreCheck calls, or on disconnected + * clients. A good way to check this is to pass userids around, which + * are unique per client connection. + * + * Calling this has no effect if it has already been called on the + * given client (thus it is safe for multiple asynchronous plugins to + * call it at various times). + */ + virtual void NotifyPostAdminChecks() =0; }; /** @@ -230,6 +266,41 @@ namespace SourceMod virtual void OnServerActivated(int max_clients) { } + + /** + * @brief Called once a client is authorized and fully in-game, but + * before admin checks are done. This can be used to override the + * default admin checks for a client. + * + * By default, this function allows the authentication process to + * continue as normal. If you need to delay the cache searching + * process in order to get asynchronous data, then return false here. + * + * If you return false, you must call IPlayerManager::NotifyPostAdminCheck + * for the same client, or else the OnClientPostAdminCheck callback will + * never be called. + * + * @param client Client index. + * @return True to continue normally, false to override + * the authentication process. + */ + virtual bool OnClientPreAdminCheck(int client) + { + return true; + } + + /** + * @brief Called once a client is authorized and fully in-game, and + * after all post-connection authorizations have been passed. If the + * client does not have an AdminId by this stage, it means that no + * admin entry was in the cache that matched, and the user could not + * be authenticated as an admin. + * + * @param client Client index. + */ + virtual void OnClientPostAdminCheck(int client) + { + } }; #define COMMAND_FILTER_ALIVE (1<<0) /**< Only allow alive players */ diff --git a/public/IShareSys.h b/public/IShareSys.h index c97db5e5..84363bbf 100644 --- a/public/IShareSys.h +++ b/public/IShareSys.h @@ -200,6 +200,29 @@ namespace SourceMod * @param name Library name. */ virtual void RegisterLibrary(IExtension *myself, const char *name) =0; + + /** + * @brief Adds a list of natives to the global native pool, to be + * bound on plugin load. + * + * Unlike AddNatives(), this function implements natives that are + * ALWAYS bound, regardless of whether a previous function is bound. + * That means extensions can override Core natives. + * + * A Core version of each native must exist. If one does not, then + * Core will simply ignore that entry. + * + * Override natives represent a weak coupling. If the extension is + * unloaded, the native will be re-bound to the Core version. + * + * @param myself Identity token of parent object. + * @param natives Array of natives to add. The last entry in + * the array must be filled with NULLs to + * terminate the array. The array must be static + * as Core will cache the pointer for the + * lifetime of the extension. + */ + virtual void OverrideNatives(IExtension *myself, const sp_nativeinfo_t *natives) =0; }; } diff --git a/public/ISourceMod.h b/public/ISourceMod.h index bddc33c4..af39d30b 100644 --- a/public/ISourceMod.h +++ b/public/ISourceMod.h @@ -43,7 +43,7 @@ #include #define SMINTERFACE_SOURCEMOD_NAME "ISourceMod" -#define SMINTERFACE_SOURCEMOD_VERSION 3 +#define SMINTERFACE_SOURCEMOD_VERSION 4 /** * @brief Forward declaration of the KeyValues class. @@ -199,6 +199,23 @@ namespace SourceMod * @return Adjusted server time. */ virtual time_t GetAdjustedTime() =0; + + /** + * @brief Sets the global client SourceMod will use for assisted + * translations (that is, %t). + * + * @param index Client index. + * @return Old global client value. + */ + virtual unsigned int SetGlobalTarget(unsigned int index) =0; + + /** + * @brief Returns the global client SourceMod is currently using + * for assisted translations (that is, %t). + * + * @return Global client value. + */ + virtual unsigned int GetGlobalTarget() const =0; }; } diff --git a/public/ITextParsers.h b/public/ITextParsers.h index 0b0f0c68..85f675e3 100644 --- a/public/ITextParsers.h +++ b/public/ITextParsers.h @@ -280,7 +280,6 @@ namespace SourceMod * @param key Key string. * @param value Value string. If no quotes were specified, this will be NULL, * and key will contain the entire string. - * @param Number of line in file. * @return SMCResult directive. */ virtual SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value) @@ -291,7 +290,7 @@ namespace SourceMod /** * @brief Called when leaving the current section. * - * @param Parsing states. + * @param states Parsing states. * @return SMCResult directive. */ virtual SMCResult ReadSMC_LeavingSection(const SMCStates *states) diff --git a/public/IUserMessages.h b/public/IUserMessages.h index 4603643a..c5524b1d 100644 --- a/public/IUserMessages.h +++ b/public/IUserMessages.h @@ -144,7 +144,7 @@ namespace SourceMod * @param flags Flags to use for sending the message. * @return bf_write structure to write message with, or NULL on failure. */ - virtual bf_write *StartMessage(int msg_id, cell_t players[], unsigned int playersNum, int flags) =0; + virtual bf_write *StartMessage(int msg_id, const cell_t players[], unsigned int playersNum, int flags) =0; /** * @brief Wrapper around UserMessageEnd for use with StartMessage(). diff --git a/public/extensions/ITopMenus.h b/public/extensions/ITopMenus.h index 97cba05f..ef063e2d 100644 --- a/public/extensions/ITopMenus.h +++ b/public/extensions/ITopMenus.h @@ -43,7 +43,7 @@ */ #define SMINTERFACE_TOPMENUS_NAME "ITopMenus" -#define SMINTERFACE_TOPMENUS_VERSION 3 +#define SMINTERFACE_TOPMENUS_VERSION 4 namespace SourceMod { @@ -268,6 +268,14 @@ namespace SourceMod * @return Object's info string, or NULL if none. */ virtual const char *GetObjectInfoString(unsigned int object_id) =0; + + /** + * @brief Returns an object's name string. + * + * @param object_id Object ID. + * @return Object's name string, or NULL if none. + */ + virtual const char *GetObjectName(unsigned int object_id) =0; }; /** diff --git a/public/sample_ext/sdk/smsdk_config.h b/public/sample_ext/sdk/smsdk_config.h index ea076290..59fb0874 100644 --- a/public/sample_ext/sdk/smsdk_config.h +++ b/public/sample_ext/sdk/smsdk_config.h @@ -74,5 +74,6 @@ //#define SMEXT_ENABLE_PLUGINSYS //#define SMEXT_ENABLE_ADMINSYS //#define SMEXT_ENABLE_TEXTPARSERS +#define SMEXT_ENABLE_USERMSGS #endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ diff --git a/public/sample_ext/sdk/smsdk_ext.cpp b/public/sample_ext/sdk/smsdk_ext.cpp index 9934974f..0c728f00 100644 --- a/public/sample_ext/sdk/smsdk_ext.cpp +++ b/public/sample_ext/sdk/smsdk_ext.cpp @@ -91,6 +91,9 @@ IAdminSystem *adminsys = NULL; #if defined SMEXT_ENABLE_TEXTPARSERS ITextParsers *textparsers = NULL; #endif +#if defined SMEXT_ENABLE_USERMSGS +IUserMessages *usermsgs = NULL; +#endif /** Exports the main interface */ PLATFORM_EXTERN_C IExtensionInterface *GetSMExtAPI() @@ -173,6 +176,9 @@ bool SDKExtension::OnExtensionLoad(IExtension *me, IShareSys *sys, char *error, #if defined SMEXT_ENABLE_TEXTPARSERS SM_GET_IFACE(TEXTPARSERS, textparsers); #endif +#if defined SMEXT_ENABLE_USERMSGS + SM_GET_IFACE(USERMSGS, usermsgs); +#endif if (SDK_OnLoad(error, maxlength, late)) { diff --git a/public/sample_ext/sdk/smsdk_ext.h b/public/sample_ext/sdk/smsdk_ext.h index b37ce03f..4702f2a2 100644 --- a/public/sample_ext/sdk/smsdk_ext.h +++ b/public/sample_ext/sdk/smsdk_ext.h @@ -85,6 +85,9 @@ #if defined SMEXT_ENABLE_TEXTPARSERS #include #endif +#if defined SMEXT_ENABLE_USERMSGS +#include +#endif #if defined SMEXT_CONF_METAMOD #include @@ -277,6 +280,9 @@ extern IMenuManager *menus; #if defined SMEXT_ENABLE_ADMINSYS extern IAdminSystem *adminsys; #endif +#if defined SMEXT_ENABLE_USERMSGS +extern IUserMessages *usermsgs; +#endif #if defined SMEXT_CONF_METAMOD PLUGIN_GLOBALVARS();