RingRacers/src/k_vote.c
Sally Coolatta 51fd4ea562 Don't animate vote planet for reduce vfx
... instead of the temporary black BG.
2024-04-13 16:55:54 -04:00

1923 lines
38 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_vote.c
/// \brief Voting screen
#include "k_vote.h"
#include "doomdef.h"
#include "doomstat.h"
#include "d_main.h"
#include "f_finale.h"
#include "g_game.h"
#include "hu_stuff.h"
#include "i_net.h"
#include "i_video.h"
#include "p_tick.h"
#include "r_defs.h"
#include "r_skins.h"
#include "s_sound.h"
#include "st_stuff.h"
#include "v_video.h"
#include "w_wad.h"
#include "y_inter.h"
#include "z_zone.h"
#include "k_menu.h"
#include "m_misc.h"
#include "i_system.h"
#include "p_setup.h"
#include "r_local.h"
#include "p_local.h"
#include "m_cond.h" // condition sets
#include "lua_hook.h" // IntermissionThinker hook
#include "lua_hud.h"
#include "lua_hudlib_drawlist.h"
#include "m_random.h" // M_RandomKey
#include "g_input.h" // G_PlayerInputDown
#include "k_hud.h" // K_DrawMapThumbnail
#include "k_battle.h"
#include "k_boss.h"
#include "k_pwrlv.h"
#include "k_grandprix.h"
#include "k_color.h"
#include "music.h"
#ifdef HWRENDER
#include "hardware/hw_main.h"
#endif
// Wait for any player to vote before starting the timer.
// Disabled because a player can join and be sent to
// the waiting screen, and if there's only 1 player in
// the vote screen they can idle for as long as they want,
// effectively locking the server up.
// This can be re-enabled if you want to send the vote
// screen in gamestate. (I don't feel like it.)
//#define VOTE_TIME_WAIT_FOR_VOTE
#define UNLOAD(x) if (x) {Patch_Free(x);} x = NULL;
#define CLEANUP(x) x = NULL;
#define PLANET_FRAMES (9)
#define TEXT_LEVEL_SCROLL (2*FRACUNIT)
#define TEXT_DERR_SCROLL (2*FRACUNIT)
#define ARM_FRAMES (4)
#define BULB_FRAMES (4)
#define SELECTOR_FRAMES (2)
#define CATCHER_SPEED (8*FRACUNIT)
#define CATCHER_Y_OFFSET (48*FRACUNIT)
#define CATCHER_OFFSCREEN (-CATCHER_Y_OFFSET * 2)
#define CATCHER_Y_OFFSET_SMALL (CATCHER_Y_OFFSET / 2)
#define SELECTION_WIDTH (72*FRACUNIT)
#define SELECTION_HEIGHT ((SELECTION_WIDTH * BASEVIDHEIGHT) / BASEVIDWIDTH)
#define SELECTION_X (10*FRACUNIT + (SELECTION_WIDTH >> 1))
#define SELECTION_Y (144*FRACUNIT + (SELECTION_HEIGHT >> 1))
#define SELECTION_SPACE (4*FRACUNIT)
#define SELECTION_SPACING_W (SELECTION_WIDTH + SELECTION_SPACE)
#define SELECTION_SPACING_H (SELECTION_HEIGHT + SELECTION_SPACE)
#define SELECTION_HOP (10*FRACUNIT)
#define SELECTOR_SPACE (8*FRACUNIT)
#define SELECTOR_Y ((SELECTION_HEIGHT / 2) + SELECTOR_SPACE)
#define SELECTOR_HEIGHT ((30*FRACUNIT) + SELECTOR_SPACE)
#define PILE_WIDTH (46*FRACUNIT)
#define PILE_HEIGHT ((PILE_WIDTH * BASEVIDHEIGHT) / BASEVIDWIDTH)
#define PILE_SPACE (4*FRACUNIT)
#define PILE_SPACING_W (PILE_WIDTH + PILE_SPACE)
#define PILE_SPACING_H (PILE_HEIGHT + PILE_SPACE)
#define LOTS_OF_VOTES_X (120*FRACUNIT)
#define LOTS_OF_VOTES_Y (80*FRACUNIT)
// Give time for the animations to finish before finalizing the vote stages.
#define SELECT_DELAY_TIME (TICRATE*4)
#define PICK_DELAY_TIME (TICRATE/2)
#define MAP_ANGER_MAX (2)
// Catcher data
enum
{
CATCHER_NA = 0,
CATCHER_FG_LOWER,
CATCHER_FG_GRAB,
CATCHER_FG_STRUGGLE,
CATCHER_FG_POPUP,
CATCHER_FG_RISE,
CATCHER_BG_LOWER,
CATCHER_BG_RELEASE,
CATCHER_BG_RISE,
};
typedef struct
{
fixed_t x, y;
fixed_t destX, destY;
UINT8 spr;
boolean small;
UINT8 action;
tic_t delay;
SINT8 level;
UINT8 player;
fixed_t anim; // UI scope variable
} y_vote_catcher;
// Clientside & splitscreen player info.
typedef struct
{
y_vote_catcher catcher;
SINT8 selection;
UINT8 delay;
boolean sentTimeOutVote;
fixed_t x, destX;
} y_vote_player;
// Vote "pile" data. Objects for each vote scattered about.
typedef struct
{
fixed_t x, y;
fixed_t destX, destY;
y_vote_catcher catcher;
} y_vote_pile;
// Voting roulette variables.
typedef struct
{
y_vote_pile pile[VOTE_TOTAL];
UINT8 anim;
UINT8 tics;
UINT32 offset;
UINT32 endOffset;
UINT8 syncTime;
} y_vote_roulette;
// General vote variables
typedef struct
{
INT32 timer;
INT32 tic, endtic;
INT32 selectFinalize, pickFinalize;
boolean notYetPicked;
boolean loaded;
SINT8 deferredLevel;
y_vote_player players[MAXSPLITSCREENPLAYERS];
y_vote_roulette roulette;
} y_vote_data;
// Voting level drawing
typedef struct
{
char str[128];
size_t str_len;
boolean encore;
fixed_t hop;
} y_vote_draw_level;
// Voting selector drawing
typedef struct
{
fixed_t x;
fixed_t destX;
} y_vote_draw_selector;
// General vote drawing
typedef struct
{
patch_t *ruby_icon;
fixed_t ruby_height;
patch_t *bg_planet[PLANET_FRAMES];
patch_t *bg_checker;
patch_t *bg_levelText;
patch_t *bg_derrText;
patch_t *catcher_ufo[2];
patch_t *catcher_arms[2][ARM_FRAMES];
patch_t *catcher_pole[2];
patch_t *catcher_bulb[2][BULB_FRAMES];
fixed_t selectTransition;
y_vote_draw_level levels[VOTE_NUM_LEVELS];
patch_t *selector_arrow;
patch_t *selector_letter[MAXSPLITSCREENPLAYERS][2];
y_vote_draw_selector selectors[MAXSPLITSCREENPLAYERS];
} y_vote_draw;
static y_vote_data vote = {0};
static y_vote_draw vote_draw = {0};
boolean Y_PlayerIDCanVote(const UINT8 playerId)
{
player_t *player = NULL;
if (playerId == VOTE_SPECIAL)
{
// Special vote spot, always allow
return true;
}
if (playerId >= MAXPLAYERS || playeringame[playerId] == false)
{
return false;
}
player = &players[playerId];
if (player->spectator == true)
{
return false;
}
if (player->bot == true && cv_botscanvote.value == 0)
{
// Bots may only vote if the server allows it
return false;
}
return true;
}
static boolean Y_PlayerCanSelect(const UINT8 localId)
{
const UINT8 p = g_localplayers[localId];
if (g_pickedVote != VOTE_NOT_PICKED)
{
return false;
}
if (g_votes[p] != VOTE_NOT_PICKED)
{
return false;
}
if (vote.players[localId].catcher.action != CATCHER_NA
|| vote.players[localId].catcher.delay > 0)
{
return false;
}
return Y_PlayerIDCanVote(p);
}
static void Y_SortPile(void)
{
UINT8 numVotes = 0;
UINT8 votesLeft = 0;
INT32 i;
for (i = 0; i < VOTE_TOTAL; i++)
{
if (g_votes[i] == VOTE_NOT_PICKED)
{
continue;
}
numVotes++;
}
if (numVotes == 0)
{
return;
}
votesLeft = numVotes;
for (i = 0; i < VOTE_TOTAL; i++)
{
y_vote_pile *const pile = &vote.roulette.pile[i];
if (g_votes[i] == VOTE_NOT_PICKED)
{
continue;
}
// Just center it for now.
pile->destX = BASEVIDWIDTH << FRACBITS >> 1;
pile->destY = BASEVIDHEIGHT << FRACBITS >> 1;
if (numVotes <= 1)
{
; // NOP
}
else if (numVotes == 2)
{
// Offset just a bit from the center.
if (votesLeft <= 1)
{
pile->destX += PILE_SPACING_W >> 1;
}
else
{
pile->destX -= PILE_SPACING_W >> 1;
}
}
else if (numVotes <= 12)
{
const boolean odd = ((numVotes % 2) != 0);
UINT8 rowSize = (numVotes + 1) / 2;
INT32 xOffset = 0;
if (votesLeft > rowSize)
{
SINT8 topRowIndex = (rowSize - ((votesLeft - 1) % rowSize)) - 1;
if (odd == true)
{
rowSize--;
topRowIndex--;
}
xOffset = -(rowSize - 1) + (topRowIndex << 1);
pile->destY -= PILE_SPACING_H >> 1;
}
else
{
const SINT8 botRowIndex = votesLeft - 1;
xOffset = -(rowSize - 1) + (botRowIndex << 1);
pile->destY += PILE_SPACING_H >> 1;
}
pile->destX += (PILE_SPACING_W >> 1) * xOffset;
}
else
{
angle_t a = ANGLE_90 + (ANGLE_MAX / numVotes) * (votesLeft - 1);
pile->destX += FixedMul(LOTS_OF_VOTES_X, FINECOSINE(a >> ANGLETOFINESHIFT));
pile->destY += FixedMul(LOTS_OF_VOTES_Y, -FINESINE(a >> ANGLETOFINESHIFT));
}
votesLeft--;
if (votesLeft == 0)
{
break;
}
}
}
void Y_SetPlayersVote(const UINT8 playerId, SINT8 newVote)
{
y_vote_pile *const pile = &vote.roulette.pile[playerId];
y_vote_catcher *const catcher = &pile->catcher;
if (gamestate != GS_VOTING)
{
return;
}
if (newVote < 0 || newVote >= VOTE_NUM_LEVELS)
{
newVote = VOTE_NOT_PICKED;
}
g_votes[playerId] = newVote;
Y_SortPile();
pile->x = pile->destX;
pile->y = pile->destY;
catcher->action = CATCHER_BG_LOWER;
catcher->x = catcher->destX = pile->x;
catcher->y = CATCHER_OFFSCREEN;
catcher->destY = pile->y;
catcher->spr = ARM_FRAMES-1;
catcher->level = g_votes[playerId];
catcher->player = playerId;
#ifdef VOTE_TIME_WAIT_FOR_VOTE
if (vote.timer == -1)
{
// Someone has voted, so start the timer now.
vote.timer = cv_votetime.value * TICRATE;
}
#endif
}
static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t width, INT32 flags, SINT8 v, boolean dim, SINT8 playerID)
{
const boolean encore = vote_draw.levels[v].encore;
const fixed_t height = (width * BASEVIDHEIGHT) / BASEVIDWIDTH;
const fixed_t x = center_x - (width >> 1);
const fixed_t y = center_y - (height >> 1);
INT32 fx, fy, fw, fh;
INT32 dupx, dupy;
if (v < 0 || v >= VOTE_NUM_LEVELS)
{
return;
}
dupx = vid.dupx;
dupy = vid.dupy;
if (flags & V_SCALEPATCHMASK)
{
switch ((flags & V_SCALEPATCHMASK) >> V_SCALEPATCHSHIFT)
{
case 1: // V_NOSCALEPATCH
dupx = dupy = 1;
break;
case 2: // V_SMALLSCALEPATCH
dupx = vid.smalldupx;
dupy = vid.smalldupy;
break;
case 3: // V_MEDSCALEPATCH
dupx = vid.meddupx;
dupy = vid.meddupy;
break;
default:
break;
}
}
// only use one dup, to avoid stretching (har har)
dupx = dupy = (dupx < dupy ? dupx : dupy);
fx = FixedMul(x, dupx << FRACBITS) >> FRACBITS;
fy = FixedMul(y, dupy << FRACBITS) >> FRACBITS;
fw = FixedMul(width - 1, dupx << FRACBITS) >> FRACBITS; // Why does only this need -1 to match up? IDFK
fh = FixedMul(height, dupy << FRACBITS) >> FRACBITS;
V_AdjustXYWithSnap(&fx, &fy, flags, dupx, dupy);
V_DrawFill(
fx - dupx, fy - dupy,
fw + (dupx << 1), fh + (dupy << 1),
0|flags|V_NOSCALESTART
);
K_DrawMapThumbnail(
x, y,
width, flags | ((encore == true) ? V_FLIP : 0),
g_voteLevels[v][0],
(dim == true ? R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE) : NULL)
);
if (encore == true)
{
const fixed_t rubyScale = width / 72;
V_DrawFixedPatch(
center_x, center_y - FixedMul(vote_draw.ruby_height << 1, rubyScale),
rubyScale, flags,
vote_draw.ruby_icon,
NULL
);
}
if (dim == true)
{
V_DrawFadeFill(
fx, fy,
fw, fh,
flags|V_NOSCALESTART,
31, 5
);
}
if (playerID >= 0)
{
const INT32 whiteSq = 16 * dupx;
if (playerID < MAXPLAYERS)
{
UINT8 *playerMap = R_GetTranslationColormap(players[playerID].skin, players[playerID].skincolor, GTC_CACHE);
patch_t *playerPatch = faceprefix[players[playerID].skin][FACE_RANK];
V_DrawFixedPatch(
(fx + fw - whiteSq + dupx) * FRACUNIT,
(fy + fh - whiteSq + dupy) * FRACUNIT,
FRACUNIT, flags|V_NOSCALESTART,
playerPatch, playerMap
);
}
else
{
const fixed_t iconHeight = (14 << FRACBITS);
const fixed_t iconWidth = (iconHeight * 320) / 200;
V_DrawFill(
fx + fw - whiteSq + dupx,
fy + fh - whiteSq + dupy,
whiteSq,
whiteSq,
0|flags|V_NOSCALESTART
);
V_SetClipRect(
fx + fw - whiteSq + (2 * dupx),
fy + fh - whiteSq + (2 * dupy),
whiteSq - (2 * dupx),
whiteSq - (2 * dupy),
flags|V_NOSCALESTART
);
K_DrawMapThumbnail(
((fx + fw - whiteSq + (2 * dupx)) * FRACUNIT) - (iconWidth - iconHeight),
(fy + fh - whiteSq + (2 * dupy)) * FRACUNIT,
iconWidth,
flags | V_NOSCALESTART | ((encore == true) ? V_FLIP : 0),
g_voteLevels[v][0],
NULL
);
V_ClearClipRect();
}
}
}
static void Y_DrawCatcher(y_vote_catcher *catcher)
{
#define NUM_UFO_COLORS (8)
static const skincolornum_t ufoColors[NUM_UFO_COLORS] = {
SKINCOLOR_EMERALD,
SKINCOLOR_SWAMP,
SKINCOLOR_TAFFY,
SKINCOLOR_ROSE,
SKINCOLOR_CYAN,
SKINCOLOR_NAVY,
SKINCOLOR_GOLD,
SKINCOLOR_BRONZE,
};
#define NUM_BULB_COLORS (2)
static const skincolornum_t bulbColors[NUM_BULB_COLORS] = {
SKINCOLOR_JAWZ,
SKINCOLOR_LILAC,
};
const UINT8 sizeOffset = (catcher->small == true) ? 1 : 0;
tic_t colorTic = 0;
fixed_t baseX = INT32_MAX;
fixed_t x = INT32_MAX;
fixed_t y = INT32_MAX;
UINT8 *craneColor = NULL;
UINT8 *bulbColor = NULL;
if (catcher->action == CATCHER_NA)
{
// Don't display in the empty state
return;
}
catcher->anim += renderdeltatics;
colorTic = (catcher->anim / 3) / FRACUNIT;
baseX = catcher->x;
if (catcher->action == CATCHER_FG_STRUGGLE)
{
if ((catcher->anim / FRACUNIT) & 1)
{
baseX += FRACUNIT;
}
else
{
baseX -= FRACUNIT;
}
}
x = baseX - (vote_draw.catcher_ufo[sizeOffset]->width * FRACUNIT / 2);
y = catcher->y - (vote_draw.catcher_ufo[sizeOffset]->height * FRACUNIT) + ((catcher->small == true) ? CATCHER_Y_OFFSET_SMALL : CATCHER_Y_OFFSET);
craneColor = R_GetTranslationColormap(TC_DEFAULT, ufoColors[colorTic % NUM_UFO_COLORS], GTC_MENUCACHE);
bulbColor = R_GetTranslationColormap(TC_DEFAULT, bulbColors[(colorTic / BULB_FRAMES) % NUM_BULB_COLORS], GTC_MENUCACHE);
if (catcher->level != VOTE_NOT_PICKED)
{
Y_DrawVoteThumbnail(
baseX, catcher->y,
((catcher->small == true) ? PILE_WIDTH : SELECTION_WIDTH), 0,
catcher->level, false,
catcher->player
);
}
V_DrawFixedPatch(
x, y,
FRACUNIT, 0,
vote_draw.catcher_arms[sizeOffset][catcher->spr % ARM_FRAMES],
craneColor
);
V_DrawFixedPatch(
x, y,
FRACUNIT, 0,
vote_draw.catcher_bulb[sizeOffset][colorTic % BULB_FRAMES],
bulbColor
);
V_DrawFixedPatch(
x, y,
FRACUNIT, 0,
vote_draw.catcher_ufo[sizeOffset],
craneColor
);
V_DrawFixedPatch(
x, y,
FRACUNIT, 0,
vote_draw.catcher_pole[sizeOffset],
NULL
);
#undef NUM_UFO_COLORS
#undef NUM_BULB_COLORS
}
//
// Y_VoteDrawer
//
// Draws the voting screen!
//
static void Y_DrawVoteBackground(void)
{
static UINT32 bgTimer = 0;
static fixed_t derrPos = 0;
const fixed_t derrLoop = vote_draw.bg_derrText->width * FRACUNIT;
static fixed_t levelPos = 0;
const fixed_t levelLoop = vote_draw.bg_levelText->height * FRACUNIT;
if (cv_reducevfx.value)
{
bgTimer = 0;
}
const UINT8 planetFrame = (bgTimer / FRACUNIT) % PLANET_FRAMES;
V_DrawFixedPatch(
0, 0,
FRACUNIT, 0,
vote_draw.bg_planet[planetFrame], NULL
);
V_DrawFixedPatch(
(BASEVIDWIDTH - vote_draw.bg_checker->width) * FRACUNIT, 0,
FRACUNIT, V_ADD|V_TRANSLUCENT,
vote_draw.bg_checker, NULL
);
V_DrawFixedPatch(
(BASEVIDWIDTH - vote_draw.bg_checker->width) * FRACUNIT, 0,
FRACUNIT, V_ADD|V_TRANSLUCENT,
vote_draw.bg_checker, NULL
);
levelPos += FixedMul(TEXT_DERR_SCROLL, renderdeltatics);
while (levelPos > levelLoop)
{
levelPos -= levelLoop;
}
V_DrawFixedPatch(
((BASEVIDWIDTH - vote_draw.bg_levelText->width) * FRACUNIT) - levelPos,
-levelPos,
FRACUNIT, V_ADD,
vote_draw.bg_levelText, NULL
);
V_DrawFixedPatch(
((BASEVIDWIDTH - vote_draw.bg_levelText->width) * FRACUNIT) - levelPos + levelLoop,
-levelPos + levelLoop,
FRACUNIT, V_ADD,
vote_draw.bg_levelText, NULL
);
derrPos += FixedMul(TEXT_DERR_SCROLL, renderdeltatics);
while (derrPos > derrLoop)
{
derrPos -= derrLoop;
}
V_DrawFixedPatch(
-derrPos,
(BASEVIDHEIGHT - vote_draw.bg_derrText->height) * FRACUNIT,
FRACUNIT, V_SUBTRACT,
vote_draw.bg_derrText, NULL
);
V_DrawFixedPatch(
-derrPos + derrLoop,
(BASEVIDHEIGHT - vote_draw.bg_derrText->height) * FRACUNIT,
FRACUNIT, V_SUBTRACT,
vote_draw.bg_derrText, NULL
);
if (!cv_reducevfx.value)
{
bgTimer += renderdeltatics;
}
}
static void Y_DrawVoteSelector(const fixed_t y, const fixed_t time, const UINT8 localPlayer)
{
const fixed_t destX = SELECTION_X + (vote.players[localPlayer].selection * SELECTION_SPACING_W);
vote_draw.selectors[localPlayer].x += FixedMul(
(destX - vote_draw.selectors[localPlayer].x) * 3 / 4,
renderdeltatics
);
if (Y_PlayerCanSelect(localPlayer) == false)
{
return;
}
static const UINT8 freq = 7;
UINT8 *colormap = NULL;
if (splitscreen > 0)
{
const UINT8 blink = ((time / freq / FRACUNIT) & 1);
colormap = R_GetTranslationColormap(TC_RAINBOW, players[ g_localplayers[localPlayer] ].skincolor, GTC_CACHE);
V_DrawFixedPatch(
vote_draw.selectors[localPlayer].x, y - SELECTOR_Y - (9*FRACUNIT),
FRACUNIT, 0,
vote_draw.selector_letter[localPlayer][blink],
colormap
);
}
fixed_t bob = FixedMul((time / freq * 2) + (FRACUNIT / 2), ANGLE_90);
if (localPlayer & 1)
{
bob = FCOS(bob);
}
else
{
bob = FSIN(bob);
}
V_DrawFixedPatch(
vote_draw.selectors[localPlayer].x, y - SELECTOR_Y + bob,
FRACUNIT, 0,
vote_draw.selector_arrow,
colormap
);
}
static void Y_DrawVoteSelection(fixed_t offset)
{
static fixed_t animTimer = 0;
animTimer += renderdeltatics;
const size_t charAnim = animTimer / FRACUNIT / 4;
fixed_t x = SELECTION_X;
fixed_t y = SELECTION_Y + FixedMul(offset, (SELECTION_HEIGHT + SELECTOR_HEIGHT) * 2);
INT32 i;
//
// Draw map icons
//
for (i = 0; i < VOTE_NUM_LEVELS; i++)
{
boolean selected = false;
fixed_t destHop = 0;
INT32 j;
for (j = 0; j <= splitscreen; j++) // another loop for drawing the selection backgrounds in the right order, grumble grumble..
{
const UINT8 p = g_localplayers[j];
if (vote.players[j].selection != i)
{
continue;
}
if (g_votes[p] != VOTE_NOT_PICKED || Y_PlayerIDCanVote(p) == false)
{
continue;
}
selected = true;
break;
}
if (selected == true)
{
destHop = SELECTION_HOP;
}
vote_draw.levels[i].hop += FixedMul(
(destHop - vote_draw.levels[i].hop) / 2,
renderdeltatics
);
if (vote_draw.levels[i].hop > FRACUNIT >> 2)
{
const fixed_t height = (SELECTION_WIDTH * BASEVIDHEIGHT) / BASEVIDWIDTH;
const fixed_t tx = x - (SELECTION_WIDTH >> 1);
const fixed_t ty = y + (height >> 1);
INT32 fx, fy, fw, fh;
INT32 dupx, dupy;
dupx = vid.dupx;
dupy = vid.dupy;
// only use one dup, to avoid stretching (har har)
dupx = dupy = (dupx < dupy ? dupx : dupy);
fx = FixedMul(tx, dupx << FRACBITS) >> FRACBITS;
fy = FixedMul(ty, dupy << FRACBITS) >> FRACBITS;
fw = FixedMul(SELECTION_WIDTH - 1, dupx << FRACBITS) >> FRACBITS; // Why does only this need -1 to match up? IDFK
fh = FixedMul(SELECTION_HOP, dupy << FRACBITS) >> FRACBITS;
V_AdjustXYWithSnap(&fx, &fy, 0, dupx, dupy);
V_DrawFill(
fx - dupx, fy - fh + dupy,
fw + (dupx << 1), fh,
31|V_NOSCALESTART
);
size_t ci;
for (ci = 0; ci < 12; ci++)
{
const size_t c = (ci + charAnim) % vote_draw.levels[i].str_len;
V_DrawCharacterScaled(
(fx + (6 * dupx * ci)) << FRACBITS,
(fy - fh + dupy) << FRACBITS,
FRACUNIT,
V_ORANGEMAP | V_FORCEUPPERCASE | V_NOSCALESTART,
MED_FONT,
vote_draw.levels[i].str[c],
NULL
);
}
}
Y_DrawVoteThumbnail(
x, y - vote_draw.levels[i].hop,
SELECTION_WIDTH, 0,
i, (selected == false),
-1
);
x += SELECTION_SPACING_W;
}
//
// Draw our catchers
//
for (i = 0; i <= splitscreen; i++)
{
Y_DrawCatcher(&vote.players[i].catcher);
}
if (offset != FRACUNIT)
{
//
// Draw splitscreen selectors
//
//if (splitscreen > 0)
{
const UINT8 priority = vote.tic % (splitscreen + 1);
for (i = 0; i <= splitscreen; i++)
{
if (i == priority)
{
continue;
}
Y_DrawVoteSelector(y, animTimer, i);
}
Y_DrawVoteSelector(y, animTimer, priority);
}
}
}
static void Y_DrawVotePile(void)
{
INT32 i;
for (i = 0; i < VOTE_TOTAL; i++)
{
y_vote_pile *const pile = &vote.roulette.pile[i];
y_vote_catcher *const catcher = &pile->catcher;
if (catcher->level != VOTE_NOT_PICKED)
{
continue;
}
if (g_votes[i] == VOTE_NOT_PICKED)
{
continue;
}
Y_DrawVoteThumbnail(
pile->x, pile->y,
PILE_WIDTH, 0,
g_votes[i],
(i != vote.roulette.anim || g_pickedVote == VOTE_NOT_PICKED),
i
);
}
for (i = 0; i < VOTE_TOTAL; i++)
{
Y_DrawCatcher(&vote.roulette.pile[i].catcher);
}
}
void Y_VoteDrawer(void)
{
static angle_t rubyFloatTime = 0;
if (rendermode == render_none)
{
return;
}
if (vote.tic >= vote.endtic && vote.endtic != -1)
{
return;
}
if (vote.loaded == false)
{
return;
}
vote_draw.ruby_height = FINESINE(rubyFloatTime >> ANGLETOFINESHIFT);
rubyFloatTime += FixedMul(ANGLE_MAX / NEWTICRATE, renderdeltatics);
if (vote.loaded == true)
{
boolean slideOut = true;
INT32 i;
for (i = 0; i <= splitscreen; i++)
{
if (g_votes[ g_localplayers[i] ] == VOTE_NOT_PICKED)
{
slideOut = false;
break;
}
}
vote_draw.selectTransition += FixedMul(
((slideOut ? FRACUNIT : 0) - vote_draw.selectTransition) / 2,
renderdeltatics
);
}
Y_DrawVoteBackground();
Y_DrawVotePile();
Y_DrawVoteSelection(vote_draw.selectTransition);
if (vote.timer > 0)
{
const INT32 tickDown = (vote.timer + 1) / TICRATE;
// See also y_inter.c
V__DrawOneScaleString(
2*FRACUNIT,
(BASEVIDHEIGHT - (2+8))*FRACUNIT,
FRACUNIT,
0, NULL,
OPPRF_FONT,
va("%d", tickDown)
);
}
M_DrawMenuForeground();
}
//
// Y_VoteStop
//
// Vote screen's selection stops moving
//
static void Y_FinalizeVote(const SINT8 level)
{
nextmap = g_voteLevels[level][0];
deferencoremode = ((g_voteLevels[level][1] & VOTE_MOD_ENCORE) == VOTE_MOD_ENCORE);
}
static void Y_VoteStops(SINT8 pick, SINT8 level)
{
Y_FinalizeVote(level);
if (netgame && P_IsPartyPlayer(&players[pick]))
{
S_StartSound(NULL, sfx_yeeeah); // yeeeah!
}
else
{
S_StartSound(NULL, sfx_kc48); // just a cool sound
}
}
static void Y_PlayerSendVote(const UINT8 localPlayer)
{
y_vote_player *const player = &vote.players[localPlayer];
y_vote_catcher *const catcher = &player->catcher;
catcher->action = CATCHER_FG_LOWER;
catcher->x = catcher->destX = SELECTION_X + (SELECTION_SPACING_W * player->selection);
catcher->y = CATCHER_OFFSCREEN;
catcher->destY = SELECTION_Y - SELECTION_HOP;
catcher->spr = 0;
catcher->level = VOTE_NOT_PICKED;
S_StartSound(NULL, sfx_kc37);
}
static boolean Y_TickGenericCatcher(y_vote_catcher *const catcher)
{
fixed_t spd = CATCHER_SPEED;
fixed_t xDelta = catcher->destX - catcher->x;
if (xDelta != 0)
{
// Move X position first
if (abs(xDelta) <= spd)
{
catcher->x = catcher->destX;
}
else
{
if (xDelta < 0)
{
catcher->x -= spd;
}
else
{
catcher->x += spd;
}
}
}
else
{
// Then start moving Y position
fixed_t yDelta = catcher->destY - catcher->y;
if (abs(yDelta) <= spd)
{
catcher->y = catcher->destY;
}
else
{
if (yDelta < 0)
{
catcher->y -= spd;
}
else
{
catcher->y += spd;
}
}
}
if (catcher->delay > 0)
{
catcher->delay--;
return false;
}
return true;
}
static void Y_TickPlayerCatcher(const UINT8 localPlayer)
{
y_vote_player *const player = &vote.players[localPlayer];
y_vote_catcher *const catcher = &player->catcher;
if (Y_TickGenericCatcher(catcher) == false)
{
return;
}
switch (catcher->action)
{
case CATCHER_FG_LOWER:
{
if (catcher->x == catcher->destX && catcher->y == catcher->destY)
{
catcher->action++;
S_StopSoundByNum(sfx_kc37);
S_StartSound(NULL, sfx_kc68);
}
break;
}
case CATCHER_FG_GRAB:
{
catcher->spr++;
if (catcher->spr >= ARM_FRAMES-1)
{
catcher->action = CATCHER_FG_STRUGGLE;
catcher->level = vote.players[localPlayer].selection;
catcher->delay = 20;
}
break;
}
case CATCHER_FG_STRUGGLE:
{
catcher->action = CATCHER_FG_POPUP;
catcher->destY -= SELECTION_HOP * 3;
catcher->delay = 15;
break;
}
case CATCHER_FG_POPUP:
{
catcher->action = CATCHER_FG_RISE;
catcher->destY = CATCHER_OFFSCREEN;
S_StartSound(NULL, sfx_kc37);
break;
}
case CATCHER_FG_RISE:
{
if (catcher->x == catcher->destX && catcher->y == catcher->destY)
{
D_ModifyClientVote(g_localplayers[localPlayer], vote.players[localPlayer].selection);
catcher->action = CATCHER_NA;
catcher->delay = 5;
S_StopSoundByNum(sfx_kc37);
}
break;
}
default:
{
catcher->action = CATCHER_NA;
break;
}
}
}
static void Y_TickPileCatcher(const UINT8 playerId)
{
y_vote_pile *const pile = &vote.roulette.pile[playerId];
y_vote_catcher *const catcher = &pile->catcher;
if (Y_TickGenericCatcher(catcher) == false)
{
return;
}
switch (catcher->action)
{
case CATCHER_BG_LOWER:
{
if (catcher->x == catcher->destX && catcher->y == catcher->destY)
{
catcher->level = VOTE_NOT_PICKED;
catcher->action++;
}
break;
}
case CATCHER_BG_RELEASE:
{
catcher->spr--;
if (catcher->spr == 0)
{
catcher->destY = CATCHER_OFFSCREEN;
catcher->action = CATCHER_BG_RISE;
}
break;
}
case CATCHER_BG_RISE:
{
if (catcher->x == catcher->destX && catcher->y == catcher->destY)
{
catcher->action = CATCHER_NA;
}
break;
}
default:
{
catcher->action = CATCHER_NA;
break;
}
}
}
static void Y_TickPlayerPile(const UINT8 playerId)
{
y_vote_pile *const pile = &vote.roulette.pile[playerId];
y_vote_catcher *const catcher = &pile->catcher;
fixed_t movedX = 0;
fixed_t movedY = 0;
if (g_votes[playerId] == VOTE_NOT_PICKED)
{
catcher->action = CATCHER_NA;
return;
}
movedX = (pile->destX - pile->x) / 2;
movedY = (pile->destY - pile->y) / 2;
if (movedX != 0 || movedY != 0)
{
pile->x += movedX;
pile->y += movedY;
catcher->x += movedX;
catcher->y += movedY;
catcher->destX += movedX;
catcher->destY += movedY;
}
Y_TickPileCatcher(playerId);
}
static void Y_TickVoteRoulette(void)
{
INT32 i;
vote.timer = 0;
vote.roulette.syncTime++;
if (vote.endtic == -1)
{
UINT8 tempvotes[VOTE_TOTAL];
UINT8 numvotes = 0;
for (i = 0; i < VOTE_TOTAL; i++)
{
if (g_votes[i] == VOTE_NOT_PICKED)
{
continue;
}
tempvotes[numvotes] = i;
numvotes++;
}
if (numvotes < 1) // Whoops! Get outta here.
{
Y_EndVote();
G_AfterIntermission();
return;
}
if (vote.roulette.tics > 0)
{
vote.roulette.tics--;
}
else
{
vote.roulette.offset++;
vote.roulette.tics = min(5, 7 * vote.roulette.offset / 40);
S_StartSound(NULL, sfx_kc39);
}
if (vote.roulette.endOffset == 0 || vote.roulette.offset < vote.roulette.endOffset)
{
vote.roulette.anim = tempvotes[((g_pickedVote + vote.roulette.offset) % numvotes)];
}
if (vote.roulette.offset > 20)
{
if (vote.roulette.endOffset == 0)
{
if (vote.roulette.syncTime % 51 == 0) // Song is 1.45 seconds long (sorry @ whoever wants to replace it in a music wad :V)
{
for (i = 5; i >= 3; i--) // Find a suitable place to stop
{
if (tempvotes[((g_pickedVote + vote.roulette.offset + i) % numvotes)] == g_pickedVote)
{
vote.roulette.endOffset = vote.roulette.offset + i;
if (M_RandomChance(FRACUNIT/4)) // Let it cheat occasionally~
{
vote.roulette.endOffset++;
}
Music_Play("vote_end");
break;
}
}
}
}
else if (vote.roulette.offset >= vote.roulette.endOffset)
{
vote.endtic = vote.tic + (3*TICRATE);
Y_VoteStops(g_pickedVote, vote.deferredLevel);
}
}
}
else
{
vote.roulette.anim = g_pickedVote;
}
}
static void Y_TryMapAngerVote(void)
{
SINT8 angryMaps[VOTE_NUM_LEVELS] = { -1 };
size_t angryMapsCount = 0;
boolean mapVoted[VOTE_NUM_LEVELS] = { false };
INT32 pick = 0;
INT32 numPlayers = 0;
INT32 i = 0;
for (i = 0; i < MAXPLAYERS; i++)
{
if (Y_PlayerIDCanVote(i) == false)
{
continue;
}
numPlayers++;
if (g_votes[i] != VOTE_NOT_PICKED)
{
mapVoted[ g_votes[i] ] = true;
}
}
if (numPlayers < 3)
{
// Don't handle map anger if there's not enough players.
return;
}
for (i = 0; i < VOTE_NUM_LEVELS; i++)
{
const INT16 mapID = g_voteLevels[i][0];
if (mapVoted[i] == true)
{
// Someone voted for us, no need to be angry anymore :)
mapheaderinfo[ mapID ]->anger = 0;
}
else
{
// Increment map anger for maps that weren't picked by a single soul.
mapheaderinfo[ mapID ]->anger++;
if (mapheaderinfo[ mapID ]->anger > MAP_ANGER_MAX)
{
// If they are angry enough, then it can vote for itself!
angryMaps[ angryMapsCount ] = i;
angryMapsCount++;
}
}
}
if (angryMapsCount == 0)
{
return;
}
// Set the special vote to a random angry map.
pick = M_RandomKey(angryMapsCount);
D_ModifyClientVote(UINT8_MAX, angryMaps[pick]);
}
static void Y_TickVoteSelection(void)
{
boolean everyone_voted = true;/* the default condition */
INT32 i;
if (vote.tic < 3*(NEWTICRATE/7)) // give it some time before letting you control it :V
{
return;
}
/*
The vote ended, but it will take at least a tic for that to reach us from
the server. Don't let me change the vote now, it won't matter anyway!
*/
for (i = 0; i <= splitscreen; i++)
{
boolean moved = false;
if (Y_PlayerCanSelect(i) == true)
{
if (vote.players[i].delay > 0)
{
vote.players[i].delay--;
}
else if (vote.timer == 0)
{
// Time's up, send our vote ASAP.
if (vote.players[i].sentTimeOutVote == false)
{
Y_PlayerSendVote(i);
vote.players[i].sentTimeOutVote = true;
vote.players[i].delay = NEWTICRATE/7;
}
}
else if (menuactive == false && chat_on == false)
{
if (G_PlayerInputDown(i, gc_left, 0))
{
vote.players[i].selection--;
moved = true;
}
if (G_PlayerInputDown(i, gc_right, 0))
{
vote.players[i].selection++;
moved = true;
}
if (vote.players[i].selection < 0)
{
vote.players[i].selection = VOTE_NUM_LEVELS - 1;
}
if (vote.players[i].selection >= VOTE_NUM_LEVELS)
{
vote.players[i].selection = 0;
}
if (G_PlayerInputDown(i, gc_a, 0) && moved == false)
{
Y_PlayerSendVote(i);
moved = true;
}
}
}
if (moved == true)
{
S_StartSound(NULL, sfx_kc4a);
vote.players[i].delay = NEWTICRATE/7;
}
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (Y_PlayerIDCanVote(i) == false)
{
continue;
}
if (players[i].bot == true && g_votes[i] == VOTE_NOT_PICKED)
{
if (( M_RandomFixed() % 100 ) == 0)
{
// bots vote randomly
D_ModifyClientVote(i, M_RandomKey(VOTE_NUM_LEVELS));
}
}
if (g_votes[i] == VOTE_NOT_PICKED)
{
everyone_voted = false;
}
}
if (everyone_voted == true)
{
vote.timer = 0;
vote.selectFinalize = SELECT_DELAY_TIME;
}
if (vote.timer == 0)
{
if (vote.selectFinalize < SELECT_DELAY_TIME)
{
vote.selectFinalize++;
}
}
else
{
vote.selectFinalize = 0;
}
if (vote.selectFinalize >= SELECT_DELAY_TIME)
{
if (vote.pickFinalize < PICK_DELAY_TIME)
{
vote.pickFinalize++;
}
else if (vote.endtic == -1)
{
vote.notYetPicked = false; /* don't pick vote twice */
if (server)
{
Y_TryMapAngerVote();
D_PickVote();
}
}
}
else if (vote.timer > 0)
{
vote.timer--;
vote.pickFinalize = 0;
}
}
//
// Y_VoteTicker
//
// Vote screen thinking :eggthinking:
//
void Y_VoteTicker(void)
{
INT32 i;
if (paused || P_AutoPause() || vote.loaded == false)
{
return;
}
LUA_HOOK(VoteThinker);
vote.tic++;
if (vote.tic == vote.endtic)
{
Y_EndVote();
G_AfterIntermission();
return;
}
// Correct invalid votes as early as possible,
// before they're processed by the rest of the ticker
for (i = 0; i < MAXPLAYERS; i++)
{
if (Y_PlayerIDCanVote(i) == false)
{
// Spectators are the lower class, and have
// effectively no voice in the government. Democracy sucks.
g_votes[i] = VOTE_NOT_PICKED;
}
}
if (server && g_pickedVote != VOTE_NOT_PICKED && g_votes[g_pickedVote] == VOTE_NOT_PICKED) // Uh oh! The person who got picked left! Recalculate, quick!
{
D_PickVote();
}
if (vote.tic == 0)
{
Music_Play("vote");
}
if (g_pickedVote != VOTE_NOT_PICKED)
{
Y_TickVoteRoulette();
}
else if (vote.notYetPicked == true)
{
Y_TickVoteSelection();
}
for (i = 0; i <= splitscreen; i++)
{
Y_TickPlayerCatcher(i);
}
Y_SortPile();
for (i = 0; i < VOTE_TOTAL; i++)
{
Y_TickPlayerPile(i);
}
}
//
// Y_StartVote
//
// MK online style voting screen, appears after intermission
//
static void Y_InitVoteDrawing(void)
{
INT32 i = 0, j = 0;
vote_draw.ruby_icon = W_CachePatchName("RUBYICON", PU_STATIC);
for (i = 0; i < PLANET_FRAMES; i++)
{
vote_draw.bg_planet[i] = W_CachePatchName(va("VT_BG_%d", i + 1), PU_STATIC);
}
vote_draw.bg_checker = W_CachePatchName("VT_RACE", PU_STATIC);
vote_draw.bg_levelText = W_CachePatchName("VT_WELC", PU_STATIC);
vote_draw.bg_derrText = W_CachePatchName("VT_DERR", PU_STATIC);
vote_draw.catcher_ufo[0] = W_CachePatchName("VT_UFO1", PU_STATIC);
vote_draw.catcher_ufo[1] = W_CachePatchName("VS_UFO1", PU_STATIC);
for (i = 0; i < ARM_FRAMES; i++)
{
vote_draw.catcher_arms[0][i] = W_CachePatchName(va("VT_ARMS%d", i + 1), PU_STATIC);
vote_draw.catcher_arms[1][i] = W_CachePatchName(va("VS_ARMS%d", i + 1), PU_STATIC);
}
vote_draw.catcher_pole[0] = W_CachePatchName("VT_POLE", PU_STATIC);
vote_draw.catcher_pole[1] = W_CachePatchName("VS_POLE", PU_STATIC);
for (i = 0; i < BULB_FRAMES; i++)
{
vote_draw.catcher_bulb[0][i] = W_CachePatchName(va("VT_BULB%d", i + 1), PU_STATIC);
vote_draw.catcher_bulb[1][i] = W_CachePatchName(va("VS_BULB%d", i + 1), PU_STATIC);
}
for (i = 0; i < VOTE_NUM_LEVELS; i++)
{
const mapheader_t *header = mapheaderinfo[g_voteLevels[i][0]];
// set up the encore
vote_draw.levels[i].encore = (g_voteLevels[i][1] & VOTE_MOD_ENCORE);
// set up the level title string
memset(vote_draw.levels[i].str, 0, sizeof(vote_draw.levels[i].str));
vote_draw.levels[i].str_len = 0;
vote_draw.levels[i].str_len += snprintf(
vote_draw.levels[i].str + vote_draw.levels[i].str_len,
sizeof(vote_draw.levels[i].str) - vote_draw.levels[i].str_len,
"%s",
header->lvlttl
);
if (header->zonttl[0])
{
vote_draw.levels[i].str_len += snprintf(
vote_draw.levels[i].str + vote_draw.levels[i].str_len,
sizeof(vote_draw.levels[i].str) - vote_draw.levels[i].str_len,
" %s",
header->zonttl
);
}
if (header->actnum > 0)
{
vote_draw.levels[i].str_len += snprintf(
vote_draw.levels[i].str + vote_draw.levels[i].str_len,
sizeof(vote_draw.levels[i].str) - vote_draw.levels[i].str_len,
" %d",
header->actnum
);
}
vote_draw.levels[i].str_len += snprintf(
vote_draw.levels[i].str + vote_draw.levels[i].str_len,
sizeof(vote_draw.levels[i].str) - vote_draw.levels[i].str_len,
" "
);
}
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
y_vote_player *const player = &vote.players[i];
vote_draw.selectors[i].x = vote_draw.selectors[i].destX = SELECTION_X + (player->selection * SELECTION_SPACING_W);
for (j = 0; j < SELECTOR_FRAMES; j++)
{
vote_draw.selector_letter[i][j] = W_CachePatchName(va("VSSPTR%c%d", 'A' + i, j + 1), PU_STATIC);
}
}
vote_draw.selector_arrow = W_CachePatchName("VSSPTR1", PU_STATIC);
vote_draw.selectTransition = FRACUNIT;
}
void Y_StartVote(void)
{
INT32 i = 0;
memset(&vote, 0, sizeof(vote));
memset(&vote_draw, 0, sizeof(vote_draw));
// Restarting vote from the menu: stop any long sounds
// that were playing (kc37).
S_StopSounds();
vote.tic = vote.endtic = -1;
#ifdef VOTE_TIME_WAIT_FOR_VOTE
vote.timer = -1; // Timer is not set until the first vote is added
#else
vote.timer = cv_votetime.value * TICRATE;
#endif
g_pickedVote = VOTE_NOT_PICKED;
vote.notYetPicked = true;
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
y_vote_player *const player = &vote.players[i];
y_vote_catcher *const catcher = &player->catcher;
player->selection = (i % VOTE_NUM_LEVELS);
catcher->action = CATCHER_NA;
catcher->small = false;
catcher->player = -1;
}
for (i = 0; i < VOTE_TOTAL; i++)
{
y_vote_pile *const pile = &vote.roulette.pile[i];
y_vote_catcher *const catcher = &pile->catcher;
g_votes[i] = VOTE_NOT_PICKED;
catcher->action = CATCHER_NA;
catcher->small = true;
catcher->player = i;
}
Y_InitVoteDrawing();
vote.loaded = true;
Automate_Run(AEV_VOTESTART);
}
//
// Y_UnloadVoteData
//
static void Y_UnloadVoteData(void)
{
INT32 i, j;
vote.loaded = false;
if (rendermode != render_soft)
{
return;
}
UNLOAD(vote_draw.ruby_icon);
for (i = 0; i < PLANET_FRAMES; i++)
{
UNLOAD(vote_draw.bg_planet[i]);
}
UNLOAD(vote_draw.bg_checker);
UNLOAD(vote_draw.bg_levelText);
UNLOAD(vote_draw.bg_derrText);
for (j = 0; j < 2; j++)
{
UNLOAD(vote_draw.catcher_ufo[j]);
for (i = 0; i < ARM_FRAMES; i++)
{
UNLOAD(vote_draw.catcher_arms[j][i]);
}
UNLOAD(vote_draw.catcher_pole[j]);
for (i = 0; i < BULB_FRAMES; i++)
{
UNLOAD(vote_draw.catcher_bulb[j][i]);
}
}
for (j = 0; j < MAXSPLITSCREENPLAYERS; j++)
{
for (i = 0; i < SELECTOR_FRAMES; i++)
{
UNLOAD(vote_draw.selector_letter[j][i]);
}
}
UNLOAD(vote_draw.selector_arrow);
}
//
// Y_EndVote
//
void Y_EndVote(void)
{
if (nextmap >= NEXTMAP_SPECIAL)
{
// Don't leave nextmap unset if the vote is ended through
// weird means! (such as a dedicated server becoming empty)
// If nextmap was left at NEXTMAP_VOTING, we'd crash!
Y_FinalizeVote(0);
}
Y_UnloadVoteData();
vote.endtic = -1;
}
//
// Y_SetupVoteFinish
//
enum
{
VOTE_END_IMMEDIATE = 0,
VOTE_END_QUICK,
VOTE_END_NORMAL,
};
void Y_SetupVoteFinish(SINT8 pick, SINT8 level)
{
if (vote.loaded == false)
{
return;
}
if (pick == VOTE_NOT_PICKED || level == VOTE_NOT_PICKED) // No other votes? We gotta get out of here, then!
{
Y_EndVote();
G_AfterIntermission();
return;
}
if (g_pickedVote == VOTE_NOT_PICKED)
{
INT32 i;
SINT8 votecompare = VOTE_NOT_PICKED;
INT32 endtype = VOTE_END_IMMEDIATE;
vote.roulette.syncTime = 0;
for (i = 0; i < VOTE_TOTAL; i++)
{
if (g_votes[i] == VOTE_NOT_PICKED)
{
continue;
}
if (votecompare == VOTE_NOT_PICKED)
{
votecompare = g_votes[i];
endtype = VOTE_END_QUICK;
}
else if (g_votes[i] != votecompare)
{
endtype = VOTE_END_NORMAL;
break;
}
}
switch (endtype)
{
case VOTE_END_IMMEDIATE:
{
// Might as well put it here, too, just in case.
Y_EndVote();
G_AfterIntermission();
return;
}
case VOTE_END_QUICK:
{
// Only one unique vote, so just end it immediately.
vote.endtic = vote.tic + (5*TICRATE);
Music_Play("vote_end");
Y_VoteStops(pick, level);
break;
}
default:
{
Music_Play("vote_suspense");
break;
}
}
}
vote.deferredLevel = level;
g_pickedVote = pick;
vote.timer = -1;
vote.selectFinalize = SELECT_DELAY_TIME;
vote.pickFinalize = PICK_DELAY_TIME;
}