/** * vim: set ts=4 sw=4 : * ============================================================================= * SourceMod * Copyright (C) 2004-2009 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 #include "MenuVoting.h" #include "PlayerManager.h" #include "sourcemm_api.h" #include #include #include #include #include #include #include "logic_bridge.h" float g_next_vote = 0.0f; #define VOTE_NOT_VOTING -2 #define VOTE_PENDING -1 ConVar sm_vote_hintbox("sm_vote_progress_hintbox", "0", 0, "Show current vote progress in a hint box", true, 0.0, true, 1.0); ConVar sm_vote_chat("sm_vote_progress_chat", "0", 0, "Show current vote progress as chat messages", true, 0.0, true, 1.0); ConVar sm_vote_console("sm_vote_progress_console", "0", 0, "Show current vote progress as console messages", true, 0.0, true, 1.0); ConVar sm_vote_client_console("sm_vote_progress_client_console", "0", 0, "Show current vote progress as console messages to clients", true, 0.0, true, 1.0); #if SOURCE_ENGINE >= SE_ORANGEBOX void OnVoteDelayChange(IConVar *cvar, const char *value, float flOldValue); #else void OnVoteDelayChange(ConVar *cvar, const char *value); #endif ConVar sm_vote_delay("sm_vote_delay", "30", 0, "Sets the recommended time in between public votes", false, 0.0, false, 0.0, OnVoteDelayChange); #if SOURCE_ENGINE >= SE_ORANGEBOX void OnVoteDelayChange(IConVar *cvar, const char *value, float flOldValue) #else void OnVoteDelayChange(ConVar *cvar, const char *value) #endif { /* See if the new vote delay isn't something we need to account for */ if (sm_vote_delay.GetFloat() < 1.0f) { g_next_vote = 0.0f; return; } /* If there was never a last vote, ignore this change */ if (g_next_vote < 0.1f) { return; } /* Subtract the original value, then add the new one. */ g_next_vote -= (float)atof(value); g_next_vote += sm_vote_delay.GetFloat(); } unsigned int VoteMenuHandler::GetRemainingVoteDelay() { if (g_next_vote <= gpGlobals->curtime) { return 0; } return (unsigned int)(g_next_vote - gpGlobals->curtime); } void VoteMenuHandler::OnSourceModAllInitialized() { g_Players.AddClientListener(this); } void VoteMenuHandler::OnSourceModShutdown() { g_Players.RemoveClientListener(this); } void VoteMenuHandler::OnSourceModLevelChange(const char *mapName) { g_next_vote = 0.0f; } unsigned int VoteMenuHandler::GetMenuAPIVersion2() { return m_pHandler->GetMenuAPIVersion2(); } void VoteMenuHandler::OnClientDisconnected(int client) { if (!IsVoteInProgress()) { return; } /* Wipe out their vote if they had one. We have to make sure the the the * newly connected client is not allowed to vote. */ int item; if ((item = m_ClientVotes[client]) >= VOTE_PENDING) { if (item >= 0) { assert((unsigned)item < m_Items); assert(m_Votes[item] > 0); m_Votes[item]--; } m_ClientVotes[client] = VOTE_NOT_VOTING; } } bool VoteMenuHandler::IsVoteInProgress() { return (m_pCurMenu != NULL); } bool VoteMenuHandler::StartVote(IBaseMenu *menu, unsigned int num_clients, int clients[], unsigned int max_time, unsigned int flags/* =0 */) { if (!InitializeVoting(menu, menu->GetHandler(), max_time, flags)) { return false; } /* Note: we can use game time and not universal time because * if we're voting then players are in-game. */ float fVoteDelay = sm_vote_delay.GetFloat(); if (fVoteDelay < 1.0) { g_next_vote = 0.0; } else { /* This little trick breaks for infinite votes! * However, we just ignore that since those 1) shouldn't exist and * 2) people must be checking IsVoteInProgress() beforehand anyway. */ g_next_vote = gpGlobals->curtime + fVoteDelay + (float)max_time; } m_fStartTime = gpGlobals->curtime; m_nMenuTime = max_time; for (unsigned int i=0; i 256) { continue; } menu->Display(clients[i], max_time, this); } StartVoting(); return true; } bool VoteMenuHandler::IsClientInVotePool(int client) { if (client < 1 || client > g_Players.MaxClients() || m_pCurMenu == NULL) { return false; } return (m_ClientVotes[client] > VOTE_NOT_VOTING); } bool VoteMenuHandler::GetClientVoteChoice(int client, unsigned int *pItem) { if (!IsClientInVotePool(client) || m_ClientVotes[client] == VOTE_PENDING) { return false; } *pItem = m_ClientVotes[client]; return true; } bool VoteMenuHandler::RedrawToClient(int client, bool revotes) { unsigned int time_limit; if (!IsClientInVotePool(client)) { return false; } if (m_ClientVotes[client] >= 0) { if ((m_VoteFlags & VOTEFLAG_NO_REVOTES) == VOTEFLAG_NO_REVOTES || !revotes) { return false; } assert((unsigned)m_ClientVotes[client] < m_Items); assert(m_Votes[m_ClientVotes[client]] > 0); m_Votes[m_ClientVotes[client]]--; m_ClientVotes[client] = VOTE_PENDING; m_Revoting[client] = true; m_NumVotes--; } if (m_nMenuTime == MENU_TIME_FOREVER) { time_limit = m_nMenuTime; } else { time_limit = (int)((float)m_nMenuTime - (gpGlobals->curtime - m_fStartTime)); /* Make sure this doesn't round to zero */ if (time_limit == MENU_TIME_FOREVER) { time_limit = 1; } } return m_pCurMenu->Display(client, time_limit, this); } bool VoteMenuHandler::InitializeVoting(IBaseMenu *menu, IMenuHandler *handler, unsigned int time, unsigned int flags) { if (IsVoteInProgress()) { return false; } InternalReset(); /* Mark all clients as not voting */ for (int i=1; i<=gpGlobals->maxClients; i++) { m_ClientVotes[i] = VOTE_NOT_VOTING; m_Revoting[i] = false; } m_Items = menu->GetItemCount(); if (m_Votes.size() < (size_t)m_Items) { /* Only clear the items we need to... */ size_t size = m_Votes.size(); for (size_t i=0; iOnMenuStart(m_pCurMenu); return true; } void VoteMenuHandler::StartVoting() { if (!m_pCurMenu) { return; } m_bStarted = true; m_pHandler->OnMenuVoteStart(m_pCurMenu); m_displayTimer = g_Timers.CreateTimer(this, 1.0, NULL, TIMER_FLAG_REPEAT|TIMER_FLAG_NO_MAPCHANGE); /* By now we know how many clients were set. * If there are none, we should end IMMEDIATELY. */ if (m_Clients == 0) { EndVoting(); } m_TotalClients = m_Clients; } void VoteMenuHandler::DecrementPlayerCount() { assert(m_Clients > 0); m_Clients--; if (m_bStarted && m_Clients == 0) { EndVoting(); } } int SortVoteItems(const void *item1, const void *item2) { return ((menu_vote_result_t::menu_item_vote_t *)item2)->count - ((menu_vote_result_t::menu_item_vote_t *)item1)->count; } void VoteMenuHandler::EndVoting() { /* Set when the next delay ends. We ignore cancellation because a menu * was, at one point, displayed, which is all that counts. However, we * do re-calculate the time just in case the menu had no time limit. */ float fVoteDelay = sm_vote_delay.GetFloat(); if (fVoteDelay < 1.0) { g_next_vote = 0.0; } else { g_next_vote = gpGlobals->curtime + fVoteDelay; } if (m_displayTimer) { g_Timers.KillTimer(m_displayTimer); } if (m_bCancelled) { /* If we were cancelled, don't bother tabulating anything. * Reset just in case someone tries to redraw, which means * we need to save our states. */ IBaseMenu *menu = m_pCurMenu; IMenuHandler *handler = m_pHandler; InternalReset(); handler->OnMenuVoteCancel(menu, VoteCancel_Generic); handler->OnMenuEnd(menu, MenuEnd_VotingCancelled); return; } menu_vote_result_t vote; menu_vote_result_t::menu_client_vote_t client_vote[256]; menu_vote_result_t::menu_item_vote_t item_vote[256]; memset(&vote, 0, sizeof(vote)); /* Build the item list */ for (unsigned int i=0; i 0) { item_vote[vote.num_items].count = m_Votes[i]; item_vote[vote.num_items].item = i; vote.num_votes += m_Votes[i]; vote.num_items++; } } vote.item_list = item_vote; if (!vote.num_votes) { IBaseMenu *menu = m_pCurMenu; IMenuHandler *handler = m_pHandler; InternalReset(); handler->OnMenuVoteCancel(menu, VoteCancel_NoVotes); handler->OnMenuEnd(menu, MenuEnd_VotingCancelled); return; } /* Build the client list */ for (int i=1; i<=gpGlobals->maxClients; i++) { if (m_ClientVotes[i] >= VOTE_PENDING) { client_vote[vote.num_clients].client = i; client_vote[vote.num_clients].item = m_ClientVotes[i]; vote.num_clients++; } } vote.client_list = client_vote; /* Sort the item list descending like we promised */ qsort(item_vote, vote.num_items, sizeof(menu_vote_result_t::menu_item_vote_t), SortVoteItems); /* Save states, then clear what we've saved. * This makes us re-entrant, which is always the safe way to go. */ IBaseMenu *menu = m_pCurMenu; IMenuHandler *handler = m_pHandler; InternalReset(); /* Send vote info */ handler->OnMenuVoteResults(menu, &vote); handler->OnMenuEnd(menu, MenuEnd_VotingDone); } void VoteMenuHandler::OnMenuStart(IBaseMenu *menu) { m_Clients++; } void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason) { DecrementPlayerCount(); } void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) { m_pHandler->OnMenuCancel(menu, client, reason); } void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display) { m_ClientVotes[client] = VOTE_PENDING; m_pHandler->OnMenuDisplay(menu, client, display); } unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr) { return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr); } void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style) { m_pHandler->OnMenuDrawItem(menu, client, item, style); } void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item) { /* Check by our item count, NOT the vote array size */ if (item < m_Items) { unsigned int index = menu->GetRealItemIndex(client, item); m_ClientVotes[client] = index; m_Votes[index]++; m_NumVotes++; if (sm_vote_chat.GetBool() || sm_vote_console.GetBool() || sm_vote_client_console.GetBool()) { static char buffer[1024]; ItemDrawInfo dr; menu->GetItemInfo(item, &dr, client); if (sm_vote_console.GetBool()) { int target = SOURCEMOD_SERVER_LANGUAGE; logicore.CoreTranslate(buffer, sizeof(buffer), "[SM] %T", 4, NULL, "Voted For", &target, g_Players.GetPlayerByIndex(client)->GetName(), dr.display); Engine_LogPrintWrapper(buffer); } if (sm_vote_chat.GetBool() || sm_vote_client_console.GetBool()) { int maxclients = g_Players.GetMaxClients(); for (int i=1; i<=maxclients; i++) { CPlayer *pPlayer = g_Players.GetPlayerByIndex(i); assert(pPlayer); if (pPlayer->IsInGame() && !pPlayer->IsFakeClient()) { if (m_Revoting[client]) { logicore.CoreTranslate(buffer, sizeof(buffer), "[SM] %T", 4, NULL, "Changed Vote", &i, g_Players.GetPlayerByIndex(client)->GetName(), dr.display); } else { logicore.CoreTranslate(buffer, sizeof(buffer), "[SM] %T", 4, NULL, "Voted For", &i, g_Players.GetPlayerByIndex(client)->GetName(), dr.display); } if (sm_vote_chat.GetBool()) { g_HL2.TextMsg(i, HUD_PRINTTALK, buffer); } if (sm_vote_client_console.GetBool()) { ClientConsolePrint(pPlayer->GetEdict(), buffer); } } } } } BuildVoteLeaders(); DrawHintProgress(); } m_pHandler->OnMenuSelect(menu, client, item); } void VoteMenuHandler::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page) { if (m_pHandler->GetMenuAPIVersion2() >= 13) { m_pHandler->OnMenuSelect2(menu, client, item, item_on_page); } } void VoteMenuHandler::InternalReset() { m_Clients = 0; m_Items = 0; m_bStarted = false; m_pCurMenu = NULL; m_NumVotes = 0; m_bCancelled = false; m_pHandler = NULL; m_leaderList[0] = '\0'; m_displayTimer = NULL; m_TotalClients = 0; } void VoteMenuHandler::CancelVoting() { if (m_bCancelled || !m_pCurMenu) { return; } m_bCancelled = true; m_pCurMenu->Cancel(); } IBaseMenu *VoteMenuHandler::GetCurrentMenu() { return m_pCurMenu; } bool VoteMenuHandler::IsCancelling() { return m_bCancelled; } void VoteMenuHandler::DrawHintProgress() { if (!sm_vote_hintbox.GetBool()) { return; } static char buffer[1024]; float timeRemaining = (m_fStartTime + m_nMenuTime) - gpGlobals->curtime; if (timeRemaining < 0) { timeRemaining = 0.0; } int iTimeRemaining = RoundFloatToInt(timeRemaining); int maxclients = g_Players.GetMaxClients(); for (int i=1; i<=maxclients; i++) { if (g_Players.GetPlayerByIndex(i)->IsInGame()) { logicore.CoreTranslate(buffer, sizeof(buffer), "%T%s", 6, NULL, "Vote Count", &i, &m_NumVotes, &m_TotalClients, &iTimeRemaining, &m_leaderList); g_HL2.HintTextMsg(i, buffer); } } } void VoteMenuHandler::BuildVoteLeaders() { if (m_NumVotes == 0 || !sm_vote_hintbox.GetBool()) { return; } menu_vote_result_t vote; menu_vote_result_t::menu_item_vote_t item_vote[256]; memset(&vote, 0, sizeof(vote)); /* Build the item list */ for (unsigned int i=0; i 0) { item_vote[vote.num_items].count = m_Votes[i]; item_vote[vote.num_items].item = i; vote.num_votes += m_Votes[i]; vote.num_items++; } } vote.item_list = item_vote; assert(vote.num_votes); /* Sort the item list descending */ qsort(item_vote, vote.num_items, sizeof(menu_vote_result_t::menu_item_vote_t), SortVoteItems); /* Take the top 3 (if applicable) and draw them */ int len = 0; for (unsigned int i=0; iGetItemInfo(curItem, &dr); len += g_SourceMod.Format(m_leaderList + len, sizeof(m_leaderList) - len, "\n%i. %s: (%i)", i+1, dr.display, vote.item_list[i].count); } } SourceMod::ResultType VoteMenuHandler::OnTimer(ITimer *pTimer, void *pData) { DrawHintProgress(); return Pl_Continue; } void VoteMenuHandler::OnTimerEnd(ITimer *pTimer, void *pData) { m_displayTimer = NULL; }