dvander@1269: /** dvander@1269: * vim: set ts=4 : ds@1330: * ============================================================================= dvander@1269: * SourceMod ds@1947: * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. ds@1330: * ============================================================================= dvander@1269: * ds@1330: * This program is free software; you can redistribute it and/or modify it under ds@1330: * the terms of the GNU General Public License, version 3.0, as published by the ds@1330: * Free Software Foundation. dvander@1269: * ds@1330: * This program is distributed in the hope that it will be useful, but WITHOUT ds@1330: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ds@1330: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ds@1330: * details. dvander@1269: * ds@1330: * You should have received a copy of the GNU General Public License along with ds@1330: * this program. If not, see . dvander@1269: * ds@1330: * As a special exception, AlliedModders LLC gives you permission to link the ds@1330: * code of this program (as well as its derivative works) to "Half-Life 2," the ds@1330: * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software ds@1330: * by the Valve Corporation. You must obey the GNU General Public License in ds@1330: * all respects for all other code used. Additionally, AlliedModders LLC grants ds@1330: * this exception to all derivative works. AlliedModders LLC defines further ds@1330: * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), ds@1330: * or . dvander@1269: * dvander@1269: * Version: $Id$ dvander@1269: */ dvander@1269: dvander@1269: #include dvander@1269: #include dvander@1269: #include "MenuVoting.h" dvander@1269: #include "PlayerManager.h" dvander@1269: #include "sourcemm_api.h" dvander@1269: dvander@1291: float g_next_vote = 0.0f; dvander@1291: ds@2400: #if SOURCE_ENGINE >= SE_ORANGEBOX faluco@1541: void OnVoteDelayChange(IConVar *cvar, const char *value, float flOldValue); faluco@1541: #else dvander@1291: void OnVoteDelayChange(ConVar *cvar, const char *value); faluco@1541: #endif dvander@1291: ConVar sm_vote_delay("sm_vote_delay", dvander@1291: "30", dvander@1291: 0, dvander@1291: "Sets the recommended time in between public votes", dvander@1291: false, dvander@1291: 0.0, dvander@1291: false, dvander@1291: 0.0, dvander@1291: OnVoteDelayChange); dvander@1291: ds@2400: #if SOURCE_ENGINE >= SE_ORANGEBOX faluco@1541: void OnVoteDelayChange(IConVar *cvar, const char *value, float flOldValue) faluco@1541: #else dvander@1291: void OnVoteDelayChange(ConVar *cvar, const char *value) faluco@1541: #endif dvander@1291: { dvander@1291: /* See if the new vote delay isn't something we need to account for */ dvander@1291: if (sm_vote_delay.GetFloat() < 1.0f) dvander@1291: { dvander@1291: g_next_vote = 0.0f; dvander@1291: return; dvander@1291: } dvander@1291: dvander@1291: /* If there was never a last vote, ignore this change */ dvander@1291: if (g_next_vote < 0.1f) dvander@1291: { dvander@1291: return; dvander@1291: } dvander@1291: dvander@1291: /* Subtract the original value, then add the new one. */ dvander@1291: g_next_vote -= (float)atof(value); dvander@1291: g_next_vote += sm_vote_delay.GetFloat(); dvander@1291: } dvander@1291: dvander@1291: unsigned int VoteMenuHandler::GetRemainingVoteDelay() dvander@1291: { dvander@1291: if (g_next_vote <= gpGlobals->curtime) dvander@1291: { dvander@1291: return 0; dvander@1291: } dvander@1291: dvander@1291: return (unsigned int)(g_next_vote - gpGlobals->curtime); dvander@1291: } dvander@1291: dvander@1269: void VoteMenuHandler::OnSourceModAllInitialized() dvander@1269: { dvander@1269: g_Players.AddClientListener(this); dvander@1269: } dvander@1269: dvander@1269: void VoteMenuHandler::OnSourceModShutdown() dvander@1269: { dvander@1269: g_Players.RemoveClientListener(this); dvander@1269: } dvander@1269: dvander@1291: void VoteMenuHandler::OnSourceModLevelChange(const char *mapName) dvander@1291: { dvander@1291: g_next_vote = 0.0f; dvander@1291: } dvander@1291: dvander@1269: unsigned int VoteMenuHandler::GetMenuAPIVersion2() dvander@1269: { dvander@1269: return m_pHandler->GetMenuAPIVersion2(); dvander@1269: } dvander@1269: dvander@1269: void VoteMenuHandler::OnClientDisconnected(int client) dvander@1269: { dvander@1269: if (!IsVoteInProgress()) dvander@1269: { dvander@1269: return; dvander@1269: } dvander@1269: dvander@2056: /* Wipe out their vote if they had one. We have to make sure the the the dvander@2056: * newly connected client is not allowed to vote. dvander@2056: */ dvander@1269: int item; dvander@2056: if ((item = m_ClientVotes[client]) >= -1) dvander@1269: { dvander@2056: if (item >= 0) dvander@2056: { dvander@2056: assert((unsigned)item < m_Items); dvander@2056: assert(m_Votes[item] > 0); dvander@2056: m_Votes[item]--; dvander@2056: } dvander@2056: m_ClientVotes[client] = -2; dvander@1269: } dvander@1269: } dvander@1269: dvander@1269: bool VoteMenuHandler::IsVoteInProgress() dvander@1269: { dvander@1269: return (m_pCurMenu != NULL); dvander@1269: } dvander@1269: dvander@2056: bool VoteMenuHandler::StartVote(IBaseMenu *menu, dvander@2056: unsigned int num_clients, dvander@2056: int clients[], dvander@2056: unsigned int max_time, dvander@2056: unsigned int flags/* =0 */) dvander@1269: { dvander@1269: if (!InitializeVoting(menu, menu->GetHandler(), max_time, flags)) dvander@1269: { dvander@1269: return false; dvander@1269: } dvander@1269: dvander@2056: /* Note: we can use game time and not universal time because dvander@2056: * if we're voting then players are in-game. dvander@2056: */ dvander@2056: dvander@1291: float fVoteDelay = sm_vote_delay.GetFloat(); dvander@1291: if (fVoteDelay < 1.0) dvander@1291: { dvander@1291: g_next_vote = 0.0; dvander@2056: } dvander@2056: else dvander@2056: { dvander@1291: /* This little trick breaks for infinite votes! dvander@1291: * However, we just ignore that since those 1) shouldn't exist and dvander@1291: * 2) people must be checking IsVoteInProgress() beforehand anyway. dvander@1291: */ dvander@1291: g_next_vote = gpGlobals->curtime + fVoteDelay + (float)max_time; dvander@1291: } dvander@1291: dvander@2056: m_fStartTime = gpGlobals->curtime; dvander@2056: m_nMenuTime = max_time; dvander@2056: dvander@1269: for (unsigned int i=0; i 256) dvander@2056: { dvander@2056: continue; dvander@2056: } dvander@1269: menu->Display(clients[i], max_time, this); dvander@1269: } dvander@1269: dvander@1269: StartVoting(); dvander@1269: dvander@1269: return true; dvander@1269: } dvander@1269: dvander@2056: bool VoteMenuHandler::IsClientInVotePool(int client) dvander@2056: { dvander@2056: if (client < 1 dvander@2056: || client > g_Players.MaxClients() dvander@2056: || m_pCurMenu == NULL) dvander@2056: { dvander@2056: return false; dvander@2056: } dvander@2056: dvander@2056: return (m_ClientVotes[client] > -2); dvander@2056: } dvander@2056: dvander@2056: bool VoteMenuHandler::GetClientVoteChoice(int client, unsigned int *pItem) dvander@2056: { dvander@2056: if (!IsClientInVotePool(client) dvander@2056: || m_ClientVotes[client] == -1) dvander@2056: { dvander@2056: return false; dvander@2056: } dvander@2056: dvander@2056: *pItem = m_ClientVotes[client]; dvander@2056: dvander@2056: return true; dvander@2056: } dvander@2056: dvander@2056: bool VoteMenuHandler::RedrawToClient(int client) dvander@2056: { dvander@2056: unsigned int time_limit; dvander@2056: dvander@2056: if (!IsClientInVotePool(client)) dvander@2056: { dvander@2056: return false; dvander@2056: } dvander@2056: dvander@2056: if (m_nMenuTime == MENU_TIME_FOREVER) dvander@2056: { dvander@2056: time_limit = m_nMenuTime; dvander@2056: } dvander@2056: else dvander@2056: { dvander@2056: time_limit = (int)((float)m_nMenuTime - (gpGlobals->curtime - m_fStartTime)); dvander@2056: dvander@2056: /* Make sure this doesn't round to zero */ dvander@2056: if (time_limit == MENU_TIME_FOREVER) dvander@2056: { dvander@2056: time_limit = 1; dvander@2056: } dvander@2056: } dvander@2056: dvander@2056: return m_pCurMenu->Display(client, time_limit, this); dvander@2056: } dvander@2056: dvander@1269: bool VoteMenuHandler::InitializeVoting(IBaseMenu *menu, dvander@1269: IMenuHandler *handler, dvander@1269: unsigned int time, dvander@1269: unsigned int flags) dvander@1269: { dvander@1269: if (IsVoteInProgress()) dvander@1269: { dvander@1269: return false; dvander@1269: } dvander@1269: dvander@1269: InternalReset(); dvander@1269: dvander@1269: /* Mark all clients as not voting */ dvander@1269: for (int i=1; i<=gpGlobals->maxClients; i++) dvander@1269: { dvander@1269: m_ClientVotes[i] = -2; dvander@1269: } dvander@1269: dvander@1269: m_Items = menu->GetItemCount(); dvander@1269: dvander@1269: if (m_Votes.size() < (size_t)m_Items) dvander@1269: { dvander@1269: /* Only clear the items we need to... */ dvander@1269: size_t size = m_Votes.size(); dvander@1269: for (size_t i=0; iOnMenuStart(m_pCurMenu); dvander@1269: dvander@1269: return true; dvander@1269: } dvander@1269: dvander@1269: void VoteMenuHandler::StartVoting() dvander@1269: { dvander@1269: if (!m_pCurMenu) dvander@1269: { dvander@1269: return; dvander@1269: } dvander@1269: dvander@1269: m_bStarted = true; dvander@1269: dvander@1269: m_pHandler->OnMenuVoteStart(m_pCurMenu); dvander@1269: dvander@1269: /* By now we know how many clients were set. dvander@1269: * If there are none, we should end IMMEDIATELY. dvander@1269: */ dvander@1269: if (m_Clients == 0) dvander@1269: { dvander@1269: EndVoting(); dvander@1269: } dvander@1269: } dvander@1269: dvander@1269: void VoteMenuHandler::DecrementPlayerCount() dvander@1269: { dvander@1269: assert(m_Clients > 0); dvander@1269: dvander@1269: m_Clients--; dvander@1269: dvander@1269: if (m_bStarted && m_Clients == 0) dvander@1269: { dvander@1269: EndVoting(); dvander@1269: } dvander@1269: } dvander@1269: dvander@1269: int SortVoteItems(const void *item1, const void *item2) dvander@1269: { dvander@1269: return ((menu_vote_result_t::menu_item_vote_t *)item2)->count dvander@1269: - ((menu_vote_result_t::menu_item_vote_t *)item1)->count; dvander@1269: } dvander@1269: dvander@1269: void VoteMenuHandler::EndVoting() dvander@1269: { dvander@1291: /* Set when the next delay ends. We ignore cancellation because a menu dvander@1291: * was, at one point, displayed, which is all that counts. However, we dvander@1291: * do re-calculate the time just in case the menu had no time limit. dvander@1291: */ dvander@1291: float fVoteDelay = sm_vote_delay.GetFloat(); dvander@1291: if (fVoteDelay < 1.0) dvander@1291: { dvander@1291: g_next_vote = 0.0; dvander@2056: } dvander@2056: else dvander@2056: { dvander@1291: g_next_vote = gpGlobals->curtime + fVoteDelay; dvander@1291: } dvander@1291: dvander@1269: if (m_bCancelled) dvander@1269: { dvander@1269: /* If we were cancelled, don't bother tabulating anything. dvander@1269: * Reset just in case someone tries to redraw, which means dvander@1269: * we need to save our states. dvander@1269: */ dvander@1269: IBaseMenu *menu = m_pCurMenu; dvander@1269: IMenuHandler *handler = m_pHandler; dvander@1269: InternalReset(); dvander@1296: handler->OnMenuVoteCancel(menu, VoteCancel_Generic); dvander@1269: handler->OnMenuEnd(menu, MenuEnd_VotingCancelled); dvander@1269: return; dvander@1269: } dvander@1269: dvander@1269: menu_vote_result_t vote; dvander@1269: menu_vote_result_t::menu_client_vote_t client_vote[256]; dvander@1269: menu_vote_result_t::menu_item_vote_t item_vote[256]; dvander@1269: dvander@1269: memset(&vote, 0, sizeof(vote)); dvander@1269: dvander@1269: /* Build the item list */ dvander@1269: for (unsigned int i=0; i 0) dvander@1269: { dvander@1269: item_vote[vote.num_items].count = m_Votes[i]; dvander@1269: item_vote[vote.num_items].item = i; dvander@1269: vote.num_votes += m_Votes[i]; dvander@1269: vote.num_items++; dvander@1269: } dvander@1269: } dvander@1269: vote.item_list = item_vote; dvander@1269: dvander@1269: if (!vote.num_votes) dvander@1269: { dvander@1269: IBaseMenu *menu = m_pCurMenu; dvander@1269: IMenuHandler *handler = m_pHandler; dvander@1269: InternalReset(); dvander@1296: handler->OnMenuVoteCancel(menu, VoteCancel_NoVotes); dvander@1296: handler->OnMenuEnd(menu, MenuEnd_VotingCancelled); dvander@1269: return; dvander@1269: } dvander@1269: dvander@1269: /* Build the client list */ dvander@1269: for (int i=1; i<=gpGlobals->maxClients; i++) dvander@1269: { dvander@1269: if (m_ClientVotes[i] >= -1) dvander@1269: { dvander@1269: client_vote[vote.num_clients].client = i; dvander@1269: client_vote[vote.num_clients].item = m_ClientVotes[i]; dvander@1269: vote.num_clients++; dvander@1269: } dvander@1269: } dvander@1269: vote.client_list = client_vote; dvander@1269: dvander@1269: /* Sort the item list descending like we promised */ dvander@1269: qsort(item_vote, dvander@1269: vote.num_items, dvander@1269: sizeof(menu_vote_result_t::menu_item_vote_t), dvander@1269: SortVoteItems); dvander@1269: dvander@1269: /* Save states, then clear what we've saved. dvander@1269: * This makes us re-entrant, which is always the safe way to go. dvander@1269: */ dvander@1269: IBaseMenu *menu = m_pCurMenu; dvander@1269: IMenuHandler *handler = m_pHandler; dvander@1269: InternalReset(); dvander@1269: dvander@1269: /* Send vote info */ dvander@1269: handler->OnMenuVoteResults(menu, &vote); dvander@1269: handler->OnMenuEnd(menu, MenuEnd_VotingDone); dvander@1269: } dvander@1269: dvander@1269: void VoteMenuHandler::OnMenuStart(IBaseMenu *menu) dvander@1269: { dvander@1269: m_Clients++; dvander@1269: } dvander@1269: dvander@1269: void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason) dvander@1269: { dvander@1269: DecrementPlayerCount(); dvander@1269: } dvander@1269: dvander@1269: void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) dvander@1269: { dvander@1269: m_pHandler->OnMenuCancel(menu, client, reason); dvander@1269: } dvander@1269: dvander@1269: void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display) dvander@1269: { dvander@1269: m_ClientVotes[client] = -1; dvander@1269: m_pHandler->OnMenuDisplay(menu, client, display); dvander@1269: } dvander@1269: dvander@1269: unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr) dvander@1269: { dvander@1269: return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr); dvander@1269: } dvander@1269: dvander@1269: void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style) dvander@1269: { dvander@1269: m_pHandler->OnMenuDrawItem(menu, client, item, style); dvander@1269: } dvander@1269: dvander@1269: void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item) dvander@1269: { dvander@1269: /* Check by our item count, NOT the vote array size */ dvander@1269: if (item < m_Items) dvander@1269: { dvander@1269: m_ClientVotes[client] = item; dvander@1269: m_Votes[item]++; dvander@1269: m_NumVotes++; dvander@1269: } dvander@1269: dvander@1269: m_pHandler->OnMenuSelect(menu, client, item); dvander@1269: } dvander@1269: dvander@1474: void VoteMenuHandler::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page) dvander@1474: { dvander@1474: if (m_pHandler->GetMenuAPIVersion2() >= 13) dvander@1474: { dvander@1474: m_pHandler->OnMenuSelect2(menu, client, item, item_on_page); dvander@1474: } dvander@1474: } dvander@1474: dvander@1269: void VoteMenuHandler::InternalReset() dvander@1269: { dvander@1269: m_Clients = 0; dvander@1269: m_Items = 0; dvander@1269: m_bStarted = false; dvander@1269: m_pCurMenu = NULL; dvander@1269: m_NumVotes = 0; dvander@1269: m_bCancelled = false; dvander@1269: m_pHandler = NULL; dvander@1269: } dvander@1269: dvander@1269: void VoteMenuHandler::CancelVoting() dvander@1269: { dvander@1269: if (m_bCancelled || !m_pCurMenu) dvander@1269: { dvander@1269: return; dvander@1269: } dvander@1269: m_bCancelled = true; dvander@1269: m_pCurMenu->Cancel(); dvander@1269: } dvander@1269: dvander@1269: IBaseMenu *VoteMenuHandler::GetCurrentMenu() dvander@1269: { dvander@1269: return m_pCurMenu; dvander@1269: } dvander@1269: dvander@1269: bool VoteMenuHandler::IsCancelling() dvander@1269: { dvander@1269: return m_bCancelled; dvander@1269: }