core/MenuStyle_Radio.cpp
author Nicholas Hastings <skamonkey@gmail.com>
Mon Jul 29 10:03:18 2013 -0400 (2013-07-29)
changeset 3960 d50b36dc6821
parent 3855 d8ba5eddf543
child 4025 542dde661d5c
permissions -rw-r--r--
Updated for latest hl2sdk-dota changes.
     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 #if SOURCE_ENGINE == SE_DOTA
   142 	return false;
   143 #else
   144 	return (g_ShowMenuId != -1);
   145 #endif
   146 }
   147 
   148 bool CRadioStyle::OnClientCommand(int client, const char *cmdname, const CCommand &cmd)
   149 {
   150 	if (strcmp(cmdname, "menuselect") == 0)
   151 	{
   152 		if (!m_players[client].bInMenu)
   153 		{
   154 			m_players[client].bInExternMenu = false;
   155 			return false;
   156 		}
   157 
   158 		int arg = atoi(cmd.Arg(1));
   159 		ClientPressedKey(client, arg);
   160 		return true;
   161 	}
   162 
   163 	return false;
   164 }
   165 
   166 static unsigned int g_last_holdtime = 0;
   167 static unsigned int g_last_client_count = 0;
   168 static int g_last_clients[256];
   169 
   170 #ifdef USE_PROTOBUF_USERMESSAGES
   171 void CRadioStyle::OnUserMessage(int msg_id, protobuf::Message &msg, IRecipientFilter *pFilter)
   172 #else
   173 void CRadioStyle::OnUserMessage(int msg_id, bf_write *bf, IRecipientFilter *pFilter)
   174 #endif
   175 {
   176 #if SOURCE_ENGINE != SE_DOTA
   177 	int count = pFilter->GetRecipientCount();
   178 
   179 #ifdef USE_PROTOBUF_USERMESSAGES
   180 	int c = ((CCSUsrMsg_ShowMenu &)msg).display_time();
   181 #else
   182 	bf_read br(bf->GetBasePointer(), 3);
   183 
   184 	br.ReadWord();
   185 	int c = br.ReadChar();
   186 #endif
   187 
   188 	g_last_holdtime = (c == -1) ? 0 : (unsigned)c;
   189 
   190 	for (int i=0; i<count; i++)
   191 	{
   192 		g_last_clients[g_last_client_count++] = pFilter->GetRecipientIndex(i);
   193 	}
   194 #endif
   195 }
   196 
   197 void CRadioStyle::OnUserMessageSent(int msg_id)
   198 {
   199 	for (unsigned int i=0; i<g_last_client_count; i++)
   200 	{
   201 		int client = g_last_clients[i];
   202 #if defined MENU_DEBUG
   203 		g_Logger.LogMessage("[SM_MENU] CRadioStyle got ShowMenu (client %d) (bInMenu %d)",
   204 			client,
   205 			m_players[client].bInExternMenu);
   206 #endif
   207 		if (m_players[client].bInMenu)
   208 		{
   209 			_CancelClientMenu(client, MenuCancel_Interrupted, true);
   210 		}
   211 		m_players[client].bInExternMenu = true;
   212 		m_players[client].menuHoldTime = g_last_holdtime;
   213 	}
   214 	g_last_client_count = 0;
   215 }
   216 
   217 void CRadioStyle::SendDisplay(int client, IMenuPanel *display)
   218 {
   219 	CRadioDisplay *rDisplay = (CRadioDisplay *)display;
   220 	rDisplay->SendRawDisplay(client, m_players[client].menuHoldTime);
   221 }
   222 
   223 IMenuPanel *CRadioStyle::CreatePanel()
   224 {
   225 	return g_RadioMenuStyle.MakeRadioDisplay();
   226 }
   227 
   228 IBaseMenu *CRadioStyle::CreateMenu(IMenuHandler *pHandler, IdentityToken_t *pOwner)
   229 {
   230 	return new CRadioMenu(pHandler, pOwner);
   231 }
   232 
   233 unsigned int CRadioStyle::GetMaxPageItems()
   234 {
   235 	return s_RadioMaxPageItems;
   236 }
   237 
   238 const char *CRadioStyle::GetStyleName()
   239 {
   240 	return "radio";
   241 }
   242 
   243 CBaseMenuPlayer *CRadioStyle::GetMenuPlayer(int client)
   244 {
   245 	return &m_players[client];
   246 }
   247 
   248 CRadioDisplay *CRadioStyle::MakeRadioDisplay(CRadioMenu *menu)
   249 {
   250 	CRadioDisplay *display;
   251 	if (m_FreeDisplays.empty())
   252 	{
   253 		display = new CRadioDisplay();
   254 	}
   255 	else
   256 	{
   257 		display = m_FreeDisplays.front();
   258 		m_FreeDisplays.pop();
   259 		display->Reset();
   260 	}
   261 	return display;
   262 }
   263 
   264 IMenuPanel *CRadioStyle::MakeRadioDisplay(const char *str, int keys)
   265 {
   266 	CRadioDisplay *pPanel = MakeRadioDisplay(NULL);
   267 
   268 	pPanel->DirectSet(str, keys);
   269 
   270 	return pPanel;
   271 }
   272 
   273 void CRadioStyle::FreeRadioDisplay(CRadioDisplay *display)
   274 {
   275 	m_FreeDisplays.push(display);
   276 }
   277 
   278 CRadioMenuPlayer *CRadioStyle::GetRadioMenuPlayer(int client)
   279 {
   280 	return &m_players[client];
   281 }
   282 
   283 void CRadioStyle::ProcessWatchList()
   284 {
   285 	if (!g_RadioMenuTimeout)
   286 	{
   287 		BaseMenuStyle::ProcessWatchList();
   288 		return;
   289 	}
   290 
   291 	BaseMenuStyle::ProcessWatchList();
   292 
   293 	CRadioMenuPlayer *pPlayer;
   294 	unsigned int max_clients = g_Players.GetMaxClients();
   295 	for (unsigned int i = 1; i <= max_clients; i++)
   296 	{
   297 		pPlayer = GetRadioMenuPlayer(i);
   298 		if (!pPlayer->bInMenu || pPlayer->bInExternMenu)
   299 		{
   300 			continue;
   301 		}
   302 		if (pPlayer->Radio_NeedsRefresh())
   303 		{
   304 			pPlayer->Radio_Refresh();
   305 		}
   306 	}
   307 }
   308 
   309 unsigned int CRadioStyle::GetApproxMemUsage()
   310 {
   311 	return sizeof(CRadioStyle) + (sizeof(CRadioMenuPlayer) * 257);
   312 }
   313 
   314 CRadioDisplay::CRadioDisplay()
   315 {
   316 	Reset();
   317 }
   318 
   319 CRadioDisplay::CRadioDisplay(CRadioMenu *menu)
   320 {
   321 	Reset();
   322 }
   323 
   324 bool CRadioDisplay::DrawRawLine(const char *rawline)
   325 {
   326 	m_BufferText.append(rawline);
   327 	m_BufferText.append("\n");
   328 	return true;
   329 }
   330 
   331 void CRadioDisplay::Reset()
   332 {
   333 	m_BufferText.assign("");
   334 	m_Title.assign("");
   335 	m_NextPos = 1;
   336 	keys = 0;
   337 }
   338 
   339 void CRadioDisplay::DirectSet(const char *str, int keymap)
   340 {
   341 	m_Title.clear();
   342 	m_BufferText.assign(str);
   343 	keys = keymap;
   344 }
   345 
   346 unsigned int CRadioDisplay::GetCurrentKey()
   347 {
   348 	return m_NextPos;
   349 }
   350 
   351 bool CRadioDisplay::SetCurrentKey(unsigned int key)
   352 {
   353 	if (key < m_NextPos || m_NextPos > s_RadioMaxPageItems)
   354 	{
   355 		return false;
   356 	}
   357 
   358 	m_NextPos = key;
   359 
   360 	return true;
   361 }
   362 
   363 bool CRadioDisplay::SendDisplay(int client, IMenuHandler *handler, unsigned int time)
   364 {
   365 	return g_RadioMenuStyle.DoClientMenu(client, this, handler, time);
   366 }
   367 
   368 bool CRadioDisplay::SetExtOption(MenuOption option, const void *valuePtr)
   369 {
   370 	return false;
   371 }
   372 
   373 IMenuStyle *CRadioDisplay::GetParentStyle()
   374 {
   375 	return &g_RadioMenuStyle;
   376 }
   377 
   378 void CRadioDisplay::DrawTitle(const char *text, bool onlyIfEmpty/* =false */)
   379 {
   380 	if (m_Title.size() != 0 && onlyIfEmpty)
   381 	{
   382 		return;
   383 	}
   384 	m_Title.assign(text);
   385 }
   386 
   387 unsigned int CRadioDisplay::DrawItem(const ItemDrawInfo &item)
   388 {
   389 	if (m_NextPos > s_RadioMaxPageItems || !CanDrawItem(item.style))
   390 	{
   391 		return 0;
   392 	}
   393 
   394 	if (item.style & ITEMDRAW_RAWLINE)
   395 	{
   396 		if (item.style & ITEMDRAW_SPACER)
   397 		{
   398 			m_BufferText.append(" \n");
   399 		} else {
   400 			m_BufferText.append(item.display);
   401 			m_BufferText.append("\n");
   402 		}
   403 		return 0;
   404 	} else if (item.style & ITEMDRAW_SPACER) {
   405 		m_BufferText.append(" \n");
   406 		return m_NextPos++;
   407 	} else if (item.style & ITEMDRAW_NOTEXT) {
   408 		return m_NextPos++;
   409 	}
   410 
   411 	if (item.style & ITEMDRAW_DISABLED)
   412 	{
   413 		m_BufferText.append(g_RadioNumTable[m_NextPos]);
   414 		m_BufferText.append(item.display);
   415 		m_BufferText.append("\n");
   416 	} else {
   417 		m_BufferText.append("->");
   418 		m_BufferText.append(g_RadioNumTable[m_NextPos]);
   419 		m_BufferText.append(item.display);
   420 		m_BufferText.append("\n");
   421 		keys |= (1<<(m_NextPos-1));
   422 	}
   423 
   424 	return m_NextPos++;
   425 }
   426 
   427 bool CRadioDisplay::CanDrawItem(unsigned int drawFlags)
   428 {
   429 	if ((drawFlags & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
   430 	{
   431 		return false;
   432 	}
   433 
   434 	if ((drawFlags & ITEMDRAW_DISABLED) && (drawFlags & ITEMDRAW_CONTROL))
   435 	{
   436 		return false;
   437 	}
   438 
   439 	return true;
   440 }
   441 
   442 void CRadioDisplay::SendRawDisplay(int client, unsigned int time)
   443 {
   444 	int _sel_keys = (keys == 0) ? (1<<9) : keys;
   445 	CRadioMenuPlayer *pPlayer = g_RadioMenuStyle.GetRadioMenuPlayer(client);
   446 	pPlayer->Radio_Init(_sel_keys, m_Title.c_str(), m_BufferText.c_str());
   447 	pPlayer->Radio_Refresh();
   448 }
   449 
   450 void CRadioMenuPlayer::Radio_SetIndex(unsigned int index)
   451 {
   452 	m_index = index;
   453 }
   454 
   455 bool CRadioMenuPlayer::Radio_NeedsRefresh()
   456 {
   457 	return (gpGlobals->curtime - display_last_refresh >= g_RadioMenuTimeout);
   458 }
   459 
   460 void CRadioMenuPlayer::Radio_Init(int keys, const char *title, const char *text)
   461 {
   462 	if (title[0] != '\0')
   463 	{
   464 		display_len = UTIL_Format(display_pkt, 
   465 			sizeof(display_pkt), 
   466 			"%s\n%s", 
   467 			title,
   468 			text);
   469 	}
   470 	else
   471 	{
   472 		display_len = UTIL_Format(display_pkt, 
   473 			sizeof(display_pkt), 
   474 			"%s", 
   475 			text);
   476 	}
   477 	display_keys = keys;
   478 }
   479 
   480 void CRadioMenuPlayer::Radio_Refresh()
   481 {
   482 #if SOURCE_ENGINE != SE_DOTA
   483 	cell_t players[1] = {m_index};
   484 	char *ptr = display_pkt;
   485 	char save = 0;
   486 	size_t len = display_len;
   487 	unsigned int time;
   488 
   489 	/* Compute the new time */
   490 	if (menuHoldTime == 0)
   491 	{
   492 		time = 0;
   493 	}
   494 	else
   495 	{
   496 		time = menuHoldTime - (unsigned int)(gpGlobals->curtime - menuStartTime);
   497 	}
   498 
   499 #ifdef USE_PROTOBUF_USERMESSAGES
   500 	// If or when we need to support multiple games per engine with this, we can switch to reflection
   501 	// TODO: find what happens past 240 on CS:GO
   502 	CCSUsrMsg_ShowMenu *msg = (CCSUsrMsg_ShowMenu *)g_UserMsgs.StartProtobufMessage(g_ShowMenuId, players, 1, USERMSG_BLOCKHOOKS);
   503 	msg->set_bits_valid_slots(display_keys);
   504 	msg->set_display_time(time);
   505 	msg->set_menu_string(ptr);
   506 	g_UserMsgs.EndMessage();
   507 #else
   508 	while (true)
   509 	{
   510 		if (len > 240)
   511 		{
   512 			save = ptr[240];
   513 			ptr[240] = '\0';
   514 		}
   515 
   516 		bf_write *buffer = g_UserMsgs.StartBitBufMessage(g_ShowMenuId, players, 1, USERMSG_BLOCKHOOKS);
   517 		buffer->WriteWord(display_keys);
   518 		buffer->WriteChar(time ? time : -1);
   519 		buffer->WriteByte( (len > 240) ? 1 : 0 );
   520 		buffer->WriteString(ptr);
   521 		g_UserMsgs.EndMessage();
   522 		if (len > 240)
   523 		{
   524 			ptr[240] = save;
   525 			ptr = &ptr[240];
   526 			len -= 240;
   527 		}
   528 		else
   529 		{
   530 			break;
   531 		}
   532 	}
   533 #endif
   534 
   535 	display_last_refresh = gpGlobals->curtime;
   536 #endif // !DOTA
   537 }
   538 
   539 int CRadioDisplay::GetAmountRemaining()
   540 {
   541 	size_t amt = m_Title.size() + 1 + m_BufferText.size();
   542 	if (amt >= 511)
   543 	{
   544 		return 0;
   545 	}
   546 	return (int)(511 - amt);
   547 }
   548 
   549 void CRadioDisplay::DeleteThis()
   550 {
   551 	delete this;
   552 }
   553 
   554 bool CRadioDisplay::SetSelectableKeys(unsigned int keymap)
   555 {
   556 	keys = (signed)keymap;
   557 	return true;
   558 }
   559 
   560 unsigned int CRadioDisplay::GetApproxMemUsage()
   561 {
   562 	return sizeof(CRadioDisplay)
   563 		+ m_BufferText.size()
   564 		+ m_Title.size();
   565 }
   566 
   567 CRadioMenu::CRadioMenu(IMenuHandler *pHandler, IdentityToken_t *pOwner) : 
   568 CBaseMenu(pHandler, &g_RadioMenuStyle, pOwner)
   569 {
   570 	m_Pagination = s_RadioMaxPageItems - MAX_PAGINATION_OPTIONS;
   571 }
   572 
   573 bool CRadioMenu::SetExtOption(MenuOption option, const void *valuePtr)
   574 {
   575 	return false;
   576 }
   577 
   578 IMenuPanel *CRadioMenu::CreatePanel()
   579 {
   580 	return g_RadioMenuStyle.MakeRadioDisplay(this);
   581 }
   582 
   583 bool CRadioMenu::Display(int client, unsigned int time, IMenuHandler *alt_handler)
   584 {
   585 	return DisplayAtItem(client, time, 0, alt_handler);
   586 }
   587 
   588 bool CRadioMenu::DisplayAtItem(int client,
   589 							   unsigned int time,
   590 							   unsigned int start_item,
   591 							   IMenuHandler *alt_handler)
   592 {
   593 #if defined MENU_DEBUG
   594 	g_Logger.LogMessage("[SM_MENU] CRadioMenu::Display(%p) (client %d) (time %d)",
   595 		this,
   596 		client,
   597 		time);
   598 #endif
   599 	if (m_bCancelling)
   600 	{
   601 		return false;
   602 	}
   603 
   604 	return g_RadioMenuStyle.DoClientMenu(client,
   605 		this,
   606 		start_item,
   607 		alt_handler ? alt_handler : m_pHandler, 
   608 		time);
   609 }
   610 
   611 bool CRadioMenu::SetPagination(unsigned int itemsPerPage)
   612 {
   613 	const unsigned int maxPerPage = s_RadioMaxPageItems - MAX_PAGINATION_OPTIONS;
   614 	if (itemsPerPage > maxPerPage)
   615 	{
   616 		return false;
   617 	}
   618 
   619 	return CBaseMenu::SetPagination(itemsPerPage);
   620 }
   621 
   622 void CRadioMenu::Cancel_Finally()
   623 {
   624 	g_RadioMenuStyle.CancelMenu(this);
   625 }
   626 
   627 unsigned int CRadioMenu::GetApproxMemUsage()
   628 {
   629 	return sizeof(CRadioMenu) + GetBaseMemUsage();
   630 }
   631 
   632 const char *g_RadioNumTable[11] = 
   633 {
   634 	"0. ", "1. ", "2. ", "3. ", "4. ", "5. ", "6. ", "7. ", "8. ", "9. ", "0. "
   635 };