Skip to content

Commit

Permalink
Additional voting support, vote to kick
Browse files Browse the repository at this point in the history
  • Loading branch information
past-due committed Oct 27, 2023
1 parent 97859ec commit 8017db5
Show file tree
Hide file tree
Showing 9 changed files with 816 additions and 125 deletions.
131 changes: 14 additions & 117 deletions src/multiint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,15 @@
#include "stdinreader.h"
#include "urlhelpers.h"
#include "hci/quickchat.h"
#include "hci/teamstrategy.h"
#include "multivote.h"

#include "activity.h"
#include <algorithm>
#include "3rdparty/gsl_finally.h"

#define MAP_PREVIEW_DISPLAY_TIME 2500 // number of milliseconds to show map in preview
#define LOBBY_DISABLED_TAG "lobbyDisabled"
#define VOTE_TAG "voting"
#define KICK_REASON_TAG "kickReason"
#define SLOTTYPE_TAG_PREFIX "slotType"
#define SLOTTYPE_REQUEST_TAG SLOTTYPE_TAG_PREFIX "::request"
Expand Down Expand Up @@ -179,7 +180,6 @@ char sPlayer[128] = {'\0'}; // player name (to be used)
bool multiintDisableLobbyRefresh = false; // if we allow lobby to be refreshed or not.

static UDWORD hideTime = 0;
static uint8_t playerVotes[MAX_PLAYERS];
LOBBY_ERROR_TYPES LobbyError = ERROR_NOERROR;
static bool bInActualHostedLobby = false;
static bool bRequestedSelfMoveToPlayers = false;
Expand Down Expand Up @@ -1279,114 +1279,14 @@ WzString formatGameName(WzString name)
return withoutTechlevel + " (T" + WzString::number(game.techLevel) + " " + WzString::number(game.maxPlayers) + "P)";
}

void resetVoteData()
{
for (unsigned int i = 0; i < MAX_PLAYERS; ++i)
{
playerVotes[i] = 0;
}
}

static void sendVoteData(uint8_t currentVote)
{
NETbeginEncode(NETbroadcastQueue(), NET_VOTE);
NETuint32_t(&selectedPlayer);
NETuint8_t(&currentVote);
NETend();
}

static uint8_t getVoteTotal()
{
ASSERT_HOST_ONLY(return true);

uint8_t total = 0;

for (unsigned i = 0; i < MAX_PLAYERS; ++i)
{
if (isHumanPlayer(i))
{
if (selectedPlayer == i)
{
// always count the host as a "yes" vote.
playerVotes[i] = 1;
}
total += playerVotes[i];
}
else
{
playerVotes[i] = 0;
}
}

return total;
}

static bool recvVote(NETQUEUE queue)
{
ASSERT_HOST_ONLY(return true);

uint8_t newVote;
uint32_t player;

NETbeginDecode(queue, NET_VOTE);
NETuint32_t(&player); // TODO: check that NETQUEUE belongs to that player :wink:
NETuint8_t(&newVote);
NETend();

if (player >= MAX_PLAYERS)
{
debug(LOG_ERROR, "Invalid NET_VOTE from player %d: player id = %d", queue.index, static_cast<int>(player));
return false;
}

playerVotes[player] = (newVote == 1) ? 1 : 0;

debug(LOG_NET, "total votes: %d/%d", static_cast<int>(getVoteTotal()), static_cast<int>(NET_numHumanPlayers()));

// there is no "votes" that disallows map change so assume they are all allowing
if(newVote == 1) {
char msg[128] = {0};
ssprintf(msg, _("%s (%d) allowed map change. Total: %d/%d"), NetPlay.players[player].name, player, static_cast<int>(getVoteTotal()), static_cast<int>(NET_numHumanPlayers()));
sendRoomSystemMessage(msg);
}

return true;
}

// Show a vote popup to allow changing maps or using the randomization feature.
static void setupVoteChoice()
{
//This shouldn't happen...
if (NetPlay.isHost)
{
ASSERT(false, "Host tried to send vote data to themself");
return;
}

if (!hasNotificationsWithTag(VOTE_TAG))
{
WZ_Notification notification;
notification.duration = 0;
notification.contentTitle = _("Vote");
notification.contentText = _("Allow host to change map or randomize?");
notification.action = WZ_Notification_Action("Allow", [](const WZ_Notification&) {
uint8_t vote = 1;
sendVoteData(vote);
});
notification.tag = VOTE_TAG;

addNotification(notification, WZ_Notification_Trigger(GAME_TICKS_PER_SEC * 1));
}
}

