/** * vim: set ts=4 : * ============================================================================= * SourceMod * Copyright (C) 2004-2008 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 "sm_stringutil.h" #include "MenuStyle_Base.h" #include "PlayerManager.h" #include "MenuManager.h" #include "CellRecipientFilter.h" #if defined MENU_DEBUG #include "Logger.h" #endif #include "logic_bridge.h" #include "AutoHandleRooter.h" BaseMenuStyle::BaseMenuStyle() : m_WatchList(256), m_hHandle(BAD_HANDLE) { } Handle_t BaseMenuStyle::GetHandle() { /* Don't create the handle until we need it */ if (m_hHandle == BAD_HANDLE) { m_hHandle = g_Menus.CreateStyleHandle(this); } return m_hHandle; } void BaseMenuStyle::AddClientToWatch(int client) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] AddClientToWatch(%d)", client); #endif m_WatchList.push_back(client); } void BaseMenuStyle::RemoveClientFromWatch(int client) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] RemoveClientFromWatch(%d)", client); #endif m_WatchList.remove(client); } void BaseMenuStyle::_CancelClientMenu(int client, MenuCancelReason reason, bool bAutoIgnore/* =false */) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] _CancelClientMenu() (client %d) (bAutoIgnore %d) (reason %d)", client, bAutoIgnore, reason); #endif CBaseMenuPlayer *player = GetMenuPlayer(client); menu_states_t &states = player->states; bool bOldIgnore = player->bAutoIgnore; if (bAutoIgnore) { player->bAutoIgnore = true; } /* Save states */ IMenuHandler *mh = states.mh; IBaseMenu *menu = states.menu; /* Clear menu */ player->bInMenu = false; if (player->menuHoldTime) { RemoveClientFromWatch(client); } /* Fire callbacks */ mh->OnMenuCancel(menu, client, reason); /* Only fire end if there's a valid menu */ if (menu) { mh->OnMenuEnd(menu, MenuEnd_Cancelled); } if (bAutoIgnore) { player->bAutoIgnore = bOldIgnore; } } void BaseMenuStyle::CancelMenu(CBaseMenu *menu) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] CancelMenu() (menu %p)", menu); #endif int maxClients = g_Players.GetMaxClients(); for (int i=1; i<=maxClients; i++) { CBaseMenuPlayer *player = GetMenuPlayer(i); if (player->bInMenu) { menu_states_t &states = player->states; if (states.menu == menu) { _CancelClientMenu(i, MenuCancel_Interrupted); } } } } bool BaseMenuStyle::CancelClientMenu(int client, bool autoIgnore) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] CancelClientMenu() (client %d) (bAutoIgnore %d)", client, autoIgnore); #endif if (client < 1 || client > g_Players.MaxClients()) { return false; } CBaseMenuPlayer *player = GetMenuPlayer(client); if (!player->bInMenu) { return false; } _CancelClientMenu(client, MenuCancel_Interrupted, autoIgnore); return true; } MenuSource BaseMenuStyle::GetClientMenu(int client, void **object) { if (client < 1 || client > g_Players.GetMaxClients()) { return MenuSource_None; } CBaseMenuPlayer *player = GetMenuPlayer(client); if (player->bInMenu) { IBaseMenu *menu; if ((menu=player->states.menu) != NULL) { if (object) { *object = menu; } return MenuSource_BaseMenu; } return MenuSource_Display; } else if (player->bInExternMenu) { if (player->menuHoldTime != 0 && (gpGlobals->curtime > player->menuStartTime + player->menuHoldTime)) { player->bInExternMenu = false; return MenuSource_None; } return MenuSource_External; } return MenuSource_None; } void BaseMenuStyle::OnClientDisconnected(int client) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] OnClientDisconnected(%d)", client); #endif CBaseMenuPlayer *player = GetMenuPlayer(client); if (!player->bInMenu) { return; } _CancelClientMenu(client, MenuCancel_Disconnected, true); player->bInMenu = false; player->bInExternMenu = false; } static int do_lookup[256]; void BaseMenuStyle::ProcessWatchList() { if (!m_WatchList.size()) { return; } #if defined MENU_DEBUG g_Logger.LogMessage("BaseMenuStyle::ProcessWatchList(%d,%d,%d,%d,%d,%p)", m_WatchList.m_Size, m_WatchList.m_FirstLink, m_WatchList.m_FreeNodes, m_WatchList.m_LastLink, m_WatchList.m_MaxSize, m_WatchList.m_Nodes); #endif unsigned int total = 0; for (FastLink::iterator iter=m_WatchList.begin(); iter!=m_WatchList.end(); ++iter) { do_lookup[total++] = (*iter); } #if defined MENU_DEBUG if (total) { g_Logger.LogMessage("[SM_MENU] ProcessWatchList() found %d clients", total); } #endif int client; CBaseMenuPlayer *player; float curTime = gpGlobals->curtime; for (unsigned int i=0; ibInMenu, player->menuHoldTime, curTime, player->menuStartTime); #endif if (!player->bInMenu || !player->menuHoldTime) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] ProcessWatchList() removing client %d", client); #endif m_WatchList.remove(client); continue; } if (curTime > player->menuStartTime + player->menuHoldTime) { _CancelClientMenu(client, MenuCancel_Timeout, false); } } } void BaseMenuStyle::ClientPressedKey(int client, unsigned int key_press) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] ClientPressedKey() (client %d) (key_press %d)", client, key_press); #endif CBaseMenuPlayer *player = GetMenuPlayer(client); /* First question: Are we in a menu? */ if (!player->bInMenu) { return; } bool cancel = false; unsigned int item = 0; MenuCancelReason reason = MenuCancel_Exit; MenuEndReason end_reason = MenuEnd_Selected; menu_states_t &states = player->states; /* Save variables */ IMenuHandler *mh = states.mh; IBaseMenu *menu = states.menu; unsigned int item_on_page = states.item_on_page; assert(mh != NULL); if (menu == NULL) { item = key_press; } else if (key_press < 1 || key_press > GetMaxPageItems()) { cancel = true; } else { ItemSelection type = states.slots[key_press].type; /* Check if we should play a sound about the type */ if (g_Menus.MenuSoundsEnabled() && (!menu || (menu->GetMenuOptionFlags() & MENUFLAG_NO_SOUND) != MENUFLAG_NO_SOUND)) { CellRecipientFilter filter; cell_t clients[1]; clients[0] = client; filter.Initialize(clients, 1); const char *sound = g_Menus.GetMenuSound(type); if (sound != NULL) { edict_t *pEdict = PEntityOfEntIndex(client); if (pEdict) { ICollideable *pCollideable = pEdict->GetCollideable(); if (pCollideable) { const Vector & pos = pCollideable->GetCollisionOrigin(); enginesound->EmitSound(filter, client, CHAN_AUTO, #if SOURCE_ENGINE >= SE_PORTAL2 sound, -1, #endif sound, VOL_NORM, ATTN_NORM, #if SOURCE_ENGINE >= SE_PORTAL2 0, #endif 0, PITCH_NORM, #if SOURCE_ENGINE == SE_CSS || SOURCE_ENGINE == SE_HL2DM || SOURCE_ENGINE == SE_DODS || SOURCE_ENGINE == SE_TF2 0, #endif &pos); } } } } /* For navigational items, we're going to redisplay */ if (type == ItemSel_Back) { if (!RedoClientMenu(client, ItemOrder_Descending)) { cancel = true; reason = MenuCancel_NoDisplay; end_reason = MenuEnd_Cancelled; } else { return; } } else if (type == ItemSel_Next) { if (!RedoClientMenu(client, ItemOrder_Ascending)) { cancel = true; /* I like Saltines. */ reason = MenuCancel_NoDisplay; end_reason = MenuEnd_Cancelled; } else { return; } } else if (type == ItemSel_Exit || type == ItemSel_None) { cancel = true; reason = MenuCancel_Exit; end_reason = MenuEnd_Exit; } else if (type == ItemSel_ExitBack) { cancel = true; reason = MenuCancel_ExitBack; end_reason = MenuEnd_ExitBack; } else { item = states.slots[key_press].item; } } /* Clear states */ player->bInMenu = false; if (player->menuHoldTime) { RemoveClientFromWatch(client); } Handle_t hndl = menu ? menu->GetHandle() : BAD_HANDLE; AutoHandleRooter ahr(hndl); if (cancel) { mh->OnMenuCancel(menu, client, reason); } else { mh->OnMenuSelect(menu, client, item); if (mh->GetMenuAPIVersion2() >= 13) { mh->OnMenuSelect2(menu, client, item, item_on_page); } } /* Only fire end for valid menus */ if (menu) { mh->OnMenuEnd(menu, end_reason); } } bool BaseMenuStyle::DoClientMenu(int client, IMenuPanel *menu, IMenuHandler *mh, unsigned int time) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] DoClientMenu() (client %d) (panel %p) (mh %p) (time %d)", client, menu, mh, time); #endif CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame()) { return false; } CBaseMenuPlayer *player = GetMenuPlayer(client); if (player->bAutoIgnore) { return false; } /* For the duration of this, we are going to totally ignore whether * the player is already in a menu or not (except to cancel the old one). * Instead, we are simply going to ignore any further menu displays, so * this display can't be interrupted. */ player->bAutoIgnore = true; /* Cancel any old menus */ menu_states_t &states = player->states; if (player->bInMenu) { _CancelClientMenu(client, MenuCancel_Interrupted, true); } states.firstItem = 0; states.lastItem = 0; states.menu = NULL; states.mh = mh; states.apiVers = SMINTERFACE_MENUMANAGER_VERSION; player->bInMenu = true; player->bInExternMenu = false; player->menuStartTime = gpGlobals->curtime; player->menuHoldTime = time; if (time) { AddClientToWatch(client); } /* Draw the display */ SendDisplay(client, menu); /* We can be interrupted again! */ player->bAutoIgnore = false; return true; } bool BaseMenuStyle::DoClientMenu(int client, CBaseMenu *menu, unsigned int first_item, IMenuHandler *mh, unsigned int time) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] DoClientMenu() (client %d) (menu %p) (mh %p) (time %d)", client, menu, mh, time); #endif mh->OnMenuStart(menu); CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame()) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] DoClientMenu(): Failed to display to client %d", client); #endif mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay); mh->OnMenuEnd(menu, MenuEnd_Cancelled); return false; } CBaseMenuPlayer *player = GetMenuPlayer(client); if (player->bAutoIgnore) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] DoClientMenu(): Client %d is autoIgnoring", client); #endif mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay); mh->OnMenuEnd(menu, MenuEnd_Cancelled); return false; } /* For the duration of this, we are going to totally ignore whether * the player is already in a menu or not (except to cancel the old one). * Instead, we are simply going to ignore any further menu displays, so * this display can't be interrupted. */ player->bAutoIgnore = true; /* Cancel any old menus */ menu_states_t &states = player->states; if (player->bInMenu) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] DoClientMenu(): Cancelling old menu to client %d", client); #endif _CancelClientMenu(client, MenuCancel_Interrupted, true); } states.firstItem = 0; states.lastItem = first_item; states.menu = menu; states.mh = mh; states.apiVers = SMINTERFACE_MENUMANAGER_VERSION; IMenuPanel *display = g_Menus.RenderMenu(client, states, ItemOrder_Ascending); if (!display) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] DoClientMenu(): Failed to render to client %d", client); #endif player->bAutoIgnore = false; player->bInMenu = false; mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay); mh->OnMenuEnd(menu, MenuEnd_Cancelled); return false; } /* Finally, set our states */ player->bInMenu = true; player->bInExternMenu = false; player->menuStartTime = gpGlobals->curtime; player->menuHoldTime = time; if (time) { AddClientToWatch(client); } /* Draw the display */ SendDisplay(client, display); /* Free the display pointer */ display->DeleteThis(); /* We can be interrupted again! */ player->bAutoIgnore = false; #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] DoClientMenu() finished successfully (client %d)", client); #endif return true; } bool BaseMenuStyle::RedoClientMenu(int client, ItemOrder order) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] RedoClientMenu() (client %d) (order %d)", client, order); #endif CBaseMenuPlayer *player = GetMenuPlayer(client); menu_states_t &states = player->states; player->bAutoIgnore = true; IMenuPanel *display = g_Menus.RenderMenu(client, states, order); if (!display) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] RedoClientMenu(): Failed to render menu"); #endif if (player->menuHoldTime) { RemoveClientFromWatch(client); } player->bAutoIgnore = false; return false; } SendDisplay(client, display); display->DeleteThis(); player->bAutoIgnore = false; #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] RedoClientMenu(): Succeeded to client %d", client); #endif return true; } CBaseMenu::CBaseMenu(IMenuHandler *pHandler, IMenuStyle *pStyle, IdentityToken_t *pOwner) : m_pStyle(pStyle), m_Strings(512), m_Pagination(7), m_bShouldDelete(false), m_bCancelling(false), m_pOwner(pOwner ? pOwner : g_pCoreIdent), m_bDeleting(false), m_bWillFreeHandle(false), m_hHandle(BAD_HANDLE), m_pHandler(pHandler), m_nFlags(MENUFLAG_BUTTON_EXIT) { } CBaseMenu::~CBaseMenu() { } Handle_t CBaseMenu::GetHandle() { if (!m_hHandle) { m_hHandle = g_Menus.CreateMenuHandle(this, m_pOwner); } return m_hHandle; } bool CBaseMenu::AppendItem(const char *info, const ItemDrawInfo &draw) { if (m_Pagination == (unsigned)MENU_NO_PAGINATION && m_items.size() >= m_pStyle->GetMaxPageItems()) { return false; } CItem item; item.infoString = m_Strings.AddString(info); if (draw.display) { item.displayString = m_Strings.AddString(draw.display); } item.style = draw.style; m_items.push_back(item); return true; } bool CBaseMenu::InsertItem(unsigned int position, const char *info, const ItemDrawInfo &draw) { if (m_Pagination == (unsigned)MENU_NO_PAGINATION && m_items.size() >= m_pStyle->GetMaxPageItems()) { return false; } if (position >= m_items.size()) { return false; } CItem item; item.infoString = m_Strings.AddString(info); if (draw.display) { item.displayString = m_Strings.AddString(draw.display); } item.style = draw.style; CVector::iterator iter = m_items.iterAt(position); m_items.insert(iter, item); return true; } bool CBaseMenu::RemoveItem(unsigned int position) { if (position >= m_items.size()) { return false; } m_items.erase(m_items.iterAt(position)); if (m_items.size() == 0) { m_Strings.Reset(); } return true; } void CBaseMenu::RemoveAllItems() { m_items.clear(); m_Strings.Reset(); } const char *CBaseMenu::GetItemInfo(unsigned int position, ItemDrawInfo *draw/* =NULL */) { if (position >= m_items.size()) { return NULL; } if (draw) { draw->display = m_Strings.GetString(m_items[position].displayString); draw->style = m_items[position].style; } return m_Strings.GetString(m_items[position].infoString); } unsigned int CBaseMenu::GetItemCount() { return m_items.size(); } bool CBaseMenu::SetPagination(unsigned int itemsPerPage) { if (itemsPerPage > 7 || itemsPerPage == 1) { return false; } if (itemsPerPage == MENU_NO_PAGINATION && m_Pagination != MENU_NO_PAGINATION) { m_nFlags &= ~MENUFLAG_BUTTON_EXIT; } m_Pagination = itemsPerPage; return true; } unsigned int CBaseMenu::GetPagination() { return m_Pagination; } IMenuStyle *CBaseMenu::GetDrawStyle() { return m_pStyle; } void CBaseMenu::SetDefaultTitle(const char *message) { m_Title.assign(message); } const char *CBaseMenu::GetDefaultTitle() { return m_Title.c_str(); } void CBaseMenu::Cancel() { if (m_bCancelling) { return; } #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] CBaseMenu::Cancel(%p) (m_bShouldDelete %d)", this, m_bShouldDelete); #endif m_bCancelling = true; Cancel_Finally(); m_bCancelling = false; if (m_bShouldDelete) { InternalDelete(); } } void CBaseMenu::Destroy(bool releaseHandle) { /* Check if we shouldn't be here */ if (m_bDeleting) { return; } #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] CBaseMenu::Destroy(%p) (release %d) (m_bCancelling %d) (m_bShouldDelete %d)", this, releaseHandle, m_bCancelling, m_bShouldDelete); #endif /* Save the destruction hint about our handle */ m_bWillFreeHandle = releaseHandle; /* Now actually do stuff */ if (!m_bCancelling || m_bShouldDelete) { Cancel(); InternalDelete(); } else { m_bShouldDelete = true; } } void CBaseMenu::InternalDelete() { if (m_bWillFreeHandle && m_hHandle != BAD_HANDLE) { Handle_t hndl = m_hHandle; HandleSecurity sec; sec.pOwner = m_pOwner; sec.pIdentity = g_pCoreIdent; m_hHandle = BAD_HANDLE; m_bDeleting = true; handlesys->FreeHandle(hndl, &sec); } m_pHandler->OnMenuDestroy(this); delete this; } unsigned int CBaseMenu::GetMenuOptionFlags() { return m_nFlags; } void CBaseMenu::SetMenuOptionFlags(unsigned int flags) { m_nFlags = flags; } IMenuHandler *CBaseMenu::GetHandler() { return m_pHandler; } unsigned int CBaseMenu::GetBaseMemUsage() { return m_Title.size() + m_Strings.GetMemTable()->GetMemUsage() + (m_items.size() * sizeof(CItem)); }