1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/core/MenuManager.cpp Sat May 12 23:45:52 2007 +0000
1.3 @@ -0,0 +1,676 @@
1.4 +#include <stdarg.h>
1.5 +#include <time.h>
1.6 +#include "MenuManager.h"
1.7 +#include "sm_stringutil.h"
1.8 +#include "sourcemm_api.h"
1.9 +#include "PlayerManager.h"
1.10 +#include "MenuStyle_Valve.h"
1.11 +
1.12 +MenuManager g_Menus;
1.13 +
1.14 +/*************************************
1.15 + *************************************
1.16 + **** BROADCAST HANDLING WRAPPERS ****
1.17 + *************************************
1.18 + *************************************/
1.19 +
1.20 +BroadcastHandler::BroadcastHandler(IMenuHandler *handler) : m_pHandler(handler), numClients(0)
1.21 +{
1.22 +}
1.23 +
1.24 +unsigned int BroadcastHandler::GetMenuAPIVersion2()
1.25 +{
1.26 + return m_pHandler->GetMenuAPIVersion2();
1.27 +}
1.28 +
1.29 +void BroadcastHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
1.30 +{
1.31 + m_pHandler->OnMenuCancel(menu, client, reason);
1.32 +}
1.33 +
1.34 +void BroadcastHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuDisplay *display)
1.35 +{
1.36 + numClients++;
1.37 + m_pHandler->OnMenuDisplay(menu, client, display);
1.38 +}
1.39 +
1.40 +void BroadcastHandler::OnBroadcastEnd(IBaseMenu *menu)
1.41 +{
1.42 + g_Menus.FreeBroadcastHandler(this);
1.43 +}
1.44 +
1.45 +void BroadcastHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
1.46 +{
1.47 + m_pHandler->OnMenuSelect(menu, client, item);
1.48 +}
1.49 +
1.50 +void BroadcastHandler::OnMenuEnd(IBaseMenu *menu)
1.51 +{
1.52 + assert(numClients > 0);
1.53 +
1.54 + /* Only fire if all clients have gotten a menu end */
1.55 + if (--numClients == 0)
1.56 + {
1.57 + IMenuHandler *pHandler = m_pHandler;
1.58 + OnBroadcastEnd(menu);
1.59 + pHandler->OnMenuEnd(menu);
1.60 + }
1.61 +}
1.62 +
1.63 +VoteHandler::VoteHandler(IMenuVoteHandler *handler)
1.64 +: BroadcastHandler(handler), m_pVoteHandler(handler)
1.65 +{
1.66 +}
1.67 +
1.68 +void VoteHandler::Initialize(IBaseMenu *menu)
1.69 +{
1.70 + unsigned int numItems = menu->GetItemCount();
1.71 +
1.72 + if (m_counts.size() >= numItems)
1.73 + {
1.74 + for (size_t i=0; i<numItems; i++)
1.75 + {
1.76 + m_counts[i] = 0;
1.77 + }
1.78 + } else {
1.79 + for (size_t i=0; i<m_counts.size(); i++)
1.80 + {
1.81 + m_counts[i] = 0;
1.82 + }
1.83 + m_counts.resize(numItems, 0);
1.84 + }
1.85 +}
1.86 +
1.87 +void VoteHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
1.88 +{
1.89 + if (item < numItems)
1.90 + {
1.91 + m_counts[item]++;
1.92 + }
1.93 +
1.94 + BroadcastHandler::OnMenuSelect(menu, client, item);
1.95 +}
1.96 +
1.97 +void VoteHandler::OnBroadcastEnd(IBaseMenu *menu)
1.98 +{
1.99 + m_ties.clear();
1.100 +
1.101 + size_t highest = 0;
1.102 + for (size_t i=1; i<numItems; i++)
1.103 + {
1.104 + if (m_counts[i] > m_counts[highest])
1.105 + {
1.106 + m_ties.clear();
1.107 + highest = i;
1.108 + } else if (m_counts[i] == m_counts[highest]) {
1.109 + m_ties.push_back(i);
1.110 + }
1.111 + }
1.112 +
1.113 + if (m_ties.size())
1.114 + {
1.115 + m_ties.push_back(highest);
1.116 + srand(static_cast<unsigned int>(time(NULL)));
1.117 + highest = m_ties[rand() % m_ties.size()];
1.118 + }
1.119 +
1.120 + m_pVoteHandler->OnMenuVoteEnd(menu, highest);
1.121 +
1.122 + g_Menus.FreeVoteHandler(this);
1.123 +}
1.124 +
1.125 +/*******************************
1.126 + *******************************
1.127 + ******** MENU MANAGER *********
1.128 + *******************************
1.129 + *******************************/
1.130 +
1.131 +MenuManager::MenuManager()
1.132 +{
1.133 + m_ShowMenu = -1;
1.134 + m_pDefaultStyle = NULL;
1.135 +}
1.136 +
1.137 +void MenuManager::OnSourceModAllInitialized()
1.138 +{
1.139 + int num = g_SMAPI->GetUserMessageCount();
1.140 + if (num >= 1)
1.141 + {
1.142 + for (int i=0; i<num; i++)
1.143 + {
1.144 + if (strcmp(g_SMAPI->GetUserMessage(i, NULL), "ShowMenu") == 0)
1.145 + {
1.146 + m_ShowMenu = i;
1.147 + break;
1.148 + }
1.149 + }
1.150 + }
1.151 +
1.152 + /* :TODO: styles */
1.153 + m_Styles.push_back(&g_ValveMenuStyle);
1.154 + SetDefaultStyle(&g_ValveMenuStyle);
1.155 +}
1.156 +
1.157 +void MenuManager::OnSourceModAllShutdown()
1.158 +{
1.159 + while (!m_BroadcastHandlers.empty())
1.160 + {
1.161 + delete m_BroadcastHandlers.front();
1.162 + m_BroadcastHandlers.pop();
1.163 + }
1.164 +
1.165 + while (!m_VoteHandlers.empty())
1.166 + {
1.167 + delete m_VoteHandlers.front();
1.168 + m_VoteHandlers.pop();
1.169 + }
1.170 +}
1.171 +
1.172 +bool MenuManager::SetDefaultStyle(IMenuStyle *style)
1.173 +{
1.174 + if (!style)
1.175 + {
1.176 + return false;
1.177 + }
1.178 +
1.179 + m_pDefaultStyle = style;
1.180 +
1.181 + return true;
1.182 +}
1.183 +
1.184 +IMenuStyle *MenuManager::GetStyle(unsigned int index)
1.185 +{
1.186 + if (index >= GetStyleCount())
1.187 + {
1.188 + return NULL;
1.189 + }
1.190 +
1.191 + return m_Styles[index];
1.192 +}
1.193 +
1.194 +unsigned int MenuManager::GetStyleCount()
1.195 +{
1.196 + return (unsigned int)m_Styles.size();
1.197 +}
1.198 +
1.199 +IMenuStyle *MenuManager::FindStyleByName(const char *name)
1.200 +{
1.201 + unsigned int count = GetStyleCount();
1.202 + for (unsigned int i=0; i<count; i++)
1.203 + {
1.204 + IMenuStyle *ptr = GetStyle(i);
1.205 + if (strcasecmp(ptr->GetStyleName(), name) == 0)
1.206 + {
1.207 + return ptr;
1.208 + }
1.209 + }
1.210 +
1.211 + return NULL;
1.212 +}
1.213 +
1.214 +inline bool IsSlotItem(IMenuDisplay *display,
1.215 + unsigned int style)
1.216 +{
1.217 + if (!display->CanDrawItem(style))
1.218 + {
1.219 + return false;
1.220 + }
1.221 + if ((style & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
1.222 + {
1.223 + return false;
1.224 + }
1.225 + if (style & ITEMDRAW_RAWLINE)
1.226 + {
1.227 + return false;
1.228 + }
1.229 +
1.230 + return true;
1.231 +}
1.232 +
1.233 +IMenuDisplay *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder order)
1.234 +{
1.235 + IBaseMenu *menu = md.menu;
1.236 +
1.237 + if (!menu)
1.238 + {
1.239 + return NULL;
1.240 + }
1.241 +
1.242 + struct
1.243 + {
1.244 + unsigned int position;
1.245 + ItemDrawInfo draw;
1.246 + } drawItems[10];
1.247 +
1.248 + /* Figure out how many items to draw */
1.249 + IMenuStyle *style = menu->GetDrawStyle();
1.250 + unsigned int pgn = menu->GetPagination();
1.251 + unsigned int maxItems = style->GetMaxPageItems();
1.252 + if (pgn != MENU_NO_PAGINATION)
1.253 + {
1.254 + maxItems = pgn;
1.255 + }
1.256 +
1.257 + unsigned int totalItems = menu->GetItemCount();
1.258 + unsigned int startItem = 0;
1.259 +
1.260 + /* For pagination, find the starting point. */
1.261 + if (pgn != MENU_NO_PAGINATION)
1.262 + {
1.263 + if (order == ItemOrder_Ascending)
1.264 + {
1.265 + startItem = md.lastItem;
1.266 + /* This shouldn't happen with well-coded menus.
1.267 + * If the item is out of bounds, switch the order to
1.268 + * Items_Descending and make us start from the top.
1.269 + */
1.270 + if (startItem >= totalItems)
1.271 + {
1.272 + startItem = totalItems - 1;
1.273 + order = ItemOrder_Descending;
1.274 + }
1.275 + } else if (order == ItemOrder_Descending) {
1.276 + startItem = md.firstItem;
1.277 + /* This shouldn't happen with well-coded menus.
1.278 + * If searching backwards doesn't give us enough room,
1.279 + * start from the beginning and change to ascending.
1.280 + */
1.281 + if (startItem <= maxItems)
1.282 + {
1.283 + startItem = 0;
1.284 + order = ItemOrder_Ascending;
1.285 + }
1.286 + }
1.287 + }
1.288 +
1.289 + /* Get our Display pointer and initialize some crap */
1.290 + IMenuDisplay *display = menu->CreateDisplay();
1.291 + IMenuHandler *mh = md.mh;
1.292 + bool foundExtra = false;
1.293 + unsigned int extraItem = 0;
1.294 +
1.295 + /**
1.296 + * We keep searching until:
1.297 + * 1) There are no more items
1.298 + * 2) We reach one OVER the maximum number of slot items
1.299 + * 3) We have reached maxItems and pagination is MENU_NO_PAGINATION
1.300 + */
1.301 + unsigned int i = startItem;
1.302 + unsigned int foundItems = 0;
1.303 + while (true)
1.304 + {
1.305 + ItemDrawInfo &dr = drawItems[foundItems].draw;
1.306 + /* Is the item valid? */
1.307 + if (menu->GetItemInfo(i, &dr) != NULL)
1.308 + {
1.309 + /* Ask the user to change the style, if necessary */
1.310 + mh->OnMenuDrawItem(menu, client, i, dr.style);
1.311 + /* Check if it's renderable */
1.312 + if (IsSlotItem(display, dr.style))
1.313 + {
1.314 + /* If we've already found the max number of items,
1.315 + * This means we should just cancel out and log our
1.316 + * "last item."
1.317 + */
1.318 + if (foundItems >= maxItems)
1.319 + {
1.320 + foundExtra = true;
1.321 + extraItem = i;
1.322 + break;
1.323 + }
1.324 + drawItems[foundItems++].position = i;
1.325 + }
1.326 + }
1.327 + /* If there's no pagination, stop once the menu is full. */
1.328 + if (pgn == MENU_NO_PAGINATION)
1.329 + {
1.330 + /* If we've filled up, then stop */
1.331 + if (foundItems >= maxItems)
1.332 + {
1.333 + break;
1.334 + }
1.335 + }
1.336 + /* If we're descending and this is the first item, stop */
1.337 + if (order == ItemOrder_Descending)
1.338 + {
1.339 + if (i == 0)
1.340 + {
1.341 + break;
1.342 + }
1.343 + i--;
1.344 + }
1.345 + /* If we're ascending and this is the last item, stop */
1.346 + else if (order == ItemOrder_Ascending)
1.347 + {
1.348 + if (i >= totalItems - 1)
1.349 + {
1.350 + break;
1.351 + }
1.352 + i++;
1.353 + }
1.354 + }
1.355 +
1.356 + /* There were no items to draw! */
1.357 + if (!foundItems)
1.358 + {
1.359 + delete display;
1.360 + return NULL;
1.361 + }
1.362 +
1.363 + /* Check initial buttons */
1.364 + bool displayPrev = false;
1.365 + bool displayNext = false;
1.366 + if (foundExtra)
1.367 + {
1.368 + if (order == ItemOrder_Descending)
1.369 + {
1.370 + displayPrev = true;
1.371 + md.firstItem = extraItem;
1.372 + } else if (order == ItemOrder_Ascending) {
1.373 + displayNext = true;
1.374 + md.lastItem = extraItem;
1.375 + }
1.376 + }
1.377 +
1.378 + /**
1.379 + * If we're paginated, we have to find if there is another page.
1.380 + * Sadly, the only way to do this is to try drawing more items!
1.381 + */
1.382 + if (pgn != MENU_NO_PAGINATION)
1.383 + {
1.384 + unsigned int lastItem = 0;
1.385 + ItemDrawInfo dr;
1.386 + /* Find the last feasible item to search from. */
1.387 + if (order == ItemOrder_Descending)
1.388 + {
1.389 + lastItem = drawItems[0].position;
1.390 + if (lastItem >= totalItems - 1)
1.391 + {
1.392 + goto skip_search;
1.393 + }
1.394 + while (++lastItem < totalItems)
1.395 + {
1.396 + if (menu->GetItemInfo(lastItem, &dr) != NULL)
1.397 + {
1.398 + mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
1.399 + if (IsSlotItem(display, dr.style))
1.400 + {
1.401 + displayNext = true;
1.402 + md.lastItem = lastItem;
1.403 + break;
1.404 + }
1.405 + }
1.406 + }
1.407 + } else if (order == ItemOrder_Ascending) {
1.408 + lastItem = drawItems[0].position;
1.409 + if (lastItem == 0)
1.410 + {
1.411 + goto skip_search;
1.412 + }
1.413 + lastItem--;
1.414 + while (lastItem-- != 0)
1.415 + {
1.416 + if (menu->GetItemInfo(lastItem, &dr) != NULL)
1.417 + {
1.418 + mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
1.419 + if (IsSlotItem(display, dr.style))
1.420 + {
1.421 + displayPrev = true;
1.422 + md.firstItem = lastItem;
1.423 + break;
1.424 + }
1.425 + }
1.426 + }
1.427 + }
1.428 + }
1.429 +skip_search:
1.430 +
1.431 + /* Draw the item according to the order */
1.432 + menu_slots_t *slots = md.slots;
1.433 + unsigned int position = 0; /* Keep track of the last position */
1.434 + if (order == ItemOrder_Ascending)
1.435 + {
1.436 + for (unsigned int i=0; i<foundItems; i++)
1.437 + {
1.438 + ItemDrawInfo &dr = drawItems[i].draw;
1.439 + menu->GetItemInfo(drawItems[i].position, &dr);
1.440 + mh->OnMenuDisplayItem(menu, client, drawItems[i].position, &(dr.display));
1.441 + if ((position = display->DrawItem(dr)) != 0)
1.442 + {
1.443 + slots[position].item = drawItems[i].position;
1.444 + slots[position].type = ItemSel_Item;
1.445 + }
1.446 + }
1.447 + } else if (order == ItemOrder_Descending) {
1.448 + unsigned int i = foundItems;
1.449 + /* NOTE: There will always be at least one item because
1.450 + * of the check earlier.
1.451 + */
1.452 + while (i--)
1.453 + {
1.454 + ItemDrawInfo &dr = drawItems[i].draw;
1.455 + menu->GetItemInfo(drawItems[i].position, &dr);
1.456 + mh->OnMenuDisplayItem(menu, client, drawItems[i].position, &(dr.display));
1.457 + if ((position = display->DrawItem(dr)) != 0)
1.458 + {
1.459 + slots[position].item = drawItems[i].position;
1.460 + slots[position].type = ItemSel_Item;
1.461 + }
1.462 + }
1.463 + }
1.464 +
1.465 + /* Now, we need to check if we need to add anything extra */
1.466 + if (pgn != MENU_NO_PAGINATION)
1.467 + {
1.468 + bool canDrawDisabled = display->CanDrawItem(ITEMDRAW_DISABLED);
1.469 + bool exitButton = menu->GetExitButton();
1.470 + char text[50];
1.471 +
1.472 + /* Calculate how many items we are allowed for control stuff */
1.473 + unsigned int padding = style->GetMaxPageItems() - maxItems;
1.474 +
1.475 + /* Add the number of available slots */
1.476 + padding += (maxItems - foundItems);
1.477 +
1.478 + /* Someday, if we are able to re-enable this, we will be very lucky men. */
1.479 +#if 0
1.480 + if (!style->FeatureExists(MenuStyleFeature_ImplicitExit))
1.481 + {
1.482 +#endif
1.483 + /* Even if we don't draw an exit button, we invalidate the slot. */
1.484 + padding--;
1.485 +#if 0
1.486 + } else {
1.487 + /* Otherwise, we don't draw anything and leave the slot available */
1.488 + exitButton = false;
1.489 + }
1.490 +#endif
1.491 +
1.492 + /* Subtract two slots for the displayNext/displayPrev padding */
1.493 + padding -= 2;
1.494 +
1.495 + /**
1.496 + * We allow next/prev to be undrawn if neither exists.
1.497 + * Thus, we only need padding if one of them will be drawn,
1.498 + * or the exit button will be drawn.
1.499 + */
1.500 + ItemDrawInfo padItem(NULL, ITEMDRAW_SPACER);
1.501 + if (exitButton || (displayNext || displayPrev))
1.502 + {
1.503 + /* Add spacers so we can pad to the end */
1.504 + unsigned int null_pos = 0;
1.505 + for (unsigned int i=0; i<padding; i++)
1.506 + {
1.507 + position = display->DrawItem(padItem);
1.508 + slots[position].type = ItemSel_None;
1.509 + }
1.510 + }
1.511 + ItemDrawInfo dr(text, 0);
1.512 +
1.513 + /* PREVIOUS */
1.514 + if (displayPrev || canDrawDisabled)
1.515 + {
1.516 + CorePlayerTranslate(client, text, sizeof(text), "Back", NULL);
1.517 + dr.style = displayPrev ? 0 : ITEMDRAW_DISABLED;
1.518 + position = display->DrawItem(dr);
1.519 + slots[position].type = ItemSel_Back;
1.520 + } else if ((displayNext || canDrawDisabled) || exitButton) {
1.521 + /* If we can't display this,
1.522 + * but there is a "next" or "exit" button, we need to pad!
1.523 + */
1.524 + position = display->DrawItem(padItem);
1.525 + slots[position].type = ItemSel_None;
1.526 + }
1.527 +
1.528 + /* NEXT */
1.529 + if (displayNext || canDrawDisabled)
1.530 + {
1.531 + CorePlayerTranslate(client, text, sizeof(text), "Next", NULL);
1.532 + dr.style = displayNext ? 0 : ITEMDRAW_DISABLED;
1.533 + position = display->DrawItem(dr);
1.534 + slots[position].type = ItemSel_Next;
1.535 + } else if (exitButton) {
1.536 + /* If we can't display this,
1.537 + * but there is an exit button, we need to pad!
1.538 + */
1.539 + position = display->DrawItem(padItem);
1.540 + slots[position].type = ItemSel_None;
1.541 + }
1.542 +
1.543 + /* EXIT */
1.544 + if (exitButton)
1.545 + {
1.546 + CorePlayerTranslate(client, text, sizeof(text), "Exit", NULL);
1.547 + dr.style = 0;
1.548 + position = display->DrawItem(dr);
1.549 + slots[position].type = ItemSel_Exit;
1.550 + }
1.551 + }
1.552 +
1.553 + /* Lastly, fill in any slots we could have missed */
1.554 + for (unsigned int i = position + 1; i < 10; i++)
1.555 + {
1.556 + slots[i].type = ItemSel_None;
1.557 + }
1.558 +
1.559 + /* Do title stuff */
1.560 + mh->OnMenuDisplay(menu, client, display);
1.561 + display->DrawTitle(menu->GetDefaultTitle(), true);
1.562 +
1.563 + return display;
1.564 +}
1.565 +
1.566 +unsigned int MenuManager::BroadcastMenu(IBaseMenu *menu,
1.567 + IMenuHandler *handler,
1.568 + int clients[],
1.569 + unsigned int numClients,
1.570 + unsigned int time)
1.571 +{
1.572 + BroadcastHandler *bh;
1.573 +
1.574 + if (m_BroadcastHandlers.empty())
1.575 + {
1.576 + bh = new BroadcastHandler(handler);
1.577 + } else {
1.578 + bh = m_BroadcastHandlers.front();
1.579 + m_BroadcastHandlers.pop();
1.580 + bh->m_pHandler = handler;
1.581 + bh->numClients = 0;
1.582 + }
1.583 +
1.584 + handler->OnMenuStart(menu);
1.585 +
1.586 + unsigned int total = 0;
1.587 + for (unsigned int i=0; i<numClients; i++)
1.588 + {
1.589 + /* Only continue if displaying works */
1.590 + if (!menu->Display(clients[i], bh, time))
1.591 + {
1.592 + continue;
1.593 + }
1.594 +
1.595 + /* :TODO: Allow sourcetv only, not all bots */
1.596 + CPlayer *player = g_Players.GetPlayerByIndex(clients[i]);
1.597 + if (player->IsFakeClient())
1.598 + {
1.599 + continue;
1.600 + }
1.601 +
1.602 + total++;
1.603 + }
1.604 +
1.605 + if (!total)
1.606 + {
1.607 + /* End the broadcast here */
1.608 + handler->OnMenuEnd(menu);
1.609 + FreeBroadcastHandler(bh);
1.610 + }
1.611 +
1.612 + return total;
1.613 +}
1.614 +
1.615 +unsigned int MenuManager::VoteMenu(IBaseMenu *menu,
1.616 + IMenuVoteHandler *handler,
1.617 + int clients[],
1.618 + unsigned int numClients,
1.619 + unsigned int time)
1.620 +{
1.621 + VoteHandler *vh;
1.622 +
1.623 + if (m_VoteHandlers.empty())
1.624 + {
1.625 + vh = new VoteHandler(handler);
1.626 + } else {
1.627 + vh = m_VoteHandlers.front();
1.628 + m_VoteHandlers.pop();
1.629 + vh->m_pHandler = handler;
1.630 + vh->numClients = 0;
1.631 + }
1.632 +
1.633 + vh->Initialize(menu);
1.634 + handler->OnMenuStart(menu);
1.635 +
1.636 + unsigned int total = 0;
1.637 + for (unsigned int i=0; i<numClients; i++)
1.638 + {
1.639 + /* Only continue if displaying works */
1.640 + if (!menu->Display(clients[i], vh, time))
1.641 + {
1.642 + continue;
1.643 + }
1.644 +
1.645 + /* :TODO: Allow sourcetv only, not all bots */
1.646 + CPlayer *player = g_Players.GetPlayerByIndex(clients[i]);
1.647 + if (player->IsFakeClient())
1.648 + {
1.649 + continue;
1.650 + }
1.651 +
1.652 + total++;
1.653 + }
1.654 +
1.655 + if (!total)
1.656 + {
1.657 + /* End the broadcast here */
1.658 + handler->OnMenuEnd(menu);
1.659 + FreeVoteHandler(vh);
1.660 + }
1.661 +
1.662 + return total;
1.663 +}
1.664 +
1.665 +void MenuManager::FreeBroadcastHandler(BroadcastHandler *bh)
1.666 +{
1.667 + m_BroadcastHandlers.push(bh);
1.668 +}
1.669 +
1.670 +void MenuManager::FreeVoteHandler(VoteHandler *vh)
1.671 +{
1.672 + m_VoteHandlers.push(vh);
1.673 +}
1.674 +
1.675 +IMenuStyle *MenuManager::GetDefaultStyle()
1.676 +{
1.677 + return m_pDefaultStyle;
1.678 +}
1.679 +