static bool canChangeMapOrRandomize()
{
ASSERT_HOST_ONLY(return true);

uint8_t numHumans = NET_numHumanPlayers();
bool allowed = (static_cast<float>(getVoteTotal()) / static_cast<float>(numHumans)) > 0.5f;
bool allowed = (static_cast<float>(getLobbyChangeVoteTotal()) / static_cast<float>(numHumans)) > 0.5f;

resetVoteData(); //So the host can only do one change every vote session
resetLobbyChangeVoteData(); //So the host can only do one change every vote session

if (numHumans == 1)
{
Expand All @@ -1395,10 +1295,7 @@ static bool canChangeMapOrRandomize()

if (!allowed)
{
//setup a vote popup for the clients
NETbeginEncode(NETbroadcastQueue(), NET_VOTE_REQUEST);
NETend();

startLobbyChangeVote();
displayRoomSystemMessage(_("Not enough votes to randomize or change the map."));
}

Expand Down Expand Up @@ -3339,7 +3236,7 @@ static SwapPlayerIndexesResult recvSwapPlayerIndexes(NETQUEUE queue, const std::
NETsetPlayerConnectionStatus(CONNECTIONSTATUS_PLAYER_DROPPED, playerIndex); // needed??
if (playerIndex < MAX_PLAYERS)
{
playerVotes[playerIndex] = 0;
resetLobbyChangePlayerVote(playerIndex);
}
}
swapPlayerMultiStatsLocal(playerIndexA, playerIndexB);
Expand Down Expand Up @@ -5429,7 +5326,7 @@ static void stopJoining(std::shared_ptr<WzTitleUI> parent)
bInActualHostedLobby = false;

reloadMPConfig(); // reload own settings
cancelOrDismissNotificationsWithTag(VOTE_TAG);
cancelOrDismissVoteNotifications();
cancelOrDismissNotificationIfTag([](const std::string& tag) {
return (tag.rfind(SLOTTYPE_TAG_PREFIX, 0) == 0);
});
Expand Down Expand Up @@ -6359,7 +6256,7 @@ void WzMultiplayerOptionsTitleUI::processMultiopWidgets(UDWORD id)
sstrcpy(game.name, widgGetString(psWScreen, MULTIOP_GNAME));
sstrcpy(sPlayer, widgGetString(psWScreen, MULTIOP_PNAME));

resetVoteData();
resetLobbyChangeVoteData();
resetDataHash();

startHost();
Expand All @@ -6381,7 +6278,7 @@ void WzMultiplayerOptionsTitleUI::processMultiopWidgets(UDWORD id)
if (NetPlay.bComms && ingame.side == InGameSide::MULTIPLAYER_CLIENT && !NetPlay.isHost)
{
// remove a potential "allow" vote if we gracefully leave
sendVoteData(0);
sendLobbyChangeVoteData(0);
}
NETGameLocked(false); // reset status on a cancel
stopJoining(parent);
Expand Down Expand Up @@ -6914,7 +6811,7 @@ void WzMultiplayerOptionsTitleUI::frontendMultiMessages(bool running)
NETsetPlayerConnectionStatus(CONNECTIONSTATUS_PLAYER_DROPPED, player_id);
if (player_id < MAX_PLAYERS)
{
playerVotes[player_id] = 0;
resetLobbyChangePlayerVote(player_id);
}
ActivityManager::instance().updateMultiplayGameData(game, ingame, NETGameIsLocked());
if (player_id == NetPlay.hostPlayer || player_id == selectedPlayer) // if host quits or we quit, abort out
Expand All @@ -6939,7 +6836,7 @@ void WzMultiplayerOptionsTitleUI::frontendMultiMessages(bool running)
break;
}
case NET_FIREUP: // campaign game started.. can fire the whole shebang up...
cancelOrDismissNotificationsWithTag(VOTE_TAG); // don't need vote notifications anymore
cancelOrDismissVoteNotifications(); // don't need vote notifications anymore
cancelOrDismissNotificationsWithTag(LOBBY_DISABLED_TAG);
cancelOrDismissNotificationIfTag([](const std::string& tag) {
return (tag.rfind(SLOTTYPE_TAG_PREFIX, 0) == 0);
Expand Down Expand Up @@ -6996,7 +6893,7 @@ void WzMultiplayerOptionsTitleUI::frontendMultiMessages(bool running)

if (player_id < MAX_PLAYERS)
{
playerVotes[player_id] = 0;
resetLobbyChangePlayerVote(player_id);
}

if (player_id == NetPlay.hostPlayer)
Expand Down Expand Up @@ -7083,7 +6980,7 @@ void WzMultiplayerOptionsTitleUI::frontendMultiMessages(bool running)
case NET_VOTE_REQUEST:
if (!NetPlay.isHost && !NetPlay.players[selectedPlayer].isSpectator)
{
setupVoteChoice();
recvVoteRequest(queue);
}
break;

Expand Down Expand Up @@ -7366,7 +7263,7 @@ TITLECODE WzMultiplayerOptionsTitleUI::run()
}
if (!NetPlay.isHostAlive && ingame.side == InGameSide::MULTIPLAYER_CLIENT)
{
cancelOrDismissNotificationsWithTag(VOTE_TAG);
cancelOrDismissVoteNotifications();
cancelOrDismissNotificationsWithTag(LOBBY_DISABLED_TAG);
cancelOrDismissNotificationIfTag([](const std::string& tag) {
return (tag.rfind(SLOTTYPE_TAG_PREFIX, 0) == 0);
Expand Down
1 change: 0 additions & 1 deletion src/multiint.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ void loadMapPreview(bool hideInterface);

bool changeReadyStatus(UBYTE player, bool bReady);
WzString formatGameName(WzString name);
void resetVoteData();
void sendRoomSystemMessage(char const *text);
void sendRoomNotifyMessage(char const *text);
void sendRoomSystemMessageToSingleReceiver(char const *text, uint32_t receiver);
Expand Down
2 changes: 2 additions & 0 deletions src/multijoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
#include "multiint.h"
#include "multistat.h"
#include "multigifts.h"
#include "multivote.h"
#include "qtscript.h"
#include "clparse.h"
#include "multilobbycommands.h"
Expand Down Expand Up @@ -417,6 +418,7 @@ void recvPlayerLeft(NETQUEUE queue)
NetPlay.players[playerIndex].allocated = false;

NETsetPlayerConnectionStatus(CONNECTIONSTATUS_PLAYER_DROPPED, playerIndex);
cancelOrDismissKickVote(playerIndex);

debug(LOG_INFO, "** player %u has dropped, in-game! (gameTime: %" PRIu32 ")", playerIndex, gameTime);
ActivityManager::instance().updateMultiplayGameData(game, ingame, NETGameIsLocked());
Expand Down
9 changes: 2 additions & 7 deletions src/multimenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
#include "loop.h"
#include "frontend.h"
#include "hci/teamstrategy.h"
#include "multivote.h"

// ////////////////////////////////////////////////////////////////////////////
// defines
Expand Down Expand Up @@ -877,17 +878,11 @@ class MultiMenuGrid: public GridLayout

if (mouseDown(MOUSE_RMB) && NetPlay.isHost) // both buttons....
{
char buf[250];

// Allow the host to kick the AI only in a MP game, or if they activated cheats in a skirmish game
if ((NetPlay.bComms || Cheated) && (NetPlay.players[i].allocated || (NetPlay.players[i].allocated == false && NetPlay.players[i].ai != AI_OPEN)))
{
inputLoseFocus();
ssprintf(buf, _("The host has kicked %s from the game!"), getPlayerName((unsigned int) i));
sendInGameSystemMessage(buf);
ssprintf(buf, _("kicked %s : %s from the game, and added them to the banned list!"), getPlayerName((unsigned int) i), NetPlay.players[i].IPtextAddress);
NETlogEntry(buf, SYNC_FLAG, (unsigned int) i);
kickPlayer((unsigned int) i, _("The host has kicked you from the game."), ERROR_KICKED, false);
startKickVote(static_cast<uint32_t>(i));
return;
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/multiopt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
#include "multigifts.h"
#include "multiint.h"
#include "multirecv.h"
#include "multivote.h"
#include "template.h"
#include "activity.h"
#include "warzoneconfig.h"
Expand Down Expand Up @@ -726,5 +727,7 @@ bool multiGameShutdown()

NET_InitPlayers();

resetKickVoteData();

return true;
}
14 changes: 14 additions & 0 deletions src/multiplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
#include "cheat.h"
#include "main.h" // for gamemode
#include "multiint.h"
#include "multivote.h"
#include "activity.h"
#include "lib/framework/wztime.h"
#include "chat.h" // for InGameChatMessage
Expand Down Expand Up @@ -370,6 +371,7 @@ bool multiPlayerLoop()
if (NetPlay.isHost)
{
autoLagKickRoutine();
processPendingKickVotes();
}

// if player has won then process the win effects...
Expand Down Expand Up @@ -1314,6 +1316,18 @@ bool recvMessage()
case GAME_ALLIANCE:
recvAlliance(queue, true);
break;
case NET_VOTE:
if (NetPlay.isHost)
{
recvVote(queue);
}
break;
case NET_VOTE_REQUEST:
if (!NetPlay.isHost && !NetPlay.players[selectedPlayer].isSpectator)
{
recvVoteRequest(queue);
}
break;
case NET_KICK: // in-game kick message
{
uint32_t player_id;
Expand Down
Loading

0 comments on commit 8017db5

Please sign in to comment.