/* =========================================================================== Copyright (C) 2023 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "bg_voteoptions.h" #include "../qcommon/script.h" #if defined(GAME_DLL) # define VO_FS_FreeFile gi.FS_FreeFile # define VO_FS_ReadFile(a, b) gi.FS_ReadFile(a, b, true) # define VO_SendRemoteCommand(s) // ? # define VO_ExecuteCommand(s) gi.Cmd_Execute(EXEC_NOW, s) # define VO_Cvar_Set(name, value) gi.cvar_set(name, value) # define VO_Cvar_Get(name, value, flags) gi.Cvar_Get(name, value, flags) # define VO_LV_ConvertString(s) gi.LV_ConvertString(s) #elif defined(CGAME_DLL) # define VO_FS_FreeFile cgi.FS_FreeFile # define VO_FS_ReadFile(a, b) cgi.FS_ReadFile(a, b, qtrue) # define VO_SendRemoteCommand(s) cgi.SendClientCommand(s) # define VO_ExecuteCommand(s) cgi.Cmd_Execute(EXEC_NOW, s) # define VO_Cvar_Set(name, value) cgi.Cvar_Set(name, value) # define VO_Cvar_Get(name, value, flags) cgi.Cvar_Get(name, value, flags) # define VO_LV_ConvertString(s) cgi.LV_ConvertString(s) #else # pragma error("VoteOptions can must be used with fgame or cgame") #endif CLASS_DECLARATION(Class, VoteOptions, NULL) { {NULL, NULL} }; VoteOptionListItem::VoteOptionListItem() : m_pNext(NULL) {} SingleVoteOption::SingleVoteOption() : m_optionType(VOTE_NO_CHOICES) , m_pListItem(NULL) , m_pNext(NULL) {} SingleVoteOption::~SingleVoteOption() { VoteOptionListItem *item; VoteOptionListItem *next; for (item = m_pListItem; item; item = next) { next = item->m_pNext; delete item; } } VoteOptions::VoteOptions() { m_pHeadOption = NULL; } VoteOptions::~VoteOptions() { ClearOptions(); } void VoteOptions::ClearOptions() { SingleVoteOption *option; SingleVoteOption *next; for (option = m_pHeadOption; option; option = next) { next = option->m_pNext; delete option; } m_pHeadOption = NULL; } void VoteOptions::SetupVoteOptions(const char *configFileName) { char *buffer; int compressedLength; long length; length = VO_FS_ReadFile(configFileName, (void **)&buffer); if (length == -1 || !length) { Com_Printf("WARNING: Couldn't find voting options file: %s\n", configFileName); return; } // compress the buffer before setting up compressedLength = COM_Compress(buffer); SetupVoteOptions(configFileName, compressedLength, buffer); VO_FS_FreeFile(buffer); } void VoteOptions::SetupVoteOptions(const char *configFileName, int length, const char *buffer) { if (length >= MAX_VOTEOPTIONS_BUFFER_LENGTH) { Com_Error( ERR_DROP, "VoteOptions: Options file '%s' is too big. Max size is %lu bytes\n", configFileName, MAX_VOTEOPTIONS_BUFFER_LENGTH ); return; } m_sFileName = configFileName; m_sBuffer = buffer; ParseVoteOptions(); } void VoteOptions::ParseVoteOptions() { SingleVoteOption *option; SingleVoteOption *newOption; VoteOptionListItem *listItem; VoteOptionListItem *newListItem; str token; Script script; ClearOptions(); script.LoadFile(m_sFileName.c_str(), m_sBuffer.length(), m_sBuffer.c_str()); option = m_pHeadOption; while (script.TokenAvailable(true)) { token = script.GetToken(true); if (!str::icmp(token, "{")) { Com_Error( ERR_DROP, "Vote Options %s: Found choices list without option header on line %d.\n", m_sFileName.c_str(), script.GetLineNumber() ); return; } if (!str::icmp(token, "}")) { Com_Error( ERR_DROP, "Vote Options %s: Illegal end of choices list without list being started on line %d.\n", m_sFileName.c_str(), script.GetLineNumber() ); return; } if (!token.length()) { Com_Error( ERR_DROP, "Vote Options %s: Empty option name on line %d.\n", m_sFileName.c_str(), script.GetLineNumber() ); return; } newOption = new SingleVoteOption(); if (option) { option->m_pNext = newOption; } else { m_pHeadOption = newOption; } option = newOption; newOption->m_sOptionName = token; if (!script.TokenAvailable(false)) { Com_Error( ERR_DROP, "Vote Options %s: Option without a command specified on line %d.\n", m_sFileName.c_str(), script.GetLineNumber() ); return; } newOption->m_sCommand = script.GetToken(false); if (script.TokenAvailable(false)) { token = script.GetToken(false); if (!str::icmp(token, "nochoices")) { newOption->m_optionType = VOTE_NO_CHOICES; } else if (!str::icmp(token, "list")) { newOption->m_optionType = VOTE_OPTION_LIST; } else if (!str::icmp(token, "text")) { newOption->m_optionType = VOTE_OPTION_TEXT; } else if (!str::icmp(token, "integer")) { newOption->m_optionType = VOTE_OPTION_INTEGER; } else if (!str::icmp(token, "float")) { newOption->m_optionType = VOTE_OPTION_FLOAT; } else if (!str::icmp(token, "client")) { newOption->m_optionType = VOTE_OPTION_CLIENT; } else if (!str::icmp(token, "clientnotself")) { newOption->m_optionType = VOTE_OPTION_CLIENT_NOT_SELF; } else { Com_Error( ERR_DROP, "Vote Options %s: Illegal option type '%s' specified on line %d.\n" " Valid types are nochoices, list, text, & number.\n", m_sFileName.c_str(), token.c_str(), script.GetLineNumber() ); return; } } if (newOption->m_optionType == VOTE_OPTION_LIST) { if (!script.TokenAvailable(true) || Q_stricmp(script.GetToken(true), "{")) { Com_Error( ERR_DROP, "Vote Options %s: Missing '{'. No choices list specified for list option on line %d.\n", m_sFileName.c_str(), script.GetLineNumber() ); return; } listItem = NULL; while (script.TokenAvailable(true)) { token = script.GetToken(true); if (!str::icmp(token, "}")) { break; } newListItem = new VoteOptionListItem(); if (listItem) { listItem->m_pNext = newListItem; } else { newOption->m_pListItem = newListItem; } listItem = newListItem; newListItem->m_sItemName = token; if (!script.TokenAvailable(false)) { Com_Error( ERR_DROP, "Vote Options %s: List choice without vote string specified on line %d.\n", m_sFileName.c_str(), script.GetLineNumber() ); return; } newListItem->m_sCommand = script.GetToken(false); } } else if (script.TokenAvailable(true)) { token = script.GetToken(true); if (!str::icmp(token, "{")) { Com_Error( ERR_DROP, "Vote Options %s: Choices list specified for non-list option on line %d.\n", m_sFileName.c_str(), script.GetLineNumber() ); return; } script.UnGetToken(); } } } const char *VoteOptions::GetVoteOptionsFile(int *outLen) const { if (outLen) { *outLen = m_sBuffer.length(); } return m_sBuffer.c_str(); } bool VoteOptions::GetVoteOptionsMain(int index, str *outOptionCommand, voteoptiontype_t *outOptionType) const { SingleVoteOption *option; int optionIndex; if (index < 1) { return false; } optionIndex = 1; for (option = m_pHeadOption; optionIndex < index && option != NULL; option = option->m_pNext) { optionIndex++; } if (!option) { return false; } *outOptionCommand = option->m_sCommand; *outOptionType = option->m_optionType; return true; } bool VoteOptions::GetVoteOptionSub(int index, int listIndex, str *outCommand) const { SingleVoteOption *option; VoteOptionListItem *item; int optionIndex; int itemIndex; if (index < 1 || listIndex < 1) { return false; } optionIndex = 1; for (option = m_pHeadOption; optionIndex < index && option != NULL; option = option->m_pNext) { optionIndex++; } if (!option) { return false; } if (option->m_optionType != VOTE_OPTION_LIST) { return false; } itemIndex = 1; for (item = option->m_pListItem; itemIndex < listIndex && option != NULL; item = item->m_pNext) { itemIndex++; } if (!item) { return false; } *outCommand = item->m_sCommand; return true; } bool VoteOptions::GetVoteOptionMainName(int index, str *outVoteName) const { SingleVoteOption *option; int optionIndex; if (index < 1) { return false; } optionIndex = 1; for (option = m_pHeadOption; optionIndex < index && option != NULL; option = option->m_pNext) { optionIndex++; } if (!option) { return false; } *outVoteName = option->m_sOptionName; return true; } bool VoteOptions::GetVoteOptionSubName(int index, int listIndex, str *outName) const { SingleVoteOption *option; VoteOptionListItem *item; int optionIndex; int itemIndex; if (index < 1 || listIndex < 1) { return false; } optionIndex = 1; for (option = m_pHeadOption; optionIndex < index && option != NULL; option = option->m_pNext) { optionIndex++; } if (!option) { return false; } if (option->m_optionType != VOTE_OPTION_LIST) { return false; } itemIndex = 1; for (item = option->m_pListItem; itemIndex < listIndex && option != NULL; item = item->m_pNext) { itemIndex++; } if (!item) { return false; } *outName = item->m_sItemName; return true; } #if defined(CGAME_DLL) static str g_sVoteString; static VoteOptions g_voteOptions; void CG_VoteOptions_StartReadFromServer(const char *string) { g_sVoteString = string; } void CG_VoteOptions_ContinueReadFromServer(const char *string) { if (g_sVoteString.length() >= MAX_VOTEOPTIONS_BUFFER_LENGTH) { return; } g_sVoteString += string; } void CG_VoteOptions_FinishReadFromServer(const char *string) { int i; if (g_sVoteString.length() >= MAX_VOTEOPTIONS_BUFFER_LENGTH) { return; } g_sVoteString += va("%s\n", string); if (!str::cmp(g_sVoteString, "\n")) { VO_SendRemoteCommand("wait 250;gvo\n"); return; } for (i = 0; i < g_sVoteString.length(); i++) { if (g_sVoteString[i] == 1) { g_sVoteString[i] = '"'; } } g_voteOptions.SetupVoteOptions("ServerVoteOptions", g_sVoteString.length(), g_sVoteString.c_str()); g_sVoteString = ""; g_voteOptions.SetupMainOptionsList(); } void CG_PushCallVote_f(void) { g_voteOptions.SetupMainOptionsList(); } void CG_PushCallVoteSubList_f(void) { g_voteOptions.SetupSubOptionsList(atoi(cgi.Argv(1))); } void CG_PushCallVoteSubText_f(void) { str command; str name; voteoptiontype_t type; if (!g_voteOptions.GetVoteOptionsMain(atoi(cgi.Argv(1)), &command, &type)) { return; } if (type != voteoptiontype_t::VOTE_OPTION_TEXT) { return; } g_voteOptions.GetVoteOptionMainName(atoi(cgi.Argv(1)), &name); VO_ExecuteCommand("forcemenu votesubtext\n"); VO_Cvar_Set("ui_votesubtitle", name.c_str()); VO_Cvar_Set("ui_votestringentry", ""); } void CG_PushCallVoteSubInteger_f(void) { str command; str name; voteoptiontype_t type; if (!g_voteOptions.GetVoteOptionsMain(atoi(cgi.Argv(1)), &command, &type)) { return; } if (type != voteoptiontype_t::VOTE_OPTION_INTEGER) { return; } g_voteOptions.GetVoteOptionMainName(atoi(cgi.Argv(1)), &name); VO_ExecuteCommand("forcemenu votesubinteger\n"); VO_Cvar_Set("ui_votesubtitle", name.c_str()); VO_Cvar_Set("ui_votestringentry", ""); } void CG_PushCallVoteSubFloat_f(void) { str command; str name; voteoptiontype_t type; if (!g_voteOptions.GetVoteOptionsMain(atoi(cgi.Argv(1)), &command, &type)) { return; } if (type != voteoptiontype_t::VOTE_OPTION_FLOAT) { return; } g_voteOptions.GetVoteOptionMainName(atoi(cgi.Argv(1)), &name); VO_ExecuteCommand("forcemenu votesubfloat\n"); VO_Cvar_Set("ui_votesubtitle", name.c_str()); VO_Cvar_Set("ui_votestringentry", ""); } void CG_PushCallVoteSubClient_f(void) { str command; str name; int index; int i; voteoptiontype_t type; index = atoi(cgi.Argv(1)); if (!g_voteOptions.GetVoteOptionsMain(index, &command, &type)) { return; } if (type != voteoptiontype_t::VOTE_OPTION_CLIENT && type != voteoptiontype_t::VOTE_OPTION_CLIENT_NOT_SELF) { return; } g_voteOptions.GetVoteOptionMainName(index, &name); VO_ExecuteCommand("forcemenu votesubclient\n"); VO_Cvar_Set("ui_votesubtitle", name.c_str()); VO_ExecuteCommand("globalwidgetcommand voteclientlist deleteallitems\n"); for (i = 0; i < cgs.maxclients; i++) { if (type == VOTE_OPTION_CLIENT_NOT_SELF && cg.snap && i == cg.snap->ps.clientNum) { continue; } if (!strlen(cg.clientinfo[i].name)) { continue; } VO_ExecuteCommand( va("globalwidgetcommand voteclientlist additem \"%i: %s\" \"callvote %i %i;popmenu 0\"\n", i, cg.clientinfo[i].name, index, i) ); } VO_ExecuteCommand( va("globalwidgetcommand voteclientlist additem \"%s\" \"popmenu 0\"\n", VO_LV_ConvertString("[Cancel Vote]")) ); } void CG_PushVote_f(void) { VO_Cvar_Set("ui_votesubtitle", cgs.voteString); VO_ExecuteCommand("forcemenu votecast\n"); } void CG_CallEntryVote_f(void) { str command; str name; const char *entry; int index; voteoptiontype_t type; index = VO_Cvar_Get("ui_votetype", "0", 0)->integer; if (!g_voteOptions.GetVoteOptionsMain(index, &command, &type)) { return; } if (type != voteoptiontype_t::VOTE_OPTION_TEXT && type != voteoptiontype_t::VOTE_OPTION_INTEGER && type != voteoptiontype_t::VOTE_OPTION_FLOAT) { return; } entry = VO_Cvar_Get("ui_votestringentry", "", 0)->string; VO_ExecuteCommand(va("callvote %i \"%s\"\n", index, entry)); } void VoteOptions::SetupMainOptionsList(void) { SingleVoteOption *option; int count; VO_ExecuteCommand("forcemenu votemain\n"); VO_ExecuteCommand("globalwidgetcommand votelistmain deleteallitems\n"); if (!IsSetup()) { VO_ExecuteCommand( va("globalwidgetcommand votelistmain additem \"%s\" \"popmenu 0\"\n", VO_LV_ConvertString("Retrieving voting options from server...")) ); VO_SendRemoteCommand("gvo\n"); return; } count = 1; for (option = m_pHeadOption; option; option = option->m_pNext, count++) { switch (option->m_optionType) { case VOTE_NO_CHOICES: VO_ExecuteCommand( va("globalwidgetcommand votelistmain additem \"%s\" \"callvote %i;popmenu 0\"\n", option->m_sOptionName.c_str(), count) ); break; case VOTE_OPTION_LIST: VO_ExecuteCommand( va("globalwidgetcommand votelistmain additem \"%s\" \"popmenu 0;wait 100;pushcallvotesublist %i\"\n", option->m_sOptionName.c_str(), count) ); break; case VOTE_OPTION_TEXT: VO_ExecuteCommand( va("globalwidgetcommand votelistmain additem \"%s\" \"popmenu 0;wait 100;set ui_votetype " "%i;pushcallvotesubtext %i\"\n", option->m_sOptionName.c_str(), count, count) ); break; case VOTE_OPTION_INTEGER: VO_ExecuteCommand( va("globalwidgetcommand votelistmain additem \"%s\" \"popmenu 0;wait 100;set ui_votetype " "%i;pushcallvotesubinteger %i\"\n", option->m_sOptionName.c_str(), count, count) ); break; case VOTE_OPTION_FLOAT: VO_ExecuteCommand( va("globalwidgetcommand votelistmain additem \"%s\" \"popmenu 0;wait 100;set ui_votetype " "%i;pushcallvotesubfloat %i\"\n", option->m_sOptionName.c_str(), count, count) ); break; case VOTE_OPTION_CLIENT: case VOTE_OPTION_CLIENT_NOT_SELF: VO_ExecuteCommand( va("globalwidgetcommand votelistmain additem \"%s\" \"popmenu 0;wait 100;set ui_votetype " "%i;pushcallvotesubclient %i\"\n", option->m_sOptionName.c_str(), count, count) ); break; } } VO_ExecuteCommand( va("globalwidgetcommand votelistmain additem \"%s\" \"popmenu 0\"\n", VO_LV_ConvertString("[Cancel Vote]")) ); } void VoteOptions::SetupSubOptionsList(int index) { SingleVoteOption *option; VoteOptionListItem *item; int count; int numItems; if (index < 1) { return; } count = 1; for (option = m_pHeadOption; count < index && option; option = option->m_pNext) { count++; } if (!option) { return; } if (option->m_optionType != VOTE_OPTION_LIST || !option->m_pListItem) { return; } VO_ExecuteCommand("forcemenu votesublist\n"); VO_Cvar_Set("ui_votesubtitle", option->m_sOptionName.c_str()); VO_ExecuteCommand("globalwidgetcommand votelistsub deleteallitems\n"); if (IsSetup()) { numItems = 1; for (item = option->m_pListItem; item; item = item->m_pNext, numItems++) { VO_ExecuteCommand( va("globalwidgetcommand votelistsub additem \"%s\" \"callvote %i %i;popmenu 0\"\n", item->m_sItemName.c_str(), index, numItems) ); } VO_ExecuteCommand( va("globalwidgetcommand votelistsub additem \"%s\" \"popmenu 0\"\n", VO_LV_ConvertString("[Cancel Vote]")) ); } else { VO_ExecuteCommand( "globalwidgetcommand votelistsub additem \"Retrieving voting options from server...\" \"popmenu 0\"\n" ); VO_SendRemoteCommand("gvo\n"); } } #endif