sourcemod/core/MenuStyle_Valve.cpp
David Anderson d7b2f9a721 behold with your eyes and view upon what are MENUS
--HG--
extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%40775
2007-05-12 23:45:52 +00:00

797 lines
18 KiB
C++

/**
* vim: set ts=4 :
* ===============================================================
* SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
* ===============================================================
*
* This file is not open source and may not be copied without explicit
* written permission of AlliedModders LLC. This file may not be redistributed
* in whole or significant part.
* For information, see LICENSE.txt or http://www.sourcemod.net/license.php
*
* Version: $Id$
*/
#include "sm_stringutil.h"
#include "PlayerManager.h"
#include "MenuStyle_Valve.h"
#include "Translator.h"
#include "PlayerManager.h"
#include "ConCmdManager.h"
SH_DECL_HOOK4_void(IServerPluginHelpers, CreateMessage, SH_NOATTRIB, false, edict_t *, DIALOG_TYPE, KeyValues *, IServerPluginCallbacks *);
ValveMenuStyle g_ValveMenuStyle;
const char *g_OptionNumTable[];
const char *g_OptionCmdTable[];
IServerPluginCallbacks *g_pVSPHandle = NULL;
CallClass<IServerPluginHelpers> *g_pSPHCC = NULL;
class TestHandler : public IMenuHandler
{
public:
virtual void OnMenuEnd(IBaseMenu *menu)
{
menu->Destroy();
}
};
ValveMenuStyle::ValveMenuStyle() : m_players(new CValveMenuPlayer[256+1]), m_WatchList(256)
{
}
bool ValveMenuStyle::OnClientCommand(int client)
{
const char *cmd = engine->Cmd_Argv(0);
if (strcmp(cmd, "sm_vmenuselect") == 0)
{
int key_press = atoi(engine->Cmd_Argv(1));
g_ValveMenuStyle.ClientPressedKey(client, key_press);
return true;
}
if (strcmp(cmd, "sm_test") == 0)
{
IBaseMenu *menu = g_ValveMenuStyle.CreateMenu();
menu->AppendItem("test1", ItemDrawInfo("Test #1", 0));
menu->AppendItem("test2", ItemDrawInfo("Test #2", 0));
menu->AppendItem("test3", ItemDrawInfo("Test #3", 0));
menu->AppendItem("test4", ItemDrawInfo("Test #4", 0));
menu->AppendItem("test5", ItemDrawInfo("Test #5", 0));
menu->AppendItem("test6", ItemDrawInfo("Test #6", 0));
menu->AppendItem("test7", ItemDrawInfo("Test #7", 0));
menu->AppendItem("test8", ItemDrawInfo("Test #8", 0));
menu->AppendItem("test9", ItemDrawInfo("Test #9", 0));
menu->AppendItem("test10", ItemDrawInfo("Test #10", 0));
menu->AppendItem("test11", ItemDrawInfo("Test #11", 0));
menu->AppendItem("test12", ItemDrawInfo("Test #12", 0));
menu->AppendItem("test13", ItemDrawInfo("Test #13", 0));
menu->AppendItem("test14", ItemDrawInfo("Test #14", 0));
menu->AppendItem("test15", ItemDrawInfo("Test #15", 0));
menu->AppendItem("test16", ItemDrawInfo("Test #16", 0));
menu->AppendItem("test17", ItemDrawInfo("Test #17", 0));
menu->AppendItem("test18", ItemDrawInfo("Test #18", 0));
menu->AppendItem("test19", ItemDrawInfo("Test #19", 0));
menu->AppendItem("test20", ItemDrawInfo("Test #20", 0));
menu->Display(client, new TestHandler, 20);
return true;
} else if (strcmp(cmd, "gaben") == 0) {
KeyValues *kv = new KeyValues("menu");
kv->SetString("msg", "hi");
serverpluginhelpers->CreateMessage(engine->PEntityOfEntIndex(client), DIALOG_MENU, kv, g_pVSPHandle);
kv->deleteThis();
}
return false;
}
void ValveMenuStyle::OnSourceModAllInitialized()
{
g_Players.AddClientListener(this);
SH_ADD_HOOK_MEMFUNC(IServerPluginHelpers, CreateMessage, serverpluginhelpers, this, &ValveMenuStyle::HookCreateMessage, false);
g_pSPHCC = SH_GET_CALLCLASS(serverpluginhelpers);
}
void ValveMenuStyle::OnSourceModShutdown()
{
SH_RELEASE_CALLCLASS(g_pSPHCC);
SH_REMOVE_HOOK_MEMFUNC(IServerPluginHelpers, CreateMessage, serverpluginhelpers, this, &ValveMenuStyle::HookCreateMessage, false);
g_Players.RemoveClientListener(this);
}
void ValveMenuStyle::OnClientDisconnected(int client)
{
CValveMenuPlayer *player = &m_players[client];
if (!player->bInMenu)
{
return;
}
menu_states_t &states = player->states;
states.mh->OnMenuCancel(states.menu, client, MenuCancel_Disconnect);
states.mh->OnMenuEnd(states.menu);
if (player->menuHoldTime)
{
m_WatchList.remove(client);
}
player->bInMenu = false;
}
void ValveMenuStyle::HookCreateMessage(edict_t *pEdict,
DIALOG_TYPE type,
KeyValues *kv,
IServerPluginCallbacks *plugin)
{
if (type != DIALOG_MENU)
{
return;
}
int client = engine->IndexOfEdict(pEdict);
if (client < 1 || client > 256)
{
return;
}
CValveMenuPlayer *player = &m_players[client];
/* We don't care if the player is in a menu because, for all intents and purposes,
* the menu is completely private. Instead, we just figure out the level we'll need
* in order to override it.
*/
player->curPrioLevel = kv->GetInt("level", player->curPrioLevel);
/* Oh no! What happens if we got a menu that overwrites ours?! */
if (player->bInMenu)
{
/* Okay, let the external menu survive for now. It may live another
* day to avenge its grandfather, killed in the great Menu Interruption
* battle.
*/
_CancelMenu(client, true);
}
}
void ValveMenuStyle::OnSourceModVSPReceived(IServerPluginCallbacks *iface)
{
g_pVSPHandle = iface;
}
IMenuDisplay *ValveMenuStyle::CreateDisplay()
{
return new CValveMenuDisplay();
}
IBaseMenu *ValveMenuStyle::CreateMenu()
{
return new CValveMenu();
}
const char *ValveMenuStyle::GetStyleName()
{
return "valve";
}
unsigned int ValveMenuStyle::GetMaxPageItems()
{
return 8;
}
static int do_lookup[256];
void ValveMenuStyle::ProcessWatchList()
{
if (!m_WatchList.size())
{
return;
}
FastLink<int>::iterator iter;
unsigned int total = 0;
for (iter=m_WatchList.begin(); iter!=m_WatchList.end(); ++iter)
{
do_lookup[total++] = (*iter);
}
int client;
CValveMenuPlayer *player;
float curTime = gpGlobals->curtime;
for (unsigned int i=0; i<total; i++)
{
client = do_lookup[i];
player = &m_players[client];
if (!player->bInMenu || !player->menuHoldTime)
{
m_WatchList.remove(i);
continue;
}
if (curTime > player->menuStartTime + player->menuHoldTime)
{
_CancelMenu(i, false);
}
}
}
void ValveMenuStyle::_CancelMenu(int client, bool bAutoIgnore)
{
CValveMenuPlayer *player = &m_players[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)
{
m_WatchList.remove(client);
}
/* Fire callbacks */
mh->OnMenuCancel(menu, client, MenuCancel_Interrupt);
mh->OnMenuEnd(menu);
if (bAutoIgnore)
{
player->bAutoIgnore = bOldIgnore;
}
}
void ValveMenuStyle::CancelMenu(CValveMenu *menu)
{
int maxClients = g_Players.GetMaxClients();
for (int i=1; i<=maxClients; i++)
{
if (m_players[i].bInMenu)
{
menu_states_t &states = m_players[i].states;
if (states.menu == menu)
{
_CancelMenu(i);
}
}
}
}
bool ValveMenuStyle::CancelClientMenu(int client, bool autoIgnore)
{
if (client < 1 || client > 256 || !m_players[client].bInMenu)
{
return false;
}
_CancelMenu(client, autoIgnore);
return true;
}
void ValveMenuStyle::ClientPressedKey(int client, unsigned int key_press)
{
CValveMenuPlayer *player = &m_players[client];
/* First question: Are we in a menu? */
if (!player->bInMenu)
{
return;
}
bool cancel = false;
unsigned int item = 0;
MenuCancelReason reason = MenuCancel_Exit;
menu_states_t &states = player->states;
assert(states.mh != NULL);
if (states.menu == NULL)
{
item = key_press;
} else if (key_press < 1 || key_press > 8) {
cancel = true;
} else {
ItemSelection type = states.slots[key_press].type;
/* For navigational items, we're going to redisplay */
if (type == ItemSel_Back)
{
if (!RedoClientMenu(client, ItemOrder_Descending))
{
cancel = true;
reason = MenuCancel_NoDisplay;
} else {
return;
}
} else if (type == ItemSel_Next) {
if (!RedoClientMenu(client, ItemOrder_Ascending))
{
cancel = true; /* I like Saltines. */
reason = MenuCancel_NoDisplay;
} else {
return;
}
} else if (type == ItemSel_Exit || type == ItemSel_None) {
cancel = true;
} else {
item = states.slots[key_press].item;
}
}
/* Save variables */
IMenuHandler *mh = states.mh;
IBaseMenu *menu = states.menu;
/* Clear states */
player->bInMenu = false;
if (player->menuHoldTime)
{
m_WatchList.remove(client);
}
if (cancel)
{
mh->OnMenuCancel(menu, client, reason);
} else {
mh->OnMenuSelect(menu, client, item);
}
mh->OnMenuEnd(menu);
}
bool ValveMenuStyle::DoClientMenu(int client, CValveMenuDisplay *menu, IMenuHandler *mh, unsigned int time)
{
if (!g_pVSPHandle || !mh)
{
return false;
}
CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame())
{
return false;
}
CValveMenuPlayer *player = &m_players[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)
{
/* We need to cancel the old menu */
if (player->menuHoldTime)
{
m_WatchList.remove(client);
}
states.mh->OnMenuCancel(states.menu, client, MenuCancel_Interrupt);
states.mh->OnMenuEnd(states.menu);
}
states.firstItem = 0;
states.lastItem = 0;
states.menu = NULL;
states.mh = mh;
states.apiVers = SMINTERFACE_MENUMANAGER_VERSION;
player->curPrioLevel--;
player->bInMenu = true;
player->menuStartTime = gpGlobals->curtime;
player->menuHoldTime = time;
if (time)
{
m_WatchList.push_back(client);
}
/* Draw the display */
menu->SendRawDisplay(client, player->curPrioLevel, time);
/* We can be interrupted again! */
player->bAutoIgnore = false;
return true;
}
bool ValveMenuStyle::DoClientMenu(int client, CValveMenu *menu, IMenuHandler *mh, unsigned int time)
{
mh->OnMenuStart(menu);
if (!g_pVSPHandle || !mh)
{
mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
mh->OnMenuEnd(menu);
return false;
}
CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame())
{
mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
mh->OnMenuEnd(menu);
return false;
}
CValveMenuPlayer *player = &m_players[client];
if (player->bAutoIgnore)
{
mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
mh->OnMenuEnd(menu);
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)
{
/* We need to cancel the old menu */
if (player->menuHoldTime)
{
m_WatchList.remove(client);
}
states.mh->OnMenuCancel(states.menu, client, MenuCancel_Interrupt);
states.mh->OnMenuEnd(states.menu);
}
states.firstItem = 0;
states.lastItem = 0;
states.menu = menu;
states.mh = mh;
states.apiVers = SMINTERFACE_MENUMANAGER_VERSION;
IMenuDisplay *display = g_Menus.RenderMenu(client, states, ItemOrder_Ascending);
if (!display)
{
player->bAutoIgnore = false;
player->bInMenu = false;
mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
mh->OnMenuEnd(menu);
return false;
}
/* Finally, set our states */
player->curPrioLevel--;
player->bInMenu = true;
player->menuStartTime = gpGlobals->curtime;
player->menuHoldTime = time;
if (time)
{
m_WatchList.push_back(client);
}
/* Draw the display */
CValveMenuDisplay *vDisplay = (CValveMenuDisplay *)display;
vDisplay->SendRawDisplay(client, player->curPrioLevel, time);
/* Free the display pointer */
delete display;
/* We can be interrupted again! */
player->bAutoIgnore = false;
return true;
}
bool ValveMenuStyle::RedoClientMenu(int client, ItemOrder order)
{
CValveMenuPlayer *player = &m_players[client];
menu_states_t &states = player->states;
player->bAutoIgnore = true;
IMenuDisplay *display = g_Menus.RenderMenu(client, states, order);
if (!display)
{
if (player->menuHoldTime)
{
m_WatchList.remove(client);
}
player->bAutoIgnore = false;
return false;
}
CValveMenuDisplay *vDisplay = (CValveMenuDisplay *)display;
vDisplay->SendRawDisplay(client, --player->curPrioLevel, player->menuHoldTime);
delete display;
player->bAutoIgnore = false;
return true;
}
MenuSource ValveMenuStyle::GetClientMenu(int client, void **object)
{
if (client < 1 || client > 256 || !m_players[client].bInMenu)
{
return MenuSource_None;
}
IBaseMenu *menu;
if ((menu=m_players[client].states.menu) != NULL)
{
if (object)
{
*object = menu;
}
return MenuSource_BaseMenu;
}
return MenuSource_Display;
}
CValveMenuDisplay::CValveMenuDisplay()
{
m_pKv = NULL;
Reset();
}
CValveMenuDisplay::CValveMenuDisplay(CValveMenu *pMenu)
{
m_pKv = NULL;
Reset();
m_pKv->SetColor("color", pMenu->m_IntroColor);
m_pKv->SetString("title", pMenu->m_IntroMsg);
}
CValveMenuDisplay::~CValveMenuDisplay()
{
m_pKv->deleteThis();
}
IMenuStyle *CValveMenuDisplay::GetParentStyle()
{
return &g_ValveMenuStyle;
}
void CValveMenuDisplay::Reset()
{
if (m_pKv)
{
m_pKv->deleteThis();
}
m_pKv = new KeyValues("menu");
m_NextPos = 1;
m_TitleDrawn = false;
}
bool CValveMenuDisplay::SetExtOption(MenuOption option, const void *valuePtr)
{
if (option == MenuOption_IntroMessage)
{
m_pKv->SetString("title", (const char *)valuePtr);
return true;
} else if (option == MenuOption_IntroColor) {
int *array = (int *)valuePtr;
m_pKv->SetColor("color", Color(array[0], array[1], array[2], array[3]));
return true;
}
return false;
}
bool CValveMenuDisplay::CanDrawItem(unsigned int drawFlags)
{
/**
* ITEMDRAW_RAWLINE - We can't draw random text, and this doesn't add a slot,
* so it's completely safe to ignore it.
* -----------------------------------------
*/
if (drawFlags & ITEMDRAW_RAWLINE)
{
return false;
}
/**
* Special cases, explained in DrawItem()
*/
if ((drawFlags & ITEMDRAW_NOTEXT)
|| (drawFlags & ITEMDRAW_SPACER))
{
return true;
}
/**
* We can't draw disabled text. We could bump the position, but we
* want DirectDraw() to find some actual items to display.
*/
if (drawFlags & ITEMDRAW_DISABLED)
{
return false;
}
return true;
}
unsigned int CValveMenuDisplay::DrawItem(const ItemDrawInfo &item)
{
if (m_NextPos > 9 || !CanDrawItem(item.style))
{
return 0;
}
/**
* For these cases we can't draw anything at all, but
* we can at least bump the position since we were explicitly asked to.
*/
if ((item.style & ITEMDRAW_NOTEXT)
|| (item.style & ITEMDRAW_SPACER))
{
return m_NextPos++;
}
char buffer[255];
UTIL_Format(buffer, sizeof(buffer), "%d. %s", m_NextPos, item.display);
KeyValues *ki = m_pKv->FindKey(g_OptionNumTable[m_NextPos], true);
ki->SetString("command", g_OptionCmdTable[m_NextPos]);
ki->SetString("msg", buffer);
return m_NextPos++;
}
void CValveMenuDisplay::DrawTitle(const char *text, bool onlyIfEmpty/* =false */)
{
if (onlyIfEmpty && m_TitleDrawn)
{
return;
}
m_pKv->SetString("msg", text);
m_TitleDrawn = true;
}
bool CValveMenuDisplay::DrawRawLine(const char *rawline)
{
return false;
}
void CValveMenuDisplay::SendRawDisplay(int client, int priority, unsigned int time)
{
m_pKv->SetInt("level", priority);
m_pKv->SetInt("time", time);
SH_CALL(g_pSPHCC, &IServerPluginHelpers::CreateMessage)(
engine->PEntityOfEntIndex(client),
DIALOG_MENU,
m_pKv,
g_pVSPHandle);
}
bool CValveMenuDisplay::SendDisplay(int client, IMenuHandler *handler, unsigned int time)
{
return g_ValveMenuStyle.DoClientMenu(client, this, handler, time);
}
CValveMenu::CValveMenu() : CBaseMenu(&g_ValveMenuStyle),
m_IntroColor(255, 0, 0, 255), m_bShouldDelete(false), m_bCancelling(false)
{
strcpy(m_IntroMsg, "You have a menu, press ESC");
m_Pagination = 5;
}
void CValveMenu::Cancel()
{
if (m_bCancelling)
{
return;
}
m_bCancelling = true;
g_ValveMenuStyle.CancelMenu(this);
m_bCancelling = false;
if (m_bShouldDelete)
{
delete this;
}
}
void CValveMenu::Destroy()
{
if (!m_bCancelling || m_bShouldDelete)
{
Cancel();
delete this;
} else {
m_bShouldDelete = true;
}
}
bool CValveMenu::SetPagination(unsigned int itemsPerPage)
{
if (itemsPerPage < 1 || itemsPerPage > 5)
{
return false;
}
CBaseMenu::SetPagination(itemsPerPage);
return true;
}
bool CValveMenu::SetExtOption(MenuOption option, const void *valuePtr)
{
if (option == MenuOption_IntroMessage)
{
strncopy(m_IntroMsg, (const char *)valuePtr, sizeof(m_IntroMsg));
return true;
} else if (option == MenuOption_IntroColor) {
unsigned int *array = (unsigned int *)valuePtr;
m_IntroColor = Color(array[0], array[1], array[2], array[3]);
return true;
}
return false;
}
bool CValveMenu::Display(int client, IMenuHandler *handler, unsigned int time)
{
if (m_bCancelling)
{
return false;
}
return g_ValveMenuStyle.DoClientMenu(client, this, handler, time);
}
IMenuDisplay *CValveMenu::CreateDisplay()
{
return new CValveMenuDisplay(this);
}
bool CValveMenu::GetExitButton()
{
return true;
}
bool CValveMenu::SetExitButton(bool set)
{
return false;
}
static const char *g_OptionNumTable[11] =
{
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"
};
static const char *g_OptionCmdTable[11] =
{
"sm_vmenuselect 0", /* INVALID! */
"sm_vmenuselect 1",
"sm_vmenuselect 2",
"sm_vmenuselect 3",
"sm_vmenuselect 4",
"sm_vmenuselect 5",
"sm_vmenuselect 6",
"sm_vmenuselect 7",
"sm_vmenuselect 8",
"sm_vmenuselect 9",
"sm_vmenuselect 10"
};