RingRacers/src/k_zvote.c
2024-09-27 15:43:19 -07:00

1287 lines
27 KiB
C

// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour
// Copyright (C) 2024 by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file k_zvote.c
/// \brief Player callable mid-game vote
#include "k_zvote.h"
#include "doomdef.h"
#include "command.h"
#include "g_game.h"
#include "g_input.h"
#include "d_clisrv.h"
#include "p_local.h"
#include "hu_stuff.h"
#include "v_video.h"
#include "k_hud.h"
#include "r_draw.h"
#include "r_fps.h"
#include "byteptr.h"
#include "s_sound.h"
extern consvar_t cv_zvote_quorum;
extern consvar_t cv_zvote_spectators;
extern consvar_t cv_zvote_length;
extern consvar_t cv_zvote_delay;
midVote_t g_midVote = {0};
typedef void (*K_ZVoteFinishCallback)(void);
typedef struct
{
const char *name;
const char *label;
consvar_t cv_allowed;
K_ZVoteFinishCallback callback;
} midVoteTypeDef_t;
/*--------------------------------------------------
static void K_MidVoteKick(void)
MVT_KICK's success function.
--------------------------------------------------*/
static void K_MidVoteKick(void)
{
if (g_midVote.victim == NULL)
{
return;
}
SendKick(g_midVote.victim - players, KICK_MSG_VOTE_KICK);
}
/*--------------------------------------------------
static void K_MidVoteRockTheVote(void)
MVT_RTV's success function.
--------------------------------------------------*/
static void K_MidVoteRockTheVote(void)
{
if (G_GamestateUsesExitLevel() == false)
{
return;
}
SendNetXCmd(XD_EXITLEVEL, NULL, 0);
}
/*--------------------------------------------------
static void K_MidVoteRunItBack(void)
MVT_RUNITBACK's success function.
--------------------------------------------------*/
static void K_MidVoteRunItBack(void)
{
boolean newencore = false;
if (cv_kartencore.value != 0)
{
newencore = (cv_kartencore.value == 1) || encoremode;
}
D_MapChange(gamemap, gametype, newencore, false, 0, false, false);
}
static midVoteTypeDef_t g_midVoteTypeDefs[MVT__MAX] =
{
{ // MVT_KICK
"KICK",
"Kick Player?",
CVAR_INIT ("zvote_kick_allowed", "Yes", CV_SAVE|CV_NETVAR, CV_YesNo, NULL),
K_MidVoteKick
},
{ // MVT_RTV
"RTV",
"Skip Level?",
CVAR_INIT ("zvote_rtv_allowed", "Yes", CV_SAVE|CV_NETVAR, CV_YesNo, NULL),
K_MidVoteRockTheVote
},
{ // MVT_RUNITBACK
"RUNITBACK",
"Redo Level?",
CVAR_INIT ("zvote_runitback_allowed", "Yes", CV_SAVE|CV_NETVAR, CV_YesNo, NULL),
K_MidVoteRunItBack
},
};
/*--------------------------------------------------
boolean K_MidVoteTypeUsesVictim(midVoteType_e voteType)
See header file for description.
--------------------------------------------------*/
boolean K_MidVoteTypeUsesVictim(midVoteType_e voteType)
{
switch (voteType)
{
case MVT_KICK:
{
return true;
}
default:
{
return false;
}
}
}
/*--------------------------------------------------
static void Command_CallVote(void)
Callback function for the "zvote_call" console command.
--------------------------------------------------*/
static void Command_CallVote(void)
{
size_t numArgs = 0;
const char *voteTypeStr = NULL;
midVoteType_e voteType = MVT__MAX;
const char *voteVariableStr = NULL;
INT32 voteVariable = 0;
INT32 i = INT32_MAX;
if (netgame == false)
{
CONS_Printf(M_GetText("This only works in a netgame.\n"));
return;
}
numArgs = COM_Argc();
if (numArgs < 2)
{
CONS_Printf("%s <type> [variable]: calls a vote\n", COM_Argv(0));
return;
}
voteTypeStr = COM_Argv(1);
for (voteType = 0; voteType < MVT__MAX; voteType++)
{
if (strcasecmp(voteTypeStr, g_midVoteTypeDefs[voteType].name) == 0)
{
break;
}
}
if (voteType == MVT__MAX)
{
CONS_Printf("Unknown vote type \"%s\".\n", voteTypeStr);
return;
}
if (numArgs > 2)
{
voteVariableStr = COM_Argv(2);
voteVariable = atoi(voteVariableStr);
if (K_MidVoteTypeUsesVictim(voteType) == true)
{
for (i = 0; i < MAXPLAYERS; i++)
{
if (strcasecmp(player_names[i], voteVariableStr) == 0)
{
voteVariable = i;
break;
}
}
}
}
K_SendCallMidVote(voteType, voteVariable);
}
/*--------------------------------------------------
void K_SendCallMidVote(midVoteType_e voteType, INT32 voteVariable)
See header file for description.
--------------------------------------------------*/
void K_SendCallMidVote(midVoteType_e voteType, INT32 voteVariable)
{
player_t *victim = NULL;
if (K_MidVoteTypeUsesVictim(voteType) == true)
{
if (voteVariable >= 0 && voteVariable < MAXPLAYERS)
{
victim = &players[voteVariable];
}
}
if (K_AllowNewMidVote(&players[consoleplayer], voteType, voteVariable, victim) == false)
{
// Invalid vote inputs.
return;
}
UINT8 buf[MAXTEXTCMD];
UINT8 *buf_p = buf;
WRITEUINT8(buf_p, voteType);
WRITEINT32(buf_p, voteVariable);
SendNetXCmd(XD_CALLZVOTE, buf, buf_p - buf);
}
/*--------------------------------------------------
static void Got_CallZVote(const UINT8 **cp, INT32 playernum)
Callback function for XD_CALLZVOTE NetXCmd.
Attempts to start a new vote using K_InitNewMidVote.
Input Arguments:-
cp - Pointer to readable byte stream.
playernum - The player this packet came from.
Return:-
N/A
--------------------------------------------------*/
static void Got_CallZVote(const UINT8 **cp, INT32 playernum)
{
midVoteType_e type = MVT__MAX;
INT32 variable = 0;
player_t *victim = NULL;
type = READUINT8(*cp);
variable = READINT32(*cp);
if (K_MidVoteTypeUsesVictim(type) == true)
{
if (variable >= 0 && variable < MAXPLAYERS)
{
victim = &players[variable];
}
}
K_InitNewMidVote(&players[playernum], type, variable, victim);
}
/*--------------------------------------------------
static void K_PlayerSendMidVote(const UINT8 id)
Sends a local player's confirmed vote to
the server.
Input Arguments:-
id - Local splitscreen player ID.
Return:-
N/A
--------------------------------------------------*/
static void K_PlayerSendMidVote(const UINT8 id)
{
if (id >= MAXSPLITSCREENPLAYERS)
{
return;
}
SendNetXCmdForPlayer(id, XD_SETZVOTE, NULL, 0);
}
/*--------------------------------------------------
static void Got_SetZVote(const UINT8 **cp, INT32 playernum)
Callback function for XD_SETZVOTE NetXCmd.
Updates the vote table.
Input Arguments:-
cp - Pointer to readable byte stream.
playernum - The player this packet came from.
Return:-
N/A
--------------------------------------------------*/
static void Got_SetZVote(const UINT8 **cp, INT32 playernum)
{
(void)cp;
if (g_midVote.active == false)
{
return;
}
S_StartSound(NULL, sfx_gshad);
g_midVote.votes[playernum] = true;
}
/*--------------------------------------------------
void K_RegisterMidVoteCVars(void)
See header file for description.
--------------------------------------------------*/
void K_RegisterMidVoteCVars(void)
{
INT32 i = INT32_MAX;
for (i = 0; i < MVT__MAX; i++)
{
CV_RegisterVar(&g_midVoteTypeDefs[i].cv_allowed);
}
COM_AddCommand("zvote_call", Command_CallVote);
RegisterNetXCmd(XD_CALLZVOTE, Got_CallZVote);
RegisterNetXCmd(XD_SETZVOTE, Got_SetZVote);
}
/*--------------------------------------------------
void K_ResetMidVote(void)
See header file for description.
--------------------------------------------------*/
void K_ResetMidVote(void)
{
memset(&g_midVote, 0, sizeof(g_midVote));
}
/*--------------------------------------------------
boolean K_AnyMidVotesAllowed(void)
See header file for description.
--------------------------------------------------*/
boolean K_AnyMidVotesAllowed(void)
{
INT32 i = INT32_MAX;
for (i = 0; i < MVT__MAX; i++)
{
if (g_midVoteTypeDefs[i].cv_allowed.value != 0)
{
return true;
}
}
return false;
}
/*--------------------------------------------------
midVoteType_e K_GetNextCallableMidVote(INT32 seed, boolean backwards)
See header file for description.
--------------------------------------------------*/
midVoteType_e K_GetNextAllowedMidVote(midVoteType_e seed, boolean backwards)
{
if (seed >= MVT__MAX)
seed = 0;
midVoteType_e i = seed;
if (backwards)
{
do
{
if (i <= 0)
i = MVT__MAX;
i--;
if (g_midVoteTypeDefs[i].cv_allowed.value != 0)
return i;
}
while (i != seed);
}
else
{
do
{
i++;
if (i >= MVT__MAX)
i = 0;
if (g_midVoteTypeDefs[i].cv_allowed.value != 0)
return i;
}
while (i != seed);
}
return MVT__MAX;
}
/*--------------------------------------------------
boolean K_PlayerIDAllowedInMidVote(const UINT8 id)
See header file for description.
--------------------------------------------------*/
boolean K_PlayerIDAllowedInMidVote(const UINT8 id)
{
const player_t *player = &players[id];
if (playeringame[id] == false)
{
// Needs to be present to vote.
return false;
}
if (player->bot == true)
{
// Bots don't vote on these issues.
return false;
}
if (cv_zvote_spectators.value == 0 && player->spectator == true)
{
// Spectators don't vote on these issues, unless the server allows it.
return false;
}
return true;
}
/*--------------------------------------------------
UINT8 K_RequiredMidVotes(void)
See header file for description.
--------------------------------------------------*/
UINT8 K_RequiredMidVotes(void)
{
UINT8 allowedCount = 0;
INT32 i = INT32_MAX;
if (g_midVote.active == false)
{
// No vote is currently running.
return 0;
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (K_PlayerIDAllowedInMidVote(i) == true)
{
allowedCount++;
}
}
if (allowedCount > 1)
{
return max(
2, // require at least 2 votes, regardless of how low the quorum is
(FixedMul(
(allowedCount << FRACBITS),
cv_zvote_quorum.value
) + (FRACUNIT >> 1)) >> FRACBITS // Round up to bias towards more votes being required in small games
);
}
else
{
// 1P session, just require the one vote.
return 1;
}
}
/*--------------------------------------------------
boolean K_PlayerIDMidVoted(const UINT8 id)
See header file for description.
--------------------------------------------------*/
boolean K_PlayerIDMidVoted(const UINT8 id)
{
const player_t *player = &players[id];
if (K_PlayerIDAllowedInMidVote(id) == false)
{
// This person isn't allowed to participate in votes.
return false;
}
if (player == g_midVote.caller)
{
// The person who called the vote always votes for it.
return true;
}
else if (player == g_midVote.victim)
{
// The person being voted off never votes for it.
return false;
}
return g_midVote.votes[id];
}
/*--------------------------------------------------
UINT8 K_CountMidVotes(void)
See header file for description.
--------------------------------------------------*/
UINT8 K_CountMidVotes(void)
{
UINT8 voteCount = 0;
INT32 i = INT32_MAX;
if (g_midVote.active == false)
{
// No vote is currently running.
return 0;
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (K_PlayerIDMidVoted(i) == true)
{
voteCount++;
}
}
return voteCount;
}
/*--------------------------------------------------
boolean K_MinimalCheckNewMidVote(midVoteType_e type)
See header file for description.
--------------------------------------------------*/
boolean K_MinimalCheckNewMidVote(midVoteType_e type)
{
if (g_midVote.active == true)
{
// Don't allow another vote if one is already running.
return false;
}
if (g_midVote.delay > 0)
{
// Don't allow another vote if one has recently just ran.
return false;
}
if (type < 0 || type >= MVT__MAX)
{
// Invalid range.
return false;
}
if (g_midVoteTypeDefs[type].cv_allowed.value == 0)
{
// These types of votes aren't allowed on this server.
return false;
}
if (K_PlayerIDAllowedInMidVote(consoleplayer) == false)
{
// Invalid calling player.
return false;
}
return true;
}
/*--------------------------------------------------
boolean K_AllowNewMidVote(player_t *caller, midVoteType_e type, INT32 variable, player_t *victim)
See header file for description.
--------------------------------------------------*/
boolean K_AllowNewMidVote(player_t *caller, midVoteType_e type, INT32 variable, player_t *victim)
{
(void)variable;
if (g_midVote.active == true)
{
// Don't allow another vote if one is already running.
if (P_IsMachineLocalPlayer(caller) == true)
{
CONS_Alert(CONS_ERROR, "A vote is already in progress.\n");
}
return false;
}
if (g_midVote.delay > 0)
{
// Don't allow another vote if one has recently just ran.
if (P_IsMachineLocalPlayer(caller) == true)
{
CONS_Alert(CONS_ERROR, "Another vote was called too recently.\n");
}
return false;
}
if (type < 0 || type >= MVT__MAX)
{
// Invalid range.
if (P_IsMachineLocalPlayer(caller) == true)
{
CONS_Alert(CONS_ERROR, "Invalid vote type.\n");
}
return false;
}
if (g_midVoteTypeDefs[type].cv_allowed.value == 0)
{
// These types of votes aren't allowed on this server.
if (P_IsMachineLocalPlayer(caller) == true)
{
CONS_Alert(CONS_ERROR, "Vote type is not allowed in this server.\n");
}
return false;
}
if (caller == NULL || K_PlayerIDAllowedInMidVote(caller - players) == false)
{
// Invalid calling player.
if (caller != NULL && P_IsMachineLocalPlayer(caller) == true)
{
CONS_Alert(CONS_ERROR, "Invalid calling player.\n");
}
return false;
}
if (K_MidVoteTypeUsesVictim(type) == true)
{
if (victim == NULL)
{
// Invalid victim.
if (P_IsMachineLocalPlayer(caller) == true)
{
CONS_Alert(CONS_ERROR, "Can't kick this player; it's invalid.\n");
}
return false;
}
if (caller == victim)
{
if (P_IsMachineLocalPlayer(caller) == true)
{
CONS_Alert(CONS_ERROR, "Can't kick yourself.\n");
}
return false;
}
if ((victim - players) == serverplayer
#ifndef DEVELOP
|| IsPlayerAdmin((victim - players)) == true
#endif
)
{
// Victim is the server or an admin.
if (P_IsMachineLocalPlayer(caller) == true)
{
CONS_Alert(CONS_ERROR, "Can't kick this player; they are an administrator.\n");
}
return false;
}
}
return true;
}
/*--------------------------------------------------
void K_InitNewMidVote(player_t *caller, midVoteType_e type, INT32 variable, player_t *victim)
See header file for description.
--------------------------------------------------*/
void K_InitNewMidVote(player_t *caller, midVoteType_e type, INT32 variable, player_t *victim)
{
INT32 i = INT32_MAX;
if (K_AllowNewMidVote(caller, type, variable, victim) == false)
{
// Invalid vote inputs.
return;
}
K_ResetMidVote();
g_midVote.active = true;
g_midVote.caller = caller;
g_midVote.type = type;
g_midVote.variable = variable;
g_midVote.victim = victim;
if (server || IsPlayerAdmin(consoleplayer))
{
if (victim)
HU_AddChatText(va("%s called a vote to %s %s\n", player_names[caller-players], g_midVoteTypeDefs[type].name, player_names[victim-players]), true);
else
HU_AddChatText(va("%s called a vote to %s\n", player_names[caller-players], g_midVoteTypeDefs[type].name), true);
}
S_StartSound(NULL, sfx_cdfm67);
g_midVote.votes[caller - players] = true;
for (i = 0; i <= splitscreen; i++)
{
if (caller == &players[g_localplayers[i]])
{
// The person who voted should already be confirmed.
g_midVote.gui[i].slide = ZVOTE_GUI_SLIDE;
g_midVote.gui[i].confirm = ZVOTE_GUI_CONFIRM;
g_midVote.gui[i].unpress = true;
}
}
}
/*--------------------------------------------------
void K_MidVoteFinalize(fixed_t delayMul)
See header file for description.
--------------------------------------------------*/
void K_MidVoteFinalize(fixed_t delayMul)
{
K_ResetMidVote();
g_midVote.delay = FixedMul(cv_zvote_delay.value * TICRATE, delayMul);
}
/*--------------------------------------------------
void K_MidVoteSuccess(void)
See header file for description.
--------------------------------------------------*/
void K_MidVoteSuccess(void)
{
if (
server == true
&& demo.playback == false
&& g_midVoteTypeDefs[ g_midVote.type ].callback != NULL
)
{
g_midVoteTypeDefs[ g_midVote.type ].callback();
}
K_MidVoteFinalize(FRACUNIT); // Vote succeeded, so the delay is normal.
}
/*--------------------------------------------------
void K_MidVoteFailure(void)
See header file for description.
--------------------------------------------------*/
void K_MidVoteFailure(void)
{
K_MidVoteFinalize(2*FRACUNIT); // Vote failed, so the delay is longer.
}
/*--------------------------------------------------
static void K_HandleMidVoteInput(void)
See header file for description.
--------------------------------------------------*/
static void K_HandleMidVoteInput(void)
{
INT32 i = INT32_MAX;
for (i = 0; i <= splitscreen; i++)
{
//player_t *const player = &players[ g_localplayers[i] ];
midVoteGUI_t *const gui = &g_midVote.gui[i];
boolean pressed = false;
if (menuactive == false)
{
pressed = G_PlayerInputDown(i, gc_z, 0);
}
// Between states, require us to unpress Z.
if (pressed == true)
{
if (gui->unpress == true)
{
pressed = false;
}
}
else
{
gui->unpress = false;
}
if (gui->slide < ZVOTE_GUI_SLIDE)
{
if (gui->slide > 0 || pressed == true)
{
gui->slide++;
gui->unpress = true;
}
}
else if (gui->confirm < ZVOTE_GUI_CONFIRM)
{
if (K_PlayerIDAllowedInMidVote(g_localplayers[i]) == false)
{
gui->confirm = 0;
continue;
}
if (pressed == true)
{
gui->confirm++;
if (gui->confirm == ZVOTE_GUI_CONFIRM)
{
K_PlayerSendMidVote(i);
gui->unpress = true;
}
}
else
{
gui->confirm = 0;
}
}
}
}
#define ZVOTE_PATCH_EXC_START (4)
#define ZVOTE_PATCH_EXC_LOOP (3)
#define ZVOTE_PATCH_BAR_SEGS (12)
/*--------------------------------------------------
void K_TickMidVote(void)
See header file for description.
--------------------------------------------------*/
void K_TickMidVote(void)
{
UINT8 numVotes = 0;
UINT8 requiredVotes = 0;
if (g_midVote.active == false)
{
// No vote is currently running.
if (g_midVote.delay > 0)
{
// Decrement timer for allowing the next vote.
g_midVote.delay--;
}
return;
}
if (g_midVote.end > 0)
{
g_midVote.end++;
if (g_midVote.end > ZVOTE_GUI_SUCCESS)
{
if (g_midVote.endVotes >= g_midVote.endRequired)
{
K_MidVoteSuccess();
}
else
{
K_MidVoteFailure();
}
}
return;
}
numVotes = K_CountMidVotes();
requiredVotes = K_RequiredMidVotes();
if (numVotes >= requiredVotes
|| g_midVote.time > (unsigned)(cv_zvote_length.value * TICRATE))
{
// Vote finished.
// Start the ending animation.
S_StartSound(NULL, sfx_kc48);
g_midVote.end++;
g_midVote.endVotes = numVotes;
g_midVote.endRequired = requiredVotes;
return;
}
K_HandleMidVoteInput();
g_midVote.time++;
// Go go gadget duplicated code. Sorry, this blows ass and makes no sense.
// I hope we never change this timing again, but if we do, check the drawer as well.
const tic_t spd = 2;
const tic_t anim = (g_midVote.time - ZVOTE_GUI_SLIDE) / spd;
const UINT8 frame = anim % (ZVOTE_PATCH_EXC_LOOP + ZVOTE_GUI_SLIDE);
if (frame == 0 && g_midVote.time % spd == 0 && g_midVote.gui[R_GetViewNumber()].slide == 0)
S_StartSound(NULL, sfx_s3kd2s);
}
/*--------------------------------------------------
void K_CacheMidVotePatches(void)
See header file for description.
--------------------------------------------------*/
static patch_t *g_exclamationSlide = NULL;
static patch_t *g_exclamationStart[ZVOTE_PATCH_EXC_LOOP] = {NULL};
static patch_t *g_exclamation = NULL;
static patch_t *g_exclamationLoop[ZVOTE_PATCH_EXC_LOOP] = {NULL};
static patch_t *g_zBar[2] = {NULL};
static patch_t *g_zBarEnds[2][2][2] = {{{NULL}}};
void K_UpdateMidVotePatches(void)
{
HU_UpdatePatch(&g_exclamationSlide, "TLSBA0");
HU_UpdatePatch(&g_exclamationStart[0], "TLSBB0");
HU_UpdatePatch(&g_exclamationStart[1], "TLSBC0");
HU_UpdatePatch(&g_exclamationStart[2], "TLSBD0");
HU_UpdatePatch(&g_exclamation, "TLSBE0");
HU_UpdatePatch(&g_exclamationLoop[0], "TLSBF0");
HU_UpdatePatch(&g_exclamationLoop[1], "TLSBG0");
HU_UpdatePatch(&g_exclamationLoop[2], "TLSBD0");
HU_UpdatePatch(&g_zBar[0], "TLBWE0");
HU_UpdatePatch(&g_zBarEnds[0][0][0], "TLBWC0");
HU_UpdatePatch(&g_zBarEnds[0][0][1], "TLBWD0");
HU_UpdatePatch(&g_zBarEnds[0][1][0], "TLBWA0");
HU_UpdatePatch(&g_zBarEnds[0][1][1], "TLBWB0");
HU_UpdatePatch(&g_zBar[1], "TLBXE0");
HU_UpdatePatch(&g_zBarEnds[1][0][0], "TLBXC0");
HU_UpdatePatch(&g_zBarEnds[1][0][1], "TLBXD0");
HU_UpdatePatch(&g_zBarEnds[1][1][0], "TLBXA0");
HU_UpdatePatch(&g_zBarEnds[1][1][1], "TLBXB0");
}
/*--------------------------------------------------
const char *K_GetMidVoteLabel(midVoteType_e i)
See header file for description.
--------------------------------------------------*/
const char *K_GetMidVoteLabel(midVoteType_e i)
{
if (
i < 0
|| i >= MVT__MAX
|| g_midVoteTypeDefs[i].label == NULL)
{
return "N/A";
}
return g_midVoteTypeDefs[i].label;
}
/*--------------------------------------------------
static void K_DrawMidVoteBar(fixed_t x, fixed_t y, INT32 flags, fixed_t fill, skincolornum_t color, boolean flipped)
Draws a bar
Input Arguments:-
voteType - The vote type to check.
Return:-
true if it uses a victim, otherwise false.
--------------------------------------------------*/
static void K_DrawMidVoteBar(fixed_t x, fixed_t y, INT32 flags, fixed_t fill, skincolornum_t color, boolean flipped)
{
const SINT8 sign = (flipped == true) ? -1 : 1;
patch_t *bar = g_zBar[0];
UINT8 *clm = NULL;
INT32 i = INT32_MAX;
if (color > SKINCOLOR_NONE)
{
clm = R_GetTranslationColormap(TC_BLINK, color, GTC_CACHE);
}
for (i = 0; i < ZVOTE_PATCH_BAR_SEGS; i++)
{
bar = g_zBar[0];
if (i == 0)
{
bar = g_zBarEnds[0][flipped][0];
}
else if (i == ZVOTE_PATCH_BAR_SEGS - 1)
{
bar = g_zBarEnds[0][flipped][1];
}
if (fill < FRACUNIT)
{
V_DrawFixedPatch(
x, y,
FRACUNIT, flags,
bar, NULL
);
}
x += bar->width * FRACUNIT * sign;
}
if (fill > 0)
{
const INT32 fillSegs = FixedMul(fill, ZVOTE_PATCH_BAR_SEGS);
for (i = 0; i < fillSegs; i++)
{
x -= bar->width * FRACUNIT * sign;
bar = g_zBar[1];
if (i == fillSegs - 1)
{
bar = g_zBarEnds[1][flipped][0];
}
else if (i == 0)
{
bar = g_zBarEnds[1][flipped][1];
}
V_DrawFixedPatch(
x, y,
FRACUNIT, flags,
bar, clm
);
}
}
}
/*--------------------------------------------------
void K_DrawMidVote(void)
See header file for description.
--------------------------------------------------*/
void K_DrawMidVote(void)
{
const INT32 id = R_GetViewNumber();
midVoteGUI_t *gui = NULL;
boolean pressed = false;
fixed_t x = INT32_MAX, y = INT32_MAX;
pressed = G_PlayerInputDown(id, gc_z, 0);
gui = &g_midVote.gui[id];
if (gui->slide == 0)
{
// Draw the exclamation indicator.
patch_t *exc = g_exclamation;
x = 295 * FRACUNIT;
y = 127 * FRACUNIT;
if (g_midVote.time < ZVOTE_GUI_SLIDE)
{
x += ((ZVOTE_GUI_SLIDE - g_midVote.time) * (ZVOTE_GUI_SLIDE - g_midVote.time)) << (FRACBITS - 1);
exc = g_exclamationSlide;
}
else
{
const tic_t spd = 2;
const tic_t anim = (g_midVote.time - ZVOTE_GUI_SLIDE) / spd;
const UINT8 frame = anim % (ZVOTE_PATCH_EXC_LOOP + ZVOTE_GUI_SLIDE);
if (frame < ZVOTE_PATCH_EXC_LOOP)
{
if (anim > ZVOTE_GUI_SLIDE)
{
exc = g_exclamationLoop[frame];
}
else
{
exc = g_exclamationStart[frame];
}
}
}
V_DrawFixedPatch(
x, y, FRACUNIT,
V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN,
exc, NULL
);
K_DrawGameControl(
x/FRACUNIT - 4, y/FRACUNIT + exc->height - 8,
id, pressed ? "<z_pressed>" : "<z>",
0, 8, V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN
);
/*
K_drawButton(
x - (4 * FRACUNIT),
y + ((exc->height - kp_button_z[1][0]->height) * FRACUNIT),
V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN,
kp_button_z[1], pressed
);
*/
}
else
{
// Draw the actual vote status
const fixed_t barHalf = (g_zBar[0]->width * FRACUNIT * (ZVOTE_PATCH_BAR_SEGS - 1)) >> 1;
const boolean blink = (gametic & 1);
boolean drawButton = blink;
boolean drawVotes = blink;
fixed_t strWidth = 0;
fixed_t fill = FRACUNIT;
skincolornum_t fillColor = SKINCOLOR_NONE;
x = (BASEVIDWIDTH * FRACUNIT) - barHalf;
y = 144 * FRACUNIT;
if (gui->slide < ZVOTE_GUI_SLIDE)
{
x += ((ZVOTE_GUI_SLIDE - gui->slide) * (ZVOTE_GUI_SLIDE - gui->slide)) << (FRACBITS - 1);
}
// Hold bar
if (g_midVote.end > 0)
{
if (g_midVote.endVotes >= g_midVote.endRequired)
{
fillColor = SKINCOLOR_GREEN;
}
else
{
fillColor = SKINCOLOR_RED;
}
}
else
{
if (gui->confirm < ZVOTE_GUI_CONFIRM)
{
fill = FixedDiv(gui->confirm, ZVOTE_GUI_CONFIRM);
fillColor = SKINCOLOR_WHITE;
}
}
K_DrawMidVoteBar(
x - barHalf, y,
V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN,
fill, fillColor,
(id & 1)
);
const char *label = K_GetMidVoteLabel(g_midVote.type);
// Vote main label
strWidth = V__OneScaleStringWidth(
FRACUNIT,
V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN,
KART_FONT, label
);
V__DrawOneScaleString(
x - (strWidth >> 1),
y - (18 * FRACUNIT),
FRACUNIT,
V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN, NULL,
KART_FONT, label
);
// Vote extra text
switch (g_midVote.type)
{
case MVT_KICK:
{
// Draw victim name
if (g_midVote.victim != NULL)
{
strWidth = V__OneScaleStringWidth(
FRACUNIT,
V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN,
TINY_FONT, player_names[g_midVote.victim - players]
);
V__DrawOneScaleString(
x - (strWidth >> 1),
y + (18 * FRACUNIT),
FRACUNIT,
V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN, NULL,
TINY_FONT, player_names[g_midVote.victim - players]
);
}
break;
}
default:
{
break;
}
}
// Button
if (g_midVote.end == 0)
{
drawButton = true;
}
if (K_PlayerIDAllowedInMidVote(g_localplayers[id]) == false)
{
// Player isn't allowed to vote, so don't show it.
drawButton = false;
}
if (drawButton == true)
{
K_DrawGameControl(
x/FRACUNIT-20, y/FRACUNIT + 2, id,
pressed ? "<z_pressed>" : "<z>",
0, 8, V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN
);
/*
K_drawButton(
x - (20 * FRACUNIT),
y - (2 * FRACUNIT),
V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN,
kp_button_z[0], pressed
);
*/
}
// Vote count
if (g_midVote.end == 0)
{
if (gui->confirm == ZVOTE_GUI_CONFIRM || pressed == false)
{
drawVotes = true;
}
}
if (drawVotes == true)
{
const fixed_t voteY = y + (2 * FRACUNIT);
fixed_t voteX = x + (8 * FRACUNIT);
fixed_t voteWidth = 0;
UINT8 votes = 0;
UINT8 require = 0;
if (g_midVote.end > 0)
{
votes = g_midVote.endVotes;
require = g_midVote.endRequired;
}
else
{
votes = K_CountMidVotes();
require = K_RequiredMidVotes();
}
voteWidth = V__OneScaleStringWidth(
FRACUNIT,
V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN,
OPPRF_FONT, va("%d/%d", votes, require)
);
V__DrawOneScaleString(
voteX - (voteWidth >> 1),
voteY,
FRACUNIT,
V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN, NULL,
OPPRF_FONT, va("%d/%d", votes, require)
);
}
}
}