3 * ===============================================================
4 * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
5 * ===============================================================
7 * This file is not open source and may not be copied without explicit
8 * written permission of AlliedModders LLC. This file may not be redistributed
9 * in whole or significant part.
10 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
15 #include "sm_stringutil.h"
16 #include "PlayerManager.h"
17 #include "MenuStyle_Valve.h"
18 #include "Translator.h"
19 #include "PlayerManager.h"
20 #include "ConCmdManager.h"
22 SH_DECL_HOOK4_void(IServerPluginHelpers, CreateMessage, SH_NOATTRIB, false, edict_t *, DIALOG_TYPE, KeyValues *, IServerPluginCallbacks *);
24 ValveMenuStyle g_ValveMenuStyle;
25 const char *g_OptionNumTable[];
26 const char *g_OptionCmdTable[];
27 IServerPluginCallbacks *g_pVSPHandle = NULL;
28 CallClass<IServerPluginHelpers> *g_pSPHCC = NULL;
30 class TestHandler : public IMenuHandler
33 virtual void OnMenuEnd(IBaseMenu *menu)
39 ValveMenuStyle::ValveMenuStyle() : m_players(new CValveMenuPlayer[256+1]), m_WatchList(256)
43 bool ValveMenuStyle::OnClientCommand(int client)
45 const char *cmd = engine->Cmd_Argv(0);
47 if (strcmp(cmd, "sm_vmenuselect") == 0)
49 int key_press = atoi(engine->Cmd_Argv(1));
50 g_ValveMenuStyle.ClientPressedKey(client, key_press);
57 void ValveMenuStyle::OnSourceModAllInitialized()
59 g_Players.AddClientListener(this);
60 SH_ADD_HOOK_MEMFUNC(IServerPluginHelpers, CreateMessage, serverpluginhelpers, this, &ValveMenuStyle::HookCreateMessage, false);
61 g_pSPHCC = SH_GET_CALLCLASS(serverpluginhelpers);
64 void ValveMenuStyle::OnSourceModShutdown()
66 SH_RELEASE_CALLCLASS(g_pSPHCC);
67 SH_REMOVE_HOOK_MEMFUNC(IServerPluginHelpers, CreateMessage, serverpluginhelpers, this, &ValveMenuStyle::HookCreateMessage, false);
68 g_Players.RemoveClientListener(this);
71 void ValveMenuStyle::OnClientDisconnected(int client)
73 CValveMenuPlayer *player = &m_players[client];
79 _CancelMenu(client, true, MenuCancel_Disconnect);
81 player->bInMenu = false;
84 void ValveMenuStyle::HookCreateMessage(edict_t *pEdict,
87 IServerPluginCallbacks *plugin)
89 if (type != DIALOG_MENU)
94 int client = engine->IndexOfEdict(pEdict);
95 if (client < 1 || client > 256)
100 CValveMenuPlayer *player = &m_players[client];
102 /* We don't care if the player is in a menu because, for all intents and purposes,
103 * the menu is completely private. Instead, we just figure out the level we'll need
104 * in order to override it.
106 player->curPrioLevel = kv->GetInt("level", player->curPrioLevel);
108 /* Oh no! What happens if we got a menu that overwrites ours?! */
111 /* Okay, let the external menu survive for now. It may live another
112 * day to avenge its grandfather, killed in the great Menu Interruption
115 _CancelMenu(client, true);
119 void ValveMenuStyle::OnSourceModVSPReceived(IServerPluginCallbacks *iface)
121 g_pVSPHandle = iface;
124 IMenuDisplay *ValveMenuStyle::CreateDisplay()
126 return new CValveMenuDisplay();
129 IBaseMenu *ValveMenuStyle::CreateMenu()
131 return new CValveMenu();
134 const char *ValveMenuStyle::GetStyleName()
139 unsigned int ValveMenuStyle::GetMaxPageItems()
144 static int do_lookup[256];
145 void ValveMenuStyle::ProcessWatchList()
147 if (!m_WatchList.size())
152 unsigned int total = 0;
153 for (FastLink<int>::iterator iter=m_WatchList.begin(); iter!=m_WatchList.end(); ++iter)
155 do_lookup[total++] = (*iter);
159 CValveMenuPlayer *player;
160 float curTime = gpGlobals->curtime;
161 for (unsigned int i=0; i<total; i++)
163 client = do_lookup[i];
164 player = &m_players[client];
165 if (!player->bInMenu || !player->menuHoldTime)
167 m_WatchList.remove(i);
170 if (curTime > player->menuStartTime + player->menuHoldTime)
172 _CancelMenu(client, false);
177 void ValveMenuStyle::_CancelMenu(int client, bool bAutoIgnore, MenuCancelReason reason)
179 CValveMenuPlayer *player = &m_players[client];
180 menu_states_t &states = player->states;
182 bool bOldIgnore = player->bAutoIgnore;
185 player->bAutoIgnore = true;
189 IMenuHandler *mh = states.mh;
190 IBaseMenu *menu = states.menu;
193 player->bInMenu = false;
194 if (player->menuHoldTime)
196 m_WatchList.remove(client);
200 mh->OnMenuCancel(menu, client, reason);
205 player->bAutoIgnore = bOldIgnore;
209 void ValveMenuStyle::CancelMenu(CValveMenu *menu)
211 int maxClients = g_Players.GetMaxClients();
212 for (int i=1; i<=maxClients; i++)
214 if (m_players[i].bInMenu)
216 menu_states_t &states = m_players[i].states;
217 if (states.menu == menu)
225 bool ValveMenuStyle::CancelClientMenu(int client, bool autoIgnore)
227 if (client < 1 || client > 256 || !m_players[client].bInMenu)
232 _CancelMenu(client, autoIgnore);
238 void ValveMenuStyle::ClientPressedKey(int client, unsigned int key_press)
240 CValveMenuPlayer *player = &m_players[client];
242 /* First question: Are we in a menu? */
243 if (!player->bInMenu)
249 unsigned int item = 0;
250 MenuCancelReason reason = MenuCancel_Exit;
251 menu_states_t &states = player->states;
253 assert(states.mh != NULL);
255 if (states.menu == NULL)
258 } else if (key_press < 1 || key_press > 8) {
261 ItemSelection type = states.slots[key_press].type;
263 /* For navigational items, we're going to redisplay */
264 if (type == ItemSel_Back)
266 if (!RedoClientMenu(client, ItemOrder_Descending))
269 reason = MenuCancel_NoDisplay;
273 } else if (type == ItemSel_Next) {
274 if (!RedoClientMenu(client, ItemOrder_Ascending))
276 cancel = true; /* I like Saltines. */
277 reason = MenuCancel_NoDisplay;
281 } else if (type == ItemSel_Exit || type == ItemSel_None) {
284 item = states.slots[key_press].item;
289 IMenuHandler *mh = states.mh;
290 IBaseMenu *menu = states.menu;
293 player->bInMenu = false;
294 if (player->menuHoldTime)
296 m_WatchList.remove(client);
301 mh->OnMenuCancel(menu, client, reason);
303 mh->OnMenuSelect(menu, client, item);
309 bool ValveMenuStyle::DoClientMenu(int client, CValveMenuDisplay *menu, IMenuHandler *mh, unsigned int time)
311 if (!g_pVSPHandle || !mh)
316 CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
317 if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame())
322 CValveMenuPlayer *player = &m_players[client];
323 if (player->bAutoIgnore)
328 /* For the duration of this, we are going to totally ignore whether
329 * the player is already in a menu or not (except to cancel the old one).
330 * Instead, we are simply going to ignore any further menu displays, so
331 * this display can't be interrupted.
333 player->bAutoIgnore = true;
335 /* Cancel any old menus */
336 menu_states_t &states = player->states;
339 /* We need to cancel the old menu */
340 if (player->menuHoldTime)
342 m_WatchList.remove(client);
344 states.mh->OnMenuCancel(states.menu, client, MenuCancel_Interrupt);
345 states.mh->OnMenuEnd(states.menu);
348 states.firstItem = 0;
352 states.apiVers = SMINTERFACE_MENUMANAGER_VERSION;
353 player->curPrioLevel--;
354 player->bInMenu = true;
355 player->menuStartTime = gpGlobals->curtime;
356 player->menuHoldTime = time;
360 m_WatchList.push_back(client);
363 /* Draw the display */
364 menu->SendRawDisplay(client, player->curPrioLevel, time);
366 /* We can be interrupted again! */
367 player->bAutoIgnore = false;
372 bool ValveMenuStyle::DoClientMenu(int client, CValveMenu *menu, IMenuHandler *mh, unsigned int time)
374 mh->OnMenuStart(menu);
376 if (!g_pVSPHandle || !mh)
378 mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
383 CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
384 if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame())
386 mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
391 CValveMenuPlayer *player = &m_players[client];
392 if (player->bAutoIgnore)
394 mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
399 /* For the duration of this, we are going to totally ignore whether
400 * the player is already in a menu or not (except to cancel the old one).
401 * Instead, we are simply going to ignore any further menu displays, so
402 * this display can't be interrupted.
404 player->bAutoIgnore = true;
406 /* Cancel any old menus */
407 menu_states_t &states = player->states;
410 _CancelMenu(client, true);
413 states.firstItem = 0;
417 states.apiVers = SMINTERFACE_MENUMANAGER_VERSION;
419 IMenuDisplay *display = g_Menus.RenderMenu(client, states, ItemOrder_Ascending);
422 player->bAutoIgnore = false;
423 player->bInMenu = false;
424 mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
429 /* Finally, set our states */
430 player->curPrioLevel--;
431 player->bInMenu = true;
432 player->menuStartTime = gpGlobals->curtime;
433 player->menuHoldTime = time;
437 m_WatchList.push_back(client);
440 /* Draw the display */
441 CValveMenuDisplay *vDisplay = (CValveMenuDisplay *)display;
442 vDisplay->SendRawDisplay(client, player->curPrioLevel, time);
444 /* Free the display pointer */
447 /* We can be interrupted again! */
448 player->bAutoIgnore = false;
453 bool ValveMenuStyle::RedoClientMenu(int client, ItemOrder order)
455 CValveMenuPlayer *player = &m_players[client];
456 menu_states_t &states = player->states;
458 player->bAutoIgnore = true;
459 IMenuDisplay *display = g_Menus.RenderMenu(client, states, order);
462 if (player->menuHoldTime)
464 m_WatchList.remove(client);
466 player->bAutoIgnore = false;
470 CValveMenuDisplay *vDisplay = (CValveMenuDisplay *)display;
471 vDisplay->SendRawDisplay(client, --player->curPrioLevel, player->menuHoldTime);
475 player->bAutoIgnore = false;
480 MenuSource ValveMenuStyle::GetClientMenu(int client, void **object)
482 if (client < 1 || client > 256 || !m_players[client].bInMenu)
484 return MenuSource_None;
488 if ((menu=m_players[client].states.menu) != NULL)
494 return MenuSource_BaseMenu;
497 return MenuSource_Display;
500 CValveMenuDisplay::CValveMenuDisplay()
506 CValveMenuDisplay::CValveMenuDisplay(CValveMenu *pMenu)
510 m_pKv->SetColor("color", pMenu->m_IntroColor);
511 m_pKv->SetString("title", pMenu->m_IntroMsg);
514 CValveMenuDisplay::~CValveMenuDisplay()
519 IMenuStyle *CValveMenuDisplay::GetParentStyle()
521 return &g_ValveMenuStyle;
524 void CValveMenuDisplay::Reset()
530 m_pKv = new KeyValues("menu");
532 m_TitleDrawn = false;
535 bool CValveMenuDisplay::SetExtOption(MenuOption option, const void *valuePtr)
537 if (option == MenuOption_IntroMessage)
539 m_pKv->SetString("title", (const char *)valuePtr);
541 } else if (option == MenuOption_IntroColor) {
542 int *array = (int *)valuePtr;
543 m_pKv->SetColor("color", Color(array[0], array[1], array[2], array[3]));
550 bool CValveMenuDisplay::CanDrawItem(unsigned int drawFlags)
553 * ITEMDRAW_RAWLINE - We can't draw random text, and this doesn't add a slot,
554 * so it's completely safe to ignore it.
555 * -----------------------------------------
557 if (drawFlags & ITEMDRAW_RAWLINE)
563 * Special cases, explained in DrawItem()
565 if ((drawFlags & ITEMDRAW_NOTEXT)
566 || (drawFlags & ITEMDRAW_SPACER))
572 * We can't draw disabled text. We could bump the position, but we
573 * want DirectDraw() to find some actual items to display.
575 if (drawFlags & ITEMDRAW_DISABLED)
583 unsigned int CValveMenuDisplay::DrawItem(const ItemDrawInfo &item)
585 if (m_NextPos > 9 || !CanDrawItem(item.style))
591 * For these cases we can't draw anything at all, but
592 * we can at least bump the position since we were explicitly asked to.
594 if ((item.style & ITEMDRAW_NOTEXT)
595 || (item.style & ITEMDRAW_SPACER))
601 UTIL_Format(buffer, sizeof(buffer), "%d. %s", m_NextPos, item.display);
603 KeyValues *ki = m_pKv->FindKey(g_OptionNumTable[m_NextPos], true);
604 ki->SetString("command", g_OptionCmdTable[m_NextPos]);
605 ki->SetString("msg", buffer);
610 void CValveMenuDisplay::DrawTitle(const char *text, bool onlyIfEmpty/* =false */)
612 if (onlyIfEmpty && m_TitleDrawn)
617 m_pKv->SetString("msg", text);
621 bool CValveMenuDisplay::DrawRawLine(const char *rawline)
626 void CValveMenuDisplay::SendRawDisplay(int client, int priority, unsigned int time)
628 m_pKv->SetInt("level", priority);
629 m_pKv->SetInt("time", time);
631 SH_CALL(g_pSPHCC, &IServerPluginHelpers::CreateMessage)(
632 engine->PEntityOfEntIndex(client),
638 bool CValveMenuDisplay::SendDisplay(int client, IMenuHandler *handler, unsigned int time)
640 return g_ValveMenuStyle.DoClientMenu(client, this, handler, time);
643 CValveMenu::CValveMenu() : CBaseMenu(&g_ValveMenuStyle),
644 m_IntroColor(255, 0, 0, 255), m_bShouldDelete(false), m_bCancelling(false)
646 strcpy(m_IntroMsg, "You have a menu, press ESC");
650 void CValveMenu::Cancel()
657 m_bCancelling = true;
658 g_ValveMenuStyle.CancelMenu(this);
659 m_bCancelling = false;
667 void CValveMenu::Destroy()
669 if (!m_bCancelling || m_bShouldDelete)
674 m_bShouldDelete = true;
678 bool CValveMenu::SetPagination(unsigned int itemsPerPage)
680 if (itemsPerPage < 1 || itemsPerPage > 5)
685 CBaseMenu::SetPagination(itemsPerPage);
690 bool CValveMenu::SetExtOption(MenuOption option, const void *valuePtr)
692 if (option == MenuOption_IntroMessage)
694 strncopy(m_IntroMsg, (const char *)valuePtr, sizeof(m_IntroMsg));
696 } else if (option == MenuOption_IntroColor) {
697 unsigned int *array = (unsigned int *)valuePtr;
698 m_IntroColor = Color(array[0], array[1], array[2], array[3]);
705 bool CValveMenu::Display(int client, IMenuHandler *handler, unsigned int time)
712 return g_ValveMenuStyle.DoClientMenu(client, this, handler, time);
715 IMenuDisplay *CValveMenu::CreateDisplay()
717 return new CValveMenuDisplay(this);
720 bool CValveMenu::GetExitButton()
725 bool CValveMenu::SetExitButton(bool set)
730 static const char *g_OptionNumTable[11] =
732 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"
735 static const char *g_OptionCmdTable[11] =
737 "sm_vmenuselect 0", /* INVALID! */