core/MenuStyle_Radio.cpp
author Nicholas Hastings <skamonkey@gmail.com>
Sat Jan 26 12:14:40 2013 -0500 (2013-01-26)
changeset 3757 aa69f3b742c7
parent 3743 a98686a77974
child 3855 d8ba5eddf543
permissions -rw-r--r--
Replaced remaining calls to old MM:S usermessages functions when using protobuf system (bug 5579, bug 5588).
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include "MenuStyle_Radio.h"
    33 #include "sm_stringutil.h"
    34 #include "UserMessages.h"
    35 #include <IGameConfigs.h>
    36 #include "PlayerManager.h"
    37 #if defined MENU_DEBUG
    38 #include "Logger.h"
    39 #endif
    40 #include "logic_bridge.h"
    41 
    42 #ifdef USE_PROTOBUF_USERMESSAGES
    43 #include <google/protobuf/descriptor.h>
    44 #endif
    45 
    46 #if SOURCE_ENGINE == SE_CSGO
    47 #include <game/shared/csgo/protobuf/cstrike15_usermessages.pb.h>
    48 #endif
    49 
    50 extern const char *g_RadioNumTable[];
    51 CRadioStyle g_RadioMenuStyle;
    52 int g_ShowMenuId = -1;
    53 bool g_bRadioInit = false;
    54 unsigned int g_RadioMenuTimeout = 0;
    55 
    56 // back, next, exit
    57 #define MAX_PAGINATION_OPTIONS 3
    58 
    59 #define MAX_MENUSLOT_KEYS 10
    60 
    61 static unsigned int s_RadioMaxPageItems = MAX_MENUSLOT_KEYS;
    62 
    63 
    64 CRadioStyle::CRadioStyle()
    65 {
    66 	m_players = new CRadioMenuPlayer[256+1];
    67 	for (size_t i = 0; i < 256+1; i++)
    68 	{
    69 		m_players[i].Radio_SetIndex(i);
    70 	}
    71 }
    72 
    73 void CRadioStyle::OnSourceModAllInitialized()
    74 {
    75 	g_Players.AddClientListener(this);
    76 }
    77 
    78 void CRadioStyle::OnSourceModLevelChange(const char *mapName)
    79 {
    80 	if (g_bRadioInit)
    81 	{
    82 		return;
    83 	}
    84 
    85 	g_bRadioInit = true;
    86 	const char *msg = g_pGameConf->GetKeyValue("HudRadioMenuMsg");
    87 	if (!msg || msg[0] == '\0')
    88 	{
    89 		return;
    90 	}
    91 
    92 	g_ShowMenuId = g_UserMsgs.GetMessageIndex(msg);
    93 
    94 	if (!IsSupported())
    95 	{
    96 		return;
    97 	}
    98 
    99 	const char *val = g_pGameConf->GetKeyValue("RadioMenuTimeout");
   100 	if (val != NULL)
   101 	{
   102 		g_RadioMenuTimeout = atoi(val);
   103 	}
   104 	else
   105 	{
   106 		g_RadioMenuTimeout = 0;
   107 	}
   108 
   109 	const char *items = g_pGameConf->GetKeyValue("RadioMenuMaxPageItems");
   110 	if (items != NULL)
   111 	{
   112 		int value = atoi(items);
   113 
   114 		// Only override the mostly-safe default if it's a sane value
   115 		if (value > MAX_PAGINATION_OPTIONS && value <= MAX_MENUSLOT_KEYS)
   116 		{
   117 			s_RadioMaxPageItems = value;
   118 		}
   119 	}
   120 
   121 	g_Menus.AddStyle(this);
   122 	g_Menus.SetDefaultStyle(this);
   123 
   124 	g_UserMsgs.HookUserMessage(g_ShowMenuId, this, false);
   125 }
   126 
   127 void CRadioStyle::OnSourceModShutdown()
   128 {
   129 	g_Players.RemoveClientListener(this);
   130 	g_UserMsgs.UnhookUserMessage(g_ShowMenuId, this, false);
   131 
   132 	while (!m_FreeDisplays.empty())
   133 	{
   134 		delete m_FreeDisplays.front();
   135 		m_FreeDisplays.pop();
   136 	}
   137 }
   138 
   139 bool CRadioStyle::IsSupported()
   140 {
   141 	return (g_ShowMenuId != -1);
   142 }
   143 
   144 bool CRadioStyle::OnClientCommand(int client, const char *cmdname, const CCommand &cmd)
   145 {
   146 	if (strcmp(cmdname, "menuselect") == 0)
   147 	{
   148 		if (!m_players[client].bInMenu)
   149 		{
   150 			m_players[client].bInExternMenu = false;
   151 			return false;
   152 		}
   153 
   154 		int arg = atoi(cmd.Arg(1));
   155 		ClientPressedKey(client, arg);
   156 		return true;
   157 	}
   158 
   159 	return false;
   160 }
   161 
   162 static unsigned int g_last_holdtime = 0;
   163 static unsigned int g_last_client_count = 0;
   164 static int g_last_clients[256];
   165 
   166 #ifdef USE_PROTOBUF_USERMESSAGES
   167 void CRadioStyle::OnUserMessage(int msg_id, protobuf::Message &msg, IRecipientFilter *pFilter)
   168 #else
   169 void CRadioStyle::OnUserMessage(int msg_id, bf_write *bf, IRecipientFilter *pFilter)
   170 #endif
   171 {
   172 	int count = pFilter->GetRecipientCount();
   173 
   174 #ifdef USE_PROTOBUF_USERMESSAGES
   175 	int c = ((CCSUsrMsg_ShowMenu &)msg).display_time();
   176 #else
   177 	bf_read br(bf->GetBasePointer(), 3);
   178 
   179 	br.ReadWord();
   180 	int c = br.ReadChar();
   181 #endif
   182 
   183 	g_last_holdtime = (c == -1) ? 0 : (unsigned)c;
   184 
   185 	for (int i=0; i<count; i++)
   186 	{
   187 		g_last_clients[g_last_client_count++] = pFilter->GetRecipientIndex(i);
   188 	}
   189 }
   190 
   191 void CRadioStyle::OnUserMessageSent(int msg_id)
   192 {
   193 	for (unsigned int i=0; i<g_last_client_count; i++)
   194 	{
   195 		int client = g_last_clients[i];
   196 #if defined MENU_DEBUG
   197 		g_Logger.LogMessage("[SM_MENU] CRadioStyle got ShowMenu (client %d) (bInMenu %d)",
   198 			client,
   199 			m_players[client].bInExternMenu);
   200 #endif
   201 		if (m_players[client].bInMenu)
   202 		{
   203 			_CancelClientMenu(client, MenuCancel_Interrupted, true);
   204 		}
   205 		m_players[client].bInExternMenu = true;
   206 		m_players[client].menuHoldTime = g_last_holdtime;
   207 	}
   208 	g_last_client_count = 0;
   209 }
   210 
   211 void CRadioStyle::SendDisplay(int client, IMenuPanel *display)
   212 {
   213 	CRadioDisplay *rDisplay = (CRadioDisplay *)display;
   214 	rDisplay->SendRawDisplay(client, m_players[client].menuHoldTime);
   215 }
   216 
   217 IMenuPanel *CRadioStyle::CreatePanel()
   218 {
   219 	return g_RadioMenuStyle.MakeRadioDisplay();
   220 }
   221 
   222 IBaseMenu *CRadioStyle::CreateMenu(IMenuHandler *pHandler, IdentityToken_t *pOwner)
   223 {
   224 	return new CRadioMenu(pHandler, pOwner);
   225 }
   226 
   227 unsigned int CRadioStyle::GetMaxPageItems()
   228 {
   229 	return s_RadioMaxPageItems;
   230 }
   231 
   232 const char *CRadioStyle::GetStyleName()
   233 {
   234 	return "radio";
   235 }
   236 
   237 CBaseMenuPlayer *CRadioStyle::GetMenuPlayer(int client)
   238 {
   239 	return &m_players[client];
   240 }
   241 
   242 CRadioDisplay *CRadioStyle::MakeRadioDisplay(CRadioMenu *menu)
   243 {
   244 	CRadioDisplay *display;
   245 	if (m_FreeDisplays.empty())
   246 	{
   247 		display = new CRadioDisplay();
   248 	}
   249 	else
   250 	{
   251 		display = m_FreeDisplays.front();
   252 		m_FreeDisplays.pop();
   253 		display->Reset();
   254 	}
   255 	return display;
   256 }
   257 
   258 IMenuPanel *CRadioStyle::MakeRadioDisplay(const char *str, int keys)
   259 {
   260 	CRadioDisplay *pPanel = MakeRadioDisplay(NULL);
   261 
   262 	pPanel->DirectSet(str, keys);
   263 
   264 	return pPanel;
   265 }
   266 
   267 void CRadioStyle::FreeRadioDisplay(CRadioDisplay *display)
   268 {
   269 	m_FreeDisplays.push(display);
   270 }
   271 
   272 CRadioMenuPlayer *CRadioStyle::GetRadioMenuPlayer(int client)
   273 {
   274 	return &m_players[client];
   275 }
   276 
   277 void CRadioStyle::ProcessWatchList()
   278 {
   279 	if (!g_RadioMenuTimeout)
   280 	{
   281 		BaseMenuStyle::ProcessWatchList();
   282 		return;
   283 	}
   284 
   285 	BaseMenuStyle::ProcessWatchList();
   286 
   287 	CRadioMenuPlayer *pPlayer;
   288 	unsigned int max_clients = g_Players.GetMaxClients();
   289 	for (unsigned int i = 1; i <= max_clients; i++)
   290 	{
   291 		pPlayer = GetRadioMenuPlayer(i);
   292 		if (!pPlayer->bInMenu || pPlayer->bInExternMenu)
   293 		{
   294 			continue;
   295 		}
   296 		if (pPlayer->Radio_NeedsRefresh())
   297 		{
   298 			pPlayer->Radio_Refresh();
   299 		}
   300 	}
   301 }
   302 
   303 unsigned int CRadioStyle::GetApproxMemUsage()
   304 {
   305 	return sizeof(CRadioStyle) + (sizeof(CRadioMenuPlayer) * 257);
   306 }
   307 
   308 CRadioDisplay::CRadioDisplay()
   309 {
   310 	Reset();
   311 }
   312 
   313 CRadioDisplay::CRadioDisplay(CRadioMenu *menu)
   314 {
   315 	Reset();
   316 }
   317 
   318 bool CRadioDisplay::DrawRawLine(const char *rawline)
   319 {
   320 	m_BufferText.append(rawline);
   321 	m_BufferText.append("\n");
   322 	return true;
   323 }
   324 
   325 void CRadioDisplay::Reset()
   326 {
   327 	m_BufferText.assign("");
   328 	m_Title.assign("");
   329 	m_NextPos = 1;
   330 	keys = 0;
   331 }
   332 
   333 void CRadioDisplay::DirectSet(const char *str, int keymap)
   334 {
   335 	m_Title.clear();
   336 	m_BufferText.assign(str);
   337 	keys = keymap;
   338 }
   339 
   340 unsigned int CRadioDisplay::GetCurrentKey()
   341 {
   342 	return m_NextPos;
   343 }
   344 
   345 bool CRadioDisplay::SetCurrentKey(unsigned int key)
   346 {
   347 	if (key < m_NextPos || m_NextPos > s_RadioMaxPageItems)
   348 	{
   349 		return false;
   350 	}
   351 
   352 	m_NextPos = key;
   353 
   354 	return true;
   355 }
   356 
   357 bool CRadioDisplay::SendDisplay(int client, IMenuHandler *handler, unsigned int time)
   358 {
   359 	return g_RadioMenuStyle.DoClientMenu(client, this, handler, time);
   360 }
   361 
   362 bool CRadioDisplay::SetExtOption(MenuOption option, const void *valuePtr)
   363 {
   364 	return false;
   365 }
   366 
   367 IMenuStyle *CRadioDisplay::GetParentStyle()
   368 {
   369 	return &g_RadioMenuStyle;
   370 }
   371 
   372 void CRadioDisplay::DrawTitle(const char *text, bool onlyIfEmpty/* =false */)
   373 {
   374 	if (m_Title.size() != 0 && onlyIfEmpty)
   375 	{
   376 		return;
   377 	}
   378 	m_Title.assign(text);
   379 }
   380 
   381 unsigned int CRadioDisplay::DrawItem(const ItemDrawInfo &item)
   382 {
   383 	if (m_NextPos > s_RadioMaxPageItems || !CanDrawItem(item.style))
   384 	{
   385 		return 0;
   386 	}
   387 
   388 	if (item.style & ITEMDRAW_RAWLINE)
   389 	{
   390 		if (item.style & ITEMDRAW_SPACER)
   391 		{
   392 			m_BufferText.append(" \n");
   393 		} else {
   394 			m_BufferText.append(item.display);
   395 			m_BufferText.append("\n");
   396 		}
   397 		return 0;
   398 	} else if (item.style & ITEMDRAW_SPACER) {
   399 		m_BufferText.append(" \n");
   400 		return m_NextPos++;
   401 	} else if (item.style & ITEMDRAW_NOTEXT) {
   402 		return m_NextPos++;
   403 	}
   404 
   405 	if (item.style & ITEMDRAW_DISABLED)
   406 	{
   407 		m_BufferText.append(g_RadioNumTable[m_NextPos]);
   408 		m_BufferText.append(item.display);
   409 		m_BufferText.append("\n");
   410 	} else {
   411 		m_BufferText.append("->");
   412 		m_BufferText.append(g_RadioNumTable[m_NextPos]);
   413 		m_BufferText.append(item.display);
   414 		m_BufferText.append("\n");
   415 		keys |= (1<<(m_NextPos-1));
   416 	}
   417 
   418 	return m_NextPos++;
   419 }
   420 
   421 bool CRadioDisplay::CanDrawItem(unsigned int drawFlags)
   422 {
   423 	if ((drawFlags & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
   424 	{
   425 		return false;
   426 	}
   427 
   428 	if ((drawFlags & ITEMDRAW_DISABLED) && (drawFlags & ITEMDRAW_CONTROL))
   429 	{
   430 		return false;
   431 	}
   432 
   433 	return true;
   434 }
   435 
   436 void CRadioDisplay::SendRawDisplay(int client, unsigned int time)
   437 {
   438 	int _sel_keys = (keys == 0) ? (1<<9) : keys;
   439 	CRadioMenuPlayer *pPlayer = g_RadioMenuStyle.GetRadioMenuPlayer(client);
   440 	pPlayer->Radio_Init(_sel_keys, m_Title.c_str(), m_BufferText.c_str());
   441 	pPlayer->Radio_Refresh();
   442 }
   443 
   444 void CRadioMenuPlayer::Radio_SetIndex(unsigned int index)
   445 {
   446 	m_index = index;
   447 }
   448 
   449 bool CRadioMenuPlayer::Radio_NeedsRefresh()
   450 {
   451 	return (gpGlobals->curtime - display_last_refresh >= g_RadioMenuTimeout);
   452 }
   453 
   454 void CRadioMenuPlayer::Radio_Init(int keys, const char *title, const char *text)
   455 {
   456 	if (title[0] != '\0')
   457 	{
   458 		display_len = UTIL_Format(display_pkt, 
   459 			sizeof(display_pkt), 
   460 			"%s\n%s", 
   461 			title,
   462 			text);
   463 	}
   464 	else
   465 	{
   466 		display_len = UTIL_Format(display_pkt, 
   467 			sizeof(display_pkt), 
   468 			"%s", 
   469 			text);
   470 	}
   471 	display_keys = keys;
   472 }
   473 
   474 void CRadioMenuPlayer::Radio_Refresh()
   475 {
   476 	cell_t players[1] = {m_index};
   477 	char *ptr = display_pkt;
   478 	char save = 0;
   479 	size_t len = display_len;
   480 	unsigned int time;
   481 
   482 	/* Compute the new time */
   483 	if (menuHoldTime == 0)
   484 	{
   485 		time = 0;
   486 	}
   487 	else
   488 	{
   489 		time = menuHoldTime - (unsigned int)(gpGlobals->curtime - menuStartTime);
   490 	}
   491 
   492 #ifdef USE_PROTOBUF_USERMESSAGES
   493 	// If or when we need to support multiple games per engine with this, we can switch to reflection
   494 	// TODO: find what happens past 240 on CS:GO
   495 	CCSUsrMsg_ShowMenu *msg = (CCSUsrMsg_ShowMenu *)g_UserMsgs.StartProtobufMessage(g_ShowMenuId, players, 1, USERMSG_BLOCKHOOKS);
   496 	msg->set_bits_valid_slots(display_keys);
   497 	msg->set_display_time(time);
   498 	msg->set_menu_string(ptr);
   499 	g_UserMsgs.EndMessage();
   500 #else
   501 	while (true)
   502 	{
   503 		if (len > 240)
   504 		{
   505 			save = ptr[240];
   506 			ptr[240] = '\0';
   507 		}
   508 
   509 		bf_write *buffer = g_UserMsgs.StartBitBufMessage(g_ShowMenuId, players, 1, USERMSG_BLOCKHOOKS);
   510 		buffer->WriteWord(display_keys);
   511 		buffer->WriteChar(time ? time : -1);
   512 		buffer->WriteByte( (len > 240) ? 1 : 0 );
   513 		buffer->WriteString(ptr);
   514 		g_UserMsgs.EndMessage();
   515 		if (len > 240)
   516 		{
   517 			ptr[240] = save;
   518 			ptr = &ptr[240];
   519 			len -= 240;
   520 		}
   521 		else
   522 		{
   523 			break;
   524 		}
   525 	}
   526 #endif
   527 
   528 	display_last_refresh = gpGlobals->curtime;
   529 }
   530 
   531 int CRadioDisplay::GetAmountRemaining()
   532 {
   533 	size_t amt = m_Title.size() + 1 + m_BufferText.size();
   534 	if (amt >= 511)
   535 	{
   536 		return 0;
   537 	}
   538 	return (int)(511 - amt);
   539 }
   540 
   541 void CRadioDisplay::DeleteThis()
   542 {
   543 	delete this;
   544 }
   545 
   546 bool CRadioDisplay::SetSelectableKeys(unsigned int keymap)
   547 {
   548 	keys = (signed)keymap;
   549 	return true;
   550 }
   551 
   552 unsigned int CRadioDisplay::GetApproxMemUsage()
   553 {
   554 	return sizeof(CRadioDisplay)
   555 		+ m_BufferText.size()
   556 		+ m_Title.size();
   557 }
   558 
   559 CRadioMenu::CRadioMenu(IMenuHandler *pHandler, IdentityToken_t *pOwner) : 
   560 CBaseMenu(pHandler, &g_RadioMenuStyle, pOwner)
   561 {
   562 	m_Pagination = s_RadioMaxPageItems - MAX_PAGINATION_OPTIONS;
   563 }
   564 
   565 bool CRadioMenu::SetExtOption(MenuOption option, const void *valuePtr)
   566 {
   567 	return false;
   568 }
   569 
   570 IMenuPanel *CRadioMenu::CreatePanel()
   571 {
   572 	return g_RadioMenuStyle.MakeRadioDisplay(this);
   573 }
   574 
   575 bool CRadioMenu::Display(int client, unsigned int time, IMenuHandler *alt_handler)
   576 {
   577 	return DisplayAtItem(client, time, 0, alt_handler);
   578 }
   579 
   580 bool CRadioMenu::DisplayAtItem(int client,
   581 							   unsigned int time,
   582 							   unsigned int start_item,
   583 							   IMenuHandler *alt_handler)
   584 {
   585 #if defined MENU_DEBUG
   586 	g_Logger.LogMessage("[SM_MENU] CRadioMenu::Display(%p) (client %d) (time %d)",
   587 		this,
   588 		client,
   589 		time);
   590 #endif
   591 	if (m_bCancelling)
   592 	{
   593 		return false;
   594 	}
   595 
   596 	return g_RadioMenuStyle.DoClientMenu(client,
   597 		this,
   598 		start_item,
   599 		alt_handler ? alt_handler : m_pHandler, 
   600 		time);
   601 }
   602 
   603 bool CRadioMenu::SetPagination(unsigned int itemsPerPage)
   604 {
   605 	const unsigned int maxPerPage = s_RadioMaxPageItems - MAX_PAGINATION_OPTIONS;
   606 	if (itemsPerPage > maxPerPage)
   607 	{
   608 		return false;
   609 	}
   610 
   611 	return CBaseMenu::SetPagination(itemsPerPage);
   612 }
   613 
   614 void CRadioMenu::Cancel_Finally()
   615 {
   616 	g_RadioMenuStyle.CancelMenu(this);
   617 }
   618 
   619 unsigned int CRadioMenu::GetApproxMemUsage()
   620 {
   621 	return sizeof(CRadioMenu) + GetBaseMemUsage();
   622 }
   623 
   624 const char *g_RadioNumTable[11] = 
   625 {
   626 	"0. ", "1. ", "2. ", "3. ", "4. ", "5. ", "6. ", "7. ", "8. ", "9. ", "0. "
   627 };