mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
Merge branch 'duel' into 'master'
Duel See merge request kart-krew-dev/ring-racers-internal!2456
This commit is contained in:
commit
65213e1363
23 changed files with 1145 additions and 119 deletions
|
|
@ -783,6 +783,8 @@ consvar_t cv_timelimit = UnsavedNetVar("timelimit", "Default").min_max(1, 30*60,
|
|||
|
||||
consvar_t cv_votetime = UnsavedNetVar("votetime", "20").min_max(10, 3600);
|
||||
|
||||
consvar_t cv_dueltimelimit = UnsavedNetVar("dueltimelimit", "180").min_max(0, 3600);
|
||||
consvar_t cv_duelscorelimit = UnsavedNetVar("duelscorelimit", "4").min_max(1, 9);
|
||||
|
||||
//
|
||||
// Online cheats - synced in netgames.
|
||||
|
|
|
|||
|
|
@ -2291,11 +2291,11 @@ void D_SetupVote(INT16 newgametype)
|
|||
|
||||
void D_ModifyClientVote(UINT8 player, SINT8 voted)
|
||||
{
|
||||
char buf[2];
|
||||
char buf[3];
|
||||
char *p = buf;
|
||||
UINT8 sendPlayer = consoleplayer;
|
||||
UINT8 sendPlayer = 0;
|
||||
|
||||
if (player == UINT8_MAX)
|
||||
if (player >= MAXPLAYERS)
|
||||
{
|
||||
// Special game vote (map anger, duel)
|
||||
if (!server)
|
||||
|
|
@ -2304,16 +2304,16 @@ void D_ModifyClientVote(UINT8 player, SINT8 voted)
|
|||
}
|
||||
}
|
||||
|
||||
if (player == UINT8_MAX)
|
||||
{
|
||||
// special vote
|
||||
WRITEUINT8(p, UINT8_MAX);
|
||||
}
|
||||
else
|
||||
{
|
||||
INT32 i = 0;
|
||||
WRITEUINT8(p, player);
|
||||
// Context value -- if context has changed, then discard vote update.
|
||||
// This is to prevent votes being registered from different vote types.
|
||||
// Currently used for Duel vs Normal votes.
|
||||
WRITEUINT8(p, Y_VoteContext());
|
||||
|
||||
WRITEUINT8(p, player);
|
||||
|
||||
if (player <= MAXPLAYERS)
|
||||
{
|
||||
INT32 i;
|
||||
for (i = 0; i <= splitscreen; i++)
|
||||
{
|
||||
if (g_localplayers[i] == player)
|
||||
|
|
@ -5636,30 +5636,35 @@ static void Got_SetupVotecmd(const UINT8 **cp, INT32 playernum)
|
|||
|
||||
static void Got_ModifyVotecmd(const UINT8 **cp, INT32 playernum)
|
||||
{
|
||||
UINT8 context = READUINT8(*cp);
|
||||
UINT8 targetID = READUINT8(*cp);
|
||||
SINT8 vote = READSINT8(*cp);
|
||||
|
||||
if (targetID == UINT8_MAX)
|
||||
if (context != Y_VoteContext())
|
||||
{
|
||||
if (playernum != serverplayer) // server-only special vote
|
||||
// Silently discard. Server changed the
|
||||
// vote type as we were sending our vote.
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetID >= MAXPLAYERS)
|
||||
{
|
||||
// only the server is allowed to send these
|
||||
if (playernum != serverplayer)
|
||||
{
|
||||
goto fail;
|
||||
}
|
||||
|
||||
targetID = VOTE_SPECIAL;
|
||||
}
|
||||
else if (playeringame[targetID] == true && players[targetID].bot == true)
|
||||
{
|
||||
if (targetID >= MAXPLAYERS
|
||||
|| playernum != serverplayer)
|
||||
if (playernum != serverplayer)
|
||||
{
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (targetID >= MAXPLAYERS
|
||||
|| playernode[targetID] != playernode[playernum])
|
||||
if (playernode[targetID] != playernode[playernum])
|
||||
{
|
||||
goto fail;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ extern consvar_t cv_joyscale[MAXSPLITSCREENPLAYERS];
|
|||
|
||||
extern consvar_t cv_pointlimit;
|
||||
extern consvar_t cv_timelimit;
|
||||
extern consvar_t cv_dueltimelimit, cv_duelscorelimit;
|
||||
extern consvar_t cv_numlaps;
|
||||
extern UINT32 timelimitintics, extratimeintics, secretextratime;
|
||||
extern UINT32 g_pointlimit;
|
||||
|
|
|
|||
|
|
@ -957,6 +957,8 @@ struct player_t
|
|||
INT32 cheatchecknum; // The number of the last cheatcheck you hit
|
||||
INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp
|
||||
|
||||
INT16 duelscore;
|
||||
|
||||
UINT8 team; // 0 == Spectator, 1 == Red, 2 == Blue
|
||||
|
||||
UINT8 checkskip; // Skipping checkpoints? Oh no no no
|
||||
|
|
|
|||
|
|
@ -882,16 +882,24 @@ extern SINT8 spbplace;
|
|||
extern boolean rainbowstartavailable;
|
||||
extern tic_t linecrossed;
|
||||
extern boolean inDuel;
|
||||
extern UINT8 overtimecheckpoints;
|
||||
|
||||
extern tic_t bombflashtimer; // Used to avoid causing seizures if multiple mines explode close to you :)
|
||||
extern boolean legitimateexit;
|
||||
extern boolean comebackshowninfo;
|
||||
|
||||
#define VOTE_NUM_LEVELS (4)
|
||||
#define VOTE_NOT_PICKED (-1)
|
||||
#define VOTE_SPECIAL (MAXPLAYERS)
|
||||
#define VOTE_TOTAL (MAXPLAYERS+1)
|
||||
extern UINT16 g_voteLevels[4][2];
|
||||
|
||||
#define VOTE_TIMEOUT_LOSER (MAXPLAYERS+1) // not a real vote ID
|
||||
#define VOTE_TIMEOUT_WINNER (MAXPLAYERS+2) // ditto
|
||||
|
||||
extern UINT16 g_voteLevels[VOTE_NUM_LEVELS][2];
|
||||
extern SINT8 g_votes[VOTE_TOTAL];
|
||||
extern SINT8 g_pickedVote;
|
||||
extern boolean g_votes_striked[VOTE_NUM_LEVELS];
|
||||
|
||||
// ===========================
|
||||
// Internal parameters, fixed.
|
||||
|
|
|
|||
10
src/g_game.c
10
src/g_game.c
|
|
@ -297,9 +297,10 @@ boolean franticitems; // Frantic items currently enabled?
|
|||
boolean g_teamplay;
|
||||
|
||||
// Voting system
|
||||
UINT16 g_voteLevels[4][2]; // Levels that were rolled by the host
|
||||
UINT16 g_voteLevels[VOTE_NUM_LEVELS][2]; // Levels that were rolled by the host
|
||||
SINT8 g_votes[VOTE_TOTAL]; // Each player's vote
|
||||
SINT8 g_pickedVote; // What vote the host rolls
|
||||
boolean g_votes_striked[VOTE_NUM_LEVELS]; // Which levels were striked from votes?
|
||||
|
||||
// Server-sided, synched variables
|
||||
tic_t wantedcalcdelay; // Time before it recalculates WANTED
|
||||
|
|
@ -311,6 +312,7 @@ SINT8 spbplace; // SPB exists, give the person behind better items
|
|||
boolean rainbowstartavailable; // Boolean, keeps track of if the rainbow start was gotten
|
||||
tic_t linecrossed; // For Time Attack
|
||||
boolean inDuel; // Boolean, keeps track of if it is a 1v1
|
||||
UINT8 overtimecheckpoints; // Duel overtime speedups!
|
||||
|
||||
// Client-sided, unsynched variables (NEVER use in anything that needs to be synced with other players)
|
||||
tic_t bombflashtimer = 0; // Cooldown before another FlashPal can be intialized by a bomb exploding near a displayplayer. Avoids seizures.
|
||||
|
|
@ -3871,6 +3873,12 @@ tryAgain:
|
|||
continue;
|
||||
}
|
||||
|
||||
if (numPlayers == 2 && gametype == GT_RACE && ((mapheaderinfo[i]->levelflags & LF_SECTIONRACE) == LF_SECTIONRACE))
|
||||
{
|
||||
// Duel doesn't support sprints.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only care about restrictions if the host is a listen server.
|
||||
if (!dedicated)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2067,6 +2067,11 @@ void K_UpdateBotGameplayVars(player_t *player)
|
|||
if (cv_levelskull.value)
|
||||
player->botvars.difficulty = MAXBOTDIFFICULTY;
|
||||
|
||||
if (K_InRaceDuel())
|
||||
player->botvars.rival = true;
|
||||
else if (grandprixinfo.gp != true)
|
||||
player->botvars.rival = false;
|
||||
|
||||
player->botvars.rubberband = K_UpdateRubberband(player);
|
||||
|
||||
player->botvars.turnconfirm += player->cmd.bot.turnconfirm;
|
||||
|
|
|
|||
192
src/k_hud.cpp
192
src/k_hud.cpp
|
|
@ -235,6 +235,13 @@ static patch_t *kp_team_underlay[2][2];
|
|||
static patch_t *kp_team_minihead;
|
||||
static patch_t *kp_team_you;
|
||||
|
||||
static patch_t *kp_duel_foe;
|
||||
static patch_t *kp_duel_you;
|
||||
static patch_t *kp_duel_sticker;
|
||||
static patch_t *kp_duel_under;
|
||||
static patch_t *kp_duel_over;
|
||||
static patch_t *kp_duel_margin[6];
|
||||
|
||||
patch_t *kp_autoroulette;
|
||||
patch_t *kp_autoring;
|
||||
|
||||
|
|
@ -1061,6 +1068,16 @@ void K_LoadKartHUDGraphics(void)
|
|||
HU_UpdatePatch(&kp_team_underlay[1][1], "TEAM4UR");
|
||||
HU_UpdatePatch(&kp_team_minihead, "TEAM4H");
|
||||
HU_UpdatePatch(&kp_team_you, "TEAM_YOU");
|
||||
|
||||
HU_UpdatePatch(&kp_duel_foe, "DUEL_FOE");
|
||||
HU_UpdatePatch(&kp_duel_sticker, "DUEL_S");
|
||||
HU_UpdatePatch(&kp_duel_under, "DUEL_B");
|
||||
HU_UpdatePatch(&kp_duel_over, "DUEL_B2");
|
||||
HU_UpdatePatch(&kp_duel_you, "DUEL_YOU");
|
||||
for (i = 0; i < 6; i++)
|
||||
{
|
||||
HU_UpdatePatch(&kp_duel_margin[i], "DUELMB0%d", i);
|
||||
}
|
||||
}
|
||||
|
||||
// For the item toggle menu
|
||||
|
|
@ -2970,6 +2987,9 @@ static boolean K_drawKartPositionFaces(void)
|
|||
if (!LUA_HudEnabled(hud_minirankings))
|
||||
return false; // Don't proceed but still return true for free play above if HUD is disabled.
|
||||
|
||||
if (K_InRaceDuel())
|
||||
return false;
|
||||
|
||||
switch (r_splitscreen)
|
||||
{
|
||||
case 0:
|
||||
|
|
@ -3229,11 +3249,162 @@ INT32 K_GetTransFlagFromFixed(fixed_t value)
|
|||
}
|
||||
}
|
||||
|
||||
static tic_t duel_lastleveltime = 0;
|
||||
static INT32 youheight = 0;
|
||||
|
||||
static void K_drawKartDuelScores(void)
|
||||
{
|
||||
if (!K_InRaceDuel())
|
||||
return;
|
||||
|
||||
using srb2::Draw;
|
||||
|
||||
player_t *foe = K_DuelOpponent(stplyr);
|
||||
|
||||
INT32 basex = 0;
|
||||
INT32 basey = 40;
|
||||
INT32 flags = V_SNAPTOLEFT|V_HUDTRANS|V_SLIDEIN;
|
||||
|
||||
// score bars, here barheight is the size of bars at tied score
|
||||
INT32 barx = 8;
|
||||
INT32 bary = 61;
|
||||
INT32 barheight = 48;
|
||||
INT32 barwidth = 6;
|
||||
|
||||
// portraits
|
||||
INT32 foex = 16;
|
||||
INT32 foey = 21;
|
||||
INT32 youx = 16;
|
||||
INT32 youy = 85;
|
||||
|
||||
// scores
|
||||
INT32 foescorex = 16;
|
||||
INT32 foescorey = 38;
|
||||
INT32 youscorex = 16;
|
||||
INT32 youscorey = 69;
|
||||
|
||||
Draw::Font scorefont = Draw::Font::kThinTimer;
|
||||
|
||||
UINT8 ri = 6;
|
||||
INT32 youfill = skincolors[stplyr->skincolor].ramp[ri];
|
||||
INT32 foefill = skincolors[foe->skincolor].ramp[ri];
|
||||
|
||||
INT32 margin = std::min(overtimecheckpoints, (UINT8)5); // Absolutely what the fuck kind of cast.
|
||||
|
||||
V_DrawScaledPatch(basex, basey, flags, kp_duel_sticker);
|
||||
|
||||
INT32 scoredelta = stplyr->duelscore - foe->duelscore;
|
||||
INT32 clutchscore = DUELWINNINGSCORE - 1; // we want the bar to be full when NEXT checkpoint wins...
|
||||
INT32 savemargin = 3; // ...minus a little bit.
|
||||
|
||||
if (leveltime/(TICRATE/2) % 2)
|
||||
savemargin += ((leveltime/2)%2);
|
||||
|
||||
if (clutchscore == 0)
|
||||
clutchscore = 1; // Fuck it, just don't crash
|
||||
|
||||
INT32 targetyouheight = barheight*abs(clutchscore+scoredelta)/clutchscore;
|
||||
|
||||
if (targetyouheight == 0)
|
||||
{
|
||||
targetyouheight = savemargin;
|
||||
}
|
||||
else if (targetyouheight >= 2*barheight)
|
||||
{
|
||||
targetyouheight = 2*barheight - savemargin;
|
||||
}
|
||||
|
||||
if (leveltime != duel_lastleveltime)
|
||||
{
|
||||
INT32 slide = std::max(1, abs(targetyouheight - youheight)/3);
|
||||
if (targetyouheight > youheight)
|
||||
youheight += slide;
|
||||
else if (targetyouheight < youheight)
|
||||
youheight -= slide;
|
||||
}
|
||||
duel_lastleveltime = leveltime;
|
||||
|
||||
INT32 foeheight = 2*barheight-youheight; // barheight is a single tied bar, so total height of the full gauge is 2x barheight
|
||||
|
||||
V_DrawFill(basex+barx, basey+bary-barheight, barwidth, foeheight, foefill|flags);
|
||||
V_DrawFill(basex+barx, basey+bary-barheight+foeheight, barwidth, youheight, youfill|flags);
|
||||
|
||||
V_DrawScaledPatch(basex, basey, flags, kp_duel_under);
|
||||
V_DrawScaledPatch(basex, basey-barheight+foeheight, flags, kp_duel_over);
|
||||
V_DrawScaledPatch(basex, basey, flags, kp_duel_foe);
|
||||
V_DrawScaledPatch(basex, basey, flags, kp_duel_you);
|
||||
|
||||
Draw foenum = Draw(basex+foescorex, basey+foescorey).flags(flags).font(scorefont).align(Draw::Align::kLeft);
|
||||
Draw younum = Draw(basex+youscorex, basey+youscorey).flags(flags).font(scorefont).align(Draw::Align::kLeft);
|
||||
|
||||
if (abs(scoredelta) == clutchscore && ((leveltime % 2) || cv_reducevfx.value))
|
||||
{
|
||||
if (foe->duelscore > stplyr->duelscore)
|
||||
foenum = foenum.colorize(SKINCOLOR_GOLD);
|
||||
else
|
||||
younum = younum.colorize(SKINCOLOR_GOLD);
|
||||
}
|
||||
|
||||
foenum.text("{}", foe->duelscore);
|
||||
younum.text("{}", stplyr->duelscore);
|
||||
|
||||
// minirankings shamelessly copypasted because i know that shit works already
|
||||
// and SURELY we will never need to use this somewhere else, right?
|
||||
|
||||
UINT8 workingskin;
|
||||
UINT8 *colormap;
|
||||
INT32 xoff, yoff, flipflag, skinflags;
|
||||
|
||||
for (UINT8 draw = 0; draw < 2; draw++)
|
||||
{
|
||||
UINT8 drawme = draw ? (stplyr - players) : (foe - players);
|
||||
UINT8 drawx = basex + (draw ? youx : foex);
|
||||
UINT8 drawy = basey + (draw ? youy : foey);
|
||||
|
||||
if (!playeringame[drawme] || players[drawme].spectator)
|
||||
continue;
|
||||
|
||||
if (!players[drawme].mo || P_MobjWasRemoved(players[drawme].mo))
|
||||
continue;
|
||||
|
||||
skinflags = (demo.playback)
|
||||
? demo.skinlist[demo.currentskinid[drawme]].flags
|
||||
: skins[players[drawme].skin].flags;
|
||||
|
||||
// Flip SF_IRONMAN portraits, but only if they're transformed
|
||||
if (skinflags & SF_IRONMAN
|
||||
&& !(players[drawme].charflags & SF_IRONMAN) )
|
||||
{
|
||||
flipflag = V_FLIP|V_VFLIP; // blonic flip
|
||||
xoff = yoff = 16;
|
||||
} else
|
||||
{
|
||||
flipflag = 0;
|
||||
xoff = yoff = 0;
|
||||
}
|
||||
|
||||
if ((skin_t*)players[drawme].mo->skin)
|
||||
workingskin = (skin_t*)players[drawme].mo->skin - skins;
|
||||
else
|
||||
workingskin = players[drawme].skin;
|
||||
|
||||
colormap = R_GetTranslationColormap(workingskin, static_cast<skincolornum_t>(players[drawme].mo->color), GTC_CACHE);
|
||||
if (players[drawme].mo->colorized)
|
||||
colormap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(players[drawme].mo->color), GTC_CACHE);
|
||||
else
|
||||
colormap = R_GetTranslationColormap(workingskin, static_cast<skincolornum_t>(players[drawme].mo->color), GTC_CACHE);
|
||||
|
||||
V_DrawMappedPatch(drawx+xoff, drawy+yoff, flags|flipflag, faceprefix[workingskin][FACE_RANK], colormap);
|
||||
}
|
||||
|
||||
V_DrawScaledPatch(basex, basey, flags, kp_duel_margin[margin]);
|
||||
}
|
||||
|
||||
static INT32 easedallyscore = 0;
|
||||
static tic_t scorechangecooldown = 0;
|
||||
// Mildly ugly. Don't want to export this to khud when it's so nicely handled here,
|
||||
// but HUD hooks run at variable timing based on your actual framerate.
|
||||
static tic_t lastleveltime = 0;
|
||||
static tic_t teams_lastleveltime = 0;
|
||||
|
||||
static void K_drawKartTeamScores(void)
|
||||
{
|
||||
|
|
@ -3360,7 +3531,7 @@ static void K_drawKartTeamScores(void)
|
|||
}
|
||||
else
|
||||
{
|
||||
if (lastleveltime != leveltime) // Timing consistency
|
||||
if (teams_lastleveltime != leveltime) // Timing consistency
|
||||
{
|
||||
INT32 delta = abs(easedallyscore - allyscore); // how wrong is display score?
|
||||
|
||||
|
|
@ -3387,7 +3558,7 @@ static void K_drawKartTeamScores(void)
|
|||
enemyscore = totalscore - allyscore;
|
||||
}
|
||||
|
||||
lastleveltime = leveltime;
|
||||
teams_lastleveltime = leveltime;
|
||||
|
||||
fixed_t enemypercent = FixedDiv(enemyscore*FRACUNIT, totalscore*FRACUNIT);
|
||||
// fixed_t allypercent = FixedDiv(allyscore*FRACUNIT, totalscore*FRACUNIT);
|
||||
|
|
@ -3543,7 +3714,9 @@ static boolean K_drawKartLaps(void)
|
|||
// I do not understand the way this system of offsets is laid out at all,
|
||||
// so it's probably going to be pretty bad to maintain. Sorry.
|
||||
|
||||
if (numlaps != 1 && displayEXP != UINT16_MAX)
|
||||
boolean drawinglaps = (numlaps != 1 && !K_InRaceDuel() && displayEXP != UINT16_MAX);
|
||||
|
||||
if (drawinglaps)
|
||||
{
|
||||
if (r_splitscreen > 1)
|
||||
bump = 27;
|
||||
|
|
@ -3551,7 +3724,7 @@ static boolean K_drawKartLaps(void)
|
|||
bump = 40;
|
||||
}
|
||||
|
||||
if (numlaps != 1)
|
||||
if (drawinglaps)
|
||||
{
|
||||
if (r_splitscreen > 1)
|
||||
{
|
||||
|
|
@ -4862,7 +5035,7 @@ playertagtype_t K_WhichPlayerTag(player_t *p)
|
|||
}
|
||||
else if (p->bot)
|
||||
{
|
||||
if (p->botvars.rival == true || cv_levelskull.value)
|
||||
if ((p->botvars.rival == true || cv_levelskull.value) && (!K_InRaceDuel()))
|
||||
{
|
||||
return PLAYERTAG_RIVAL;
|
||||
}
|
||||
|
|
@ -7251,9 +7424,14 @@ void K_drawKartHUD(void)
|
|||
K_drawKartTeamScores();
|
||||
}
|
||||
|
||||
if (K_InRaceDuel())
|
||||
{
|
||||
K_drawKartDuelScores();
|
||||
}
|
||||
|
||||
if (LUA_HudEnabled(hud_gametypeinfo))
|
||||
{
|
||||
if (gametyperules & GTR_CIRCUIT)
|
||||
if (gametyperules & GTR_CIRCUIT && !K_InRaceDuel())
|
||||
{
|
||||
K_drawKartLaps();
|
||||
gametypeinfoshown = true;
|
||||
|
|
|
|||
124
src/k_kart.c
124
src/k_kart.c
|
|
@ -119,6 +119,27 @@ boolean K_DuelItemAlwaysSpawns(mapthing_t *mt)
|
|||
return !!(mt->thing_args[0]);
|
||||
}
|
||||
|
||||
boolean K_InRaceDuel(void)
|
||||
{
|
||||
return (inDuel && (gametyperules & GTR_CIRCUIT) && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)) && !specialstageinfo.valid;
|
||||
}
|
||||
|
||||
player_t *K_DuelOpponent(player_t *player)
|
||||
{
|
||||
if (!K_InRaceDuel())
|
||||
return player; // ????
|
||||
else
|
||||
{
|
||||
for (UINT8 i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
if (playeringame[i] && !players[i].spectator && player - players != i)
|
||||
return &players[i];
|
||||
}
|
||||
}
|
||||
|
||||
return player; // ????????????
|
||||
}
|
||||
|
||||
static void K_SpawnDuelOnlyItems(void)
|
||||
{
|
||||
mapthing_t *mt = NULL;
|
||||
|
|
@ -145,6 +166,7 @@ void K_TimerReset(void)
|
|||
numbulbs = 1;
|
||||
inDuel = rainbowstartavailable = false;
|
||||
linecrossed = 0;
|
||||
overtimecheckpoints = 0;
|
||||
timelimitintics = extratimeintics = secretextratime = 0;
|
||||
g_pointlimit = 0;
|
||||
}
|
||||
|
|
@ -273,6 +295,9 @@ void K_TimerInit(void)
|
|||
introtime = (108) + 5; // 108 for rotation, + 5 for white fade
|
||||
numbulbs += (numPlayers-2); // Extra POSITION!! time
|
||||
}
|
||||
|
||||
if (K_InRaceDuel())
|
||||
numlaps = 99;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -471,7 +496,10 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value)
|
|||
if (cv_4thgear.value && !netgame && (!demo.playback || !demo.netgame) && !modeattacking)
|
||||
value = 3;
|
||||
|
||||
return ((13 + (3*value)) << FRACBITS) / 16;
|
||||
fixed_t base = ((13 + (3*value)) << FRACBITS) / 16;
|
||||
fixed_t duel = overtimecheckpoints*(1<<FRACBITS)/16;
|
||||
|
||||
return base + duel;
|
||||
}
|
||||
|
||||
// Array of states to pick the starting point of the animation, based on the actual time left for invincibility.
|
||||
|
|
@ -4250,6 +4278,92 @@ void K_CheckpointCrossAward(player_t *player)
|
|||
player->cangrabitems = 1;
|
||||
|
||||
K_AwardPlayerRings(player, (player->bot ? 20 : 10), true);
|
||||
|
||||
// Update Duel scoring.
|
||||
if (K_InRaceDuel() && player->position == 1)
|
||||
{
|
||||
player->duelscore += 1;
|
||||
|
||||
if (leveltime > (tic_t)(TICRATE*DUELOVERTIME))
|
||||
{
|
||||
overtimecheckpoints++;
|
||||
if (overtimecheckpoints > 1)
|
||||
{
|
||||
K_AddMessage(va("Margin Boost x%d!", overtimecheckpoints), true, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
K_AddMessage("Margin Boost!", true, false);
|
||||
g_darkness.start = leveltime;
|
||||
g_darkness.end = INT32_MAX;
|
||||
for (UINT8 i = 0; i < MAXSPLITSCREENPLAYERS; i++)
|
||||
{
|
||||
g_darkness.value[i] = FRACUNIT;
|
||||
}
|
||||
}
|
||||
|
||||
S_StartSound(NULL, sfx_gsha6);
|
||||
}
|
||||
|
||||
player_t *opp = K_DuelOpponent(player);
|
||||
boolean clutch = (player->duelscore - opp->duelscore == (DUELWINNINGSCORE-1));
|
||||
boolean win = (player->duelscore - opp->duelscore == DUELWINNINGSCORE);
|
||||
|
||||
if (!win)
|
||||
{
|
||||
for (UINT8 i = 0; i < MAXSPLITSCREENPLAYERS; i++)
|
||||
{
|
||||
player_t *check = &players[displayplayers[i]];
|
||||
if (check == player)
|
||||
{
|
||||
S_StartSound(NULL, sfx_mbs45);
|
||||
if (clutch)
|
||||
S_StartSoundAtVolume(NULL, sfx_s3k9c, 170);
|
||||
}
|
||||
|
||||
else if (check == opp)
|
||||
{
|
||||
S_StartSound(NULL, sfx_mbs60);
|
||||
if (clutch)
|
||||
S_StartSoundAtVolume(NULL, sfx_kc4b, 150);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (player->duelscore - opp->duelscore == DUELWINNINGSCORE)
|
||||
{
|
||||
opp->position = 2;
|
||||
player->position = 1;
|
||||
|
||||
if (opp->distancetofinish - player->distancetofinish < 200) // Setting player.exiting changes distance reporting, check these first!
|
||||
{
|
||||
K_StartRoundWinCamera(
|
||||
player->mo,
|
||||
player->angleturn + ANGLE_180,
|
||||
400*mapobjectscale,
|
||||
6*TICRATE,
|
||||
FRACUNIT/16
|
||||
);
|
||||
}
|
||||
|
||||
S_StartSound(NULL, sfx_s3k6a);
|
||||
P_DoPlayerExit(player, 0);
|
||||
P_DoAllPlayersExit(PF_NOCONTEST, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Doing this here because duel exit is a weird path, and we don't want to transform for endcam.
|
||||
UINT32 skinflags = (demo.playback)
|
||||
? demo.skinlist[demo.currentskinid[(player-players)]].flags
|
||||
: skins[player->skin].flags;
|
||||
if (skinflags & SF_IRONMAN)
|
||||
{
|
||||
SetRandomFakePlayerSkin(player, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean K_Overdrive(player_t *player)
|
||||
|
|
@ -10996,7 +11110,7 @@ static void K_UpdateDistanceFromFinishLine(player_t *const player)
|
|||
const mapheader_t *mapheader = mapheaderinfo[gamemap - 1];
|
||||
if ((mapheader->levelflags & LF_SECTIONRACE) == 0U)
|
||||
{
|
||||
const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection;
|
||||
UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection;
|
||||
player->distancetofinish += numfulllapsleft * K_GetCircuitLength();
|
||||
}
|
||||
}
|
||||
|
|
@ -11044,6 +11158,7 @@ static void K_UpdatePlayerWaypoints(player_t *const player)
|
|||
player->respawn.state == RESPAWNST_NONE && // Respawning should be a full reset.
|
||||
old_currentwaypoint != NULL && // So should touching the first waypoint ever.
|
||||
player->laps != 0 && // POSITION rooms may have unorthodox waypoints to guide bots.
|
||||
player->exiting == 0 && // What the fuck? Why do duels antiskip the bot?
|
||||
!(player->pflags & PF_TRUSTWAYPOINTS)) // Special exception.
|
||||
{
|
||||
extern consvar_t cv_debuglapcheat;
|
||||
|
|
@ -12043,6 +12158,11 @@ void K_KartUpdatePosition(player_t *player)
|
|||
realplayers++;
|
||||
}
|
||||
}
|
||||
else if (K_InRaceDuel() && player->exiting)
|
||||
{
|
||||
// Positions directly set in K_CheckpointCrossAward, don't touch.
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ Make sure this matches the actual number of states
|
|||
#define RINGVOLUMEREGEN 1
|
||||
#define RINGTRANSPARENCYREGEN 3
|
||||
|
||||
#define DUELOVERTIME (cv_dueltimelimit.value)
|
||||
#define DUELWINNINGSCORE (cv_duelscorelimit.value)
|
||||
|
||||
#define MIN_WAVEDASH_CHARGE ((11*TICRATE/16)*9)
|
||||
|
||||
#define MAXTOPACCEL (12*FRACUNIT)
|
||||
|
|
@ -105,6 +108,8 @@ angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t
|
|||
|
||||
boolean K_IsDuelItem(mobjtype_t type);
|
||||
boolean K_DuelItemAlwaysSpawns(mapthing_t *mt);
|
||||
boolean K_InRaceDuel(void);
|
||||
player_t *K_DuelOpponent(player_t *player);
|
||||
|
||||
void K_TimerReset(void);
|
||||
void K_TimerInit(void);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include "k_grandprix.h"
|
||||
#include "k_profiles.h"
|
||||
#include "k_serverstats.h"
|
||||
#include "k_kart.h" // K_InRaceDuel
|
||||
|
||||
// Client-sided calculations done for Power Levels.
|
||||
// This is done so that clients will never be able to hack someone else's score over the server.
|
||||
|
|
@ -213,6 +214,10 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit)
|
|||
CONS_Debug(DBG_PWRLV, "========\n");
|
||||
|
||||
yourPower = clientpowerlevels[playerNum][powerType];
|
||||
|
||||
if (K_InRaceDuel())
|
||||
yourPower += clientPowerAdd[playerNum];
|
||||
|
||||
if (yourPower == 0)
|
||||
{
|
||||
// Guests don't record power level changes.
|
||||
|
|
@ -225,6 +230,8 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit)
|
|||
CONS_Debug(DBG_PWRLV, "%s's gametype score: %d\n", player_names[playerNum], yourScore);
|
||||
|
||||
CONS_Debug(DBG_PWRLV, "========\n");
|
||||
|
||||
boolean dueling = K_InRaceDuel();
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
UINT16 theirScore = 0;
|
||||
|
|
@ -254,6 +261,9 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit)
|
|||
CONS_Debug(DBG_PWRLV, "%s VS %s:\n", player_names[playerNum], player_names[i]);
|
||||
|
||||
theirPower = clientpowerlevels[i][powerType];
|
||||
if (K_InRaceDuel())
|
||||
theirPower += clientPowerAdd[i];
|
||||
|
||||
if (theirPower == 0)
|
||||
{
|
||||
// No power level (splitscreen guests, bots)
|
||||
|
|
@ -295,11 +305,13 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit)
|
|||
}
|
||||
}
|
||||
|
||||
if (exitBonus == false)
|
||||
if (dueling)
|
||||
{
|
||||
INT16 prevInc = inc;
|
||||
|
||||
inc /= max(numlaps-1, 1);
|
||||
// INT32 winnerscore = (yourScore > theirScore) ? player->duelscore : players[i].duelscore;
|
||||
INT32 multiplier = 2;
|
||||
inc *= multiplier;
|
||||
|
||||
if (inc == 0)
|
||||
{
|
||||
|
|
@ -313,7 +325,32 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit)
|
|||
}
|
||||
}
|
||||
|
||||
CONS_Debug(DBG_PWRLV, "Reduced (%d / %d = %d) because it's not the end of the race\n", prevInc, numlaps, inc);
|
||||
// CONS_Printf("%s PWR UPDATE: %d\n", player_names[player - players], inc);
|
||||
|
||||
CONS_Debug(DBG_PWRLV, "DUELING: Boosted (%d * %d = %d)\n", prevInc, multiplier, inc);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (exitBonus == false)
|
||||
{
|
||||
INT16 prevInc = inc;
|
||||
|
||||
inc /= max(numlaps-1, 1);
|
||||
|
||||
if (inc == 0)
|
||||
{
|
||||
if (prevInc > 0)
|
||||
{
|
||||
inc = 1;
|
||||
}
|
||||
else if (prevInc < 0)
|
||||
{
|
||||
inc = -1;
|
||||
}
|
||||
}
|
||||
|
||||
CONS_Debug(DBG_PWRLV, "Reduced (%d / %d = %d) because it's not the end of the race\n", prevInc, numlaps, inc);
|
||||
}
|
||||
}
|
||||
|
||||
CONS_Debug(DBG_PWRLV, "========\n");
|
||||
|
|
@ -346,6 +383,10 @@ void K_UpdatePowerLevelsFinalize(player_t *player, boolean onForfeit)
|
|||
INT16 lapsLeft = 0;
|
||||
UINT8 i;
|
||||
|
||||
// No remaining laps in Duel.
|
||||
if (K_InRaceDuel())
|
||||
return;
|
||||
|
||||
lapsLeft = (numlaps - player->latestlap) + 1;
|
||||
|
||||
if (lapsLeft <= 0)
|
||||
|
|
@ -394,7 +435,7 @@ INT16 K_FinalPowerIncrement(player_t *player, INT16 yourPower, INT16 baseInc)
|
|||
|
||||
if (inc <= 0)
|
||||
{
|
||||
if (player->position == 1 && numPlayers > 1)
|
||||
if (player->position == 1 && numPlayers > 1 && !(K_InRaceDuel()))
|
||||
{
|
||||
// Won the whole match?
|
||||
// Get at least one point.
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ boolean level_tally_t::UseBonuses(void)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (K_InRaceDuel())
|
||||
return false;
|
||||
|
||||
// No bonuses / ranking in FREE PLAY or Time Attack
|
||||
return (grandprixinfo.gp == true || K_TimeAttackRules() == false);
|
||||
}
|
||||
|
|
|
|||
673
src/k_vote.c
673
src/k_vote.c
|
|
@ -109,6 +109,7 @@
|
|||
// 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 STRIKE_DELAY_TIME (TICRATE/3)
|
||||
|
||||
#define MAP_ANGER_MAX (2)
|
||||
|
||||
|
|
@ -179,12 +180,21 @@ typedef struct
|
|||
{
|
||||
INT32 timer;
|
||||
INT32 tic, endtic;
|
||||
INT32 selectFinalize, pickFinalize;
|
||||
INT32 selectFinalize, pickFinalize, strikeFinalize;
|
||||
boolean notYetPicked;
|
||||
boolean loaded;
|
||||
SINT8 deferredLevel;
|
||||
y_vote_player players[MAXSPLITSCREENPLAYERS];
|
||||
y_vote_roulette roulette;
|
||||
|
||||
// If both of these players are valid,
|
||||
// and they're the only players in the server,
|
||||
// then we want stage striking!
|
||||
player_t *strike_loser;
|
||||
player_t *strike_winner;
|
||||
boolean strike_turn;
|
||||
boolean strike_time_out;
|
||||
boolean stage_striking;
|
||||
} y_vote_data;
|
||||
|
||||
// Voting level drawing
|
||||
|
|
@ -209,6 +219,8 @@ typedef struct
|
|||
patch_t *ruby_icon;
|
||||
fixed_t ruby_height;
|
||||
|
||||
patch_t *strike_icon;
|
||||
|
||||
patch_t *bg_planet[PLANET_FRAMES];
|
||||
patch_t *bg_checker;
|
||||
patch_t *bg_levelText;
|
||||
|
|
@ -230,13 +242,54 @@ typedef struct
|
|||
static y_vote_data vote = {0};
|
||||
static y_vote_draw vote_draw = {0};
|
||||
|
||||
static void Y_SetVoteTimer(void)
|
||||
{
|
||||
vote.timer = cv_votetime.value * TICRATE;
|
||||
|
||||
if (vote.stage_striking == true)
|
||||
{
|
||||
vote.timer /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
static UINT8 Y_CountStriked(void)
|
||||
{
|
||||
INT32 i;
|
||||
|
||||
UINT8 num_striked = 0;
|
||||
for (i = 0; i < VOTE_NUM_LEVELS; i++)
|
||||
{
|
||||
if (g_votes_striked[i] == true)
|
||||
{
|
||||
num_striked++;
|
||||
}
|
||||
}
|
||||
|
||||
return num_striked;
|
||||
}
|
||||
|
||||
static boolean Y_VoteIDIsSpecial(const UINT8 playerId)
|
||||
{
|
||||
switch (playerId)
|
||||
{
|
||||
case VOTE_SPECIAL:
|
||||
case VOTE_TIMEOUT_LOSER:
|
||||
case VOTE_TIMEOUT_WINNER:
|
||||
{
|
||||
// Special vote spot, always allow
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean Y_PlayerIDCanVote(const UINT8 playerId)
|
||||
{
|
||||
player_t *player = NULL;
|
||||
|
||||
if (playerId == VOTE_SPECIAL)
|
||||
if (Y_VoteIDIsSpecial(playerId) == true)
|
||||
{
|
||||
// Special vote spot, always allow
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -245,7 +298,7 @@ boolean Y_PlayerIDCanVote(const UINT8 playerId)
|
|||
return false;
|
||||
}
|
||||
|
||||
player = &players[playerId];
|
||||
const player_t *player = &players[playerId];
|
||||
if (player->spectator == true)
|
||||
{
|
||||
return false;
|
||||
|
|
@ -260,8 +313,48 @@ boolean Y_PlayerIDCanVote(const UINT8 playerId)
|
|||
return true;
|
||||
}
|
||||
|
||||
static boolean Y_IsPlayersTurn(const UINT8 playerId)
|
||||
{
|
||||
if (Y_VoteIDIsSpecial(playerId) == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (vote.stage_striking == false)
|
||||
{
|
||||
// Not stage striking -- we can always vote.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Is it our turn to strike a stage?
|
||||
const player_t *player = &players[playerId];
|
||||
if (vote.strike_turn == true)
|
||||
{
|
||||
return (player == vote.strike_winner);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (player == vote.strike_loser);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean Y_PlayerIDCanVoteRightNow(const UINT8 playerId)
|
||||
{
|
||||
if (Y_IsPlayersTurn(playerId) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Y_PlayerIDCanVote(playerId);
|
||||
}
|
||||
|
||||
static boolean Y_PlayerCanSelect(const UINT8 localId)
|
||||
{
|
||||
if (localId > splitscreen)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const UINT8 p = g_localplayers[localId];
|
||||
|
||||
if (g_pickedVote != VOTE_NOT_PICKED)
|
||||
|
|
@ -280,7 +373,7 @@ static boolean Y_PlayerCanSelect(const UINT8 localId)
|
|||
return false;
|
||||
}
|
||||
|
||||
return Y_PlayerIDCanVote(p);
|
||||
return Y_PlayerIDCanVoteRightNow(p);
|
||||
}
|
||||
|
||||
static void Y_SortPile(void)
|
||||
|
|
@ -381,21 +474,73 @@ static void Y_SortPile(void)
|
|||
}
|
||||
}
|
||||
|
||||
void Y_SetPlayersVote(const UINT8 playerId, SINT8 newVote)
|
||||
void Y_SetPlayersVote(const UINT8 inputPlayerId, SINT8 newVote)
|
||||
{
|
||||
y_vote_pile *const pile = &vote.roulette.pile[playerId];
|
||||
y_vote_catcher *const catcher = &pile->catcher;
|
||||
INT32 i;
|
||||
|
||||
if (gamestate != GS_VOTING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UINT8 playerId = inputPlayerId;
|
||||
|
||||
// Manually overwrite these players for timed out votes.
|
||||
// Loser/winner is encoded in the vote ID to prevent race
|
||||
// race conditions with real votes causing problems.
|
||||
if (inputPlayerId == VOTE_TIMEOUT_LOSER)
|
||||
{
|
||||
playerId = (vote.strike_loser - players);
|
||||
}
|
||||
else if (inputPlayerId == VOTE_TIMEOUT_WINNER)
|
||||
{
|
||||
playerId = (vote.strike_winner - players);
|
||||
}
|
||||
|
||||
if (newVote < 0 || newVote >= VOTE_NUM_LEVELS)
|
||||
{
|
||||
newVote = VOTE_NOT_PICKED;
|
||||
}
|
||||
|
||||
if (playerId < MAXPLAYERS)
|
||||
{
|
||||
if (Y_PlayerIDCanVoteRightNow(playerId) == false)
|
||||
{
|
||||
// Not your turn, dude!
|
||||
return;
|
||||
}
|
||||
|
||||
if (vote.stage_striking == true)
|
||||
{
|
||||
if (newVote != VOTE_NOT_PICKED
|
||||
&& g_votes_striked[newVote] == false
|
||||
&& Y_CountStriked() < VOTE_NUM_LEVELS-1)
|
||||
{
|
||||
// Strike a stage, instead of voting.
|
||||
g_votes_striked[newVote] = true;
|
||||
|
||||
// Change turn.
|
||||
vote.strike_turn = !vote.strike_turn;
|
||||
|
||||
// Reset variables.
|
||||
Y_SetVoteTimer();
|
||||
for (i = 0; i <= splitscreen; i++)
|
||||
{
|
||||
vote.players[i].sentTimeOutVote = false;
|
||||
vote.players[i].delay = NEWTICRATE/7;
|
||||
}
|
||||
vote.strike_time_out = false;
|
||||
|
||||
// TODO: striking animation
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
y_vote_pile *const pile = &vote.roulette.pile[playerId];
|
||||
y_vote_catcher *const catcher = &pile->catcher;
|
||||
|
||||
g_votes[playerId] = newVote;
|
||||
|
||||
Y_SortPile();
|
||||
|
|
@ -416,12 +561,12 @@ void Y_SetPlayersVote(const UINT8 playerId, SINT8 newVote)
|
|||
if (vote.timer == -1)
|
||||
{
|
||||
// Someone has voted, so start the timer now.
|
||||
vote.timer = cv_votetime.value * TICRATE;
|
||||
Y_SetVoteTimer();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t width, INT32 flags, SINT8 v, boolean dim, SINT8 playerID)
|
||||
static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t width, INT32 flags, SINT8 v, boolean dim, SINT8 playerID, boolean from_selection)
|
||||
{
|
||||
const boolean encore = vote_draw.levels[v].encore;
|
||||
const fixed_t height = (width * BASEVIDHEIGHT) / BASEVIDWIDTH;
|
||||
|
|
@ -468,29 +613,64 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt
|
|||
|
||||
V_AdjustXYWithSnap(&fx, &fy, flags, dupx, dupy);
|
||||
|
||||
boolean striked = false;
|
||||
if (from_selection == true)
|
||||
{
|
||||
striked = g_votes_striked[v];
|
||||
}
|
||||
|
||||
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)
|
||||
if (striked == true)
|
||||
{
|
||||
const fixed_t rubyScale = width / 72;
|
||||
const fixed_t strikeScale = width / 32;
|
||||
V_DrawFixedPatch(
|
||||
center_x, center_y - FixedMul(vote_draw.ruby_height << 1, rubyScale),
|
||||
rubyScale, flags,
|
||||
vote_draw.ruby_icon,
|
||||
center_x - (strikeScale * 25 / 2), center_y - (strikeScale * 22 / 2),
|
||||
strikeScale, flags,
|
||||
vote_draw.strike_icon,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
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 (vote.stage_striking == true
|
||||
&& from_selection == true
|
||||
&& dim == false)
|
||||
{
|
||||
if (Y_CountStriked() < VOTE_NUM_LEVELS-1)
|
||||
{
|
||||
const fixed_t strikeScale = width / 32;
|
||||
V_DrawFixedPatch(
|
||||
center_x - (strikeScale * 25 / 2), center_y - (strikeScale * 22 / 2),
|
||||
strikeScale, flags /*| V_TRANSLUCENT*/,
|
||||
vote_draw.strike_icon,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dim == true)
|
||||
{
|
||||
|
|
@ -506,7 +686,7 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt
|
|||
{
|
||||
const INT32 whiteSq = 16 * dupx;
|
||||
|
||||
if (playerID < MAXPLAYERS)
|
||||
if (playerID < MAXPLAYERS) // Player vote
|
||||
{
|
||||
UINT8 *playerMap = R_GetTranslationColormap(players[playerID].skin, players[playerID].skincolor, GTC_CACHE);
|
||||
patch_t *playerPatch = faceprefix[players[playerID].skin][FACE_RANK];
|
||||
|
|
@ -518,7 +698,7 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt
|
|||
playerPatch, playerMap
|
||||
);
|
||||
}
|
||||
else
|
||||
else if (vote.stage_striking == false) // Angry map
|
||||
{
|
||||
const fixed_t iconHeight = (14 << FRACBITS);
|
||||
const fixed_t iconWidth = (iconHeight * 320) / 200;
|
||||
|
|
@ -618,7 +798,7 @@ static void Y_DrawCatcher(y_vote_catcher *catcher)
|
|||
baseX, catcher->y,
|
||||
((catcher->small == true) ? PILE_WIDTH : SELECTION_WIDTH), 0,
|
||||
catcher->level, false,
|
||||
catcher->player
|
||||
catcher->player, false
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -813,7 +993,7 @@ static void Y_DrawVoteSelection(fixed_t offset)
|
|||
continue;
|
||||
}
|
||||
|
||||
if (g_votes[p] != VOTE_NOT_PICKED || Y_PlayerIDCanVote(p) == false)
|
||||
if (g_votes[p] != VOTE_NOT_PICKED || Y_PlayerIDCanVoteRightNow(p) == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -881,12 +1061,41 @@ static void Y_DrawVoteSelection(fixed_t offset)
|
|||
x, y - vote_draw.levels[i].hop,
|
||||
SELECTION_WIDTH, 0,
|
||||
i, (selected == false),
|
||||
-1
|
||||
-1, true
|
||||
);
|
||||
|
||||
x += SELECTION_SPACING_W;
|
||||
}
|
||||
|
||||
if (vote.stage_striking == true && Y_CountStriked() < VOTE_NUM_LEVELS-1)
|
||||
{
|
||||
UINT8 current_strike_player = (
|
||||
(vote.strike_turn == true)
|
||||
? (vote.strike_winner - players)
|
||||
: (vote.strike_loser - players)
|
||||
);
|
||||
|
||||
for (i = 0; i <= splitscreen; i++)
|
||||
{
|
||||
if (g_localplayers[i] == current_strike_player)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > splitscreen)
|
||||
{
|
||||
const char *wait_str = va("Waiting for %s...", player_names[current_strike_player]);
|
||||
|
||||
V_DrawThinString(
|
||||
BASEVIDWIDTH / 2 - (V_ThinStringWidth(wait_str, 0) / 2),
|
||||
180,
|
||||
0,
|
||||
wait_str
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Draw our catchers
|
||||
//
|
||||
|
|
@ -944,7 +1153,7 @@ static void Y_DrawVotePile(void)
|
|||
PILE_WIDTH, 0,
|
||||
g_votes[i],
|
||||
(i != vote.roulette.anim || g_pickedVote == VOTE_NOT_PICKED),
|
||||
i
|
||||
i, false
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1060,8 +1269,30 @@ static void Y_VoteStops(SINT8 pick, SINT8 level)
|
|||
}
|
||||
}
|
||||
|
||||
static void Y_PlayerSendStrike(const UINT8 localPlayer)
|
||||
{
|
||||
y_vote_player *const player = &vote.players[localPlayer];
|
||||
y_vote_catcher *const catcher = &player->catcher;
|
||||
|
||||
if (g_votes_striked[player->selection] == true)
|
||||
{
|
||||
// TODO: "Can't select" animation
|
||||
return;
|
||||
}
|
||||
|
||||
D_ModifyClientVote(g_localplayers[localPlayer], player->selection);
|
||||
catcher->action = CATCHER_NA;
|
||||
catcher->delay = 5;
|
||||
}
|
||||
|
||||
static void Y_PlayerSendVote(const UINT8 localPlayer)
|
||||
{
|
||||
if (vote.stage_striking == true)
|
||||
{
|
||||
Y_PlayerSendStrike(localPlayer);
|
||||
return;
|
||||
}
|
||||
|
||||
y_vote_player *const player = &vote.players[localPlayer];
|
||||
y_vote_catcher *const catcher = &player->catcher;
|
||||
|
||||
|
|
@ -1188,7 +1419,11 @@ static void Y_TickPlayerCatcher(const UINT8 localPlayer)
|
|||
{
|
||||
if (catcher->x == catcher->destX && catcher->y == catcher->destY)
|
||||
{
|
||||
D_ModifyClientVote(g_localplayers[localPlayer], vote.players[localPlayer].selection);
|
||||
if (vote.stage_striking == false)
|
||||
{
|
||||
D_ModifyClientVote(g_localplayers[localPlayer], vote.players[localPlayer].selection);
|
||||
}
|
||||
|
||||
catcher->action = CATCHER_NA;
|
||||
catcher->delay = 5;
|
||||
S_StopSoundByNum(sfx_kc37);
|
||||
|
|
@ -1434,6 +1669,201 @@ static SINT8 Y_TryMapAngerVote(void)
|
|||
return angryMaps[pick];
|
||||
}
|
||||
|
||||
static void Y_ExitStageStrike(void)
|
||||
{
|
||||
INT32 i;
|
||||
|
||||
vote.stage_striking = false;
|
||||
|
||||
for (i = 0; i < VOTE_NUM_LEVELS; i++)
|
||||
{
|
||||
g_votes_striked[i] = false;
|
||||
}
|
||||
|
||||
vote.strike_loser = NULL;
|
||||
vote.strike_winner = NULL;
|
||||
vote.strike_turn = false;
|
||||
vote.strike_time_out = false;
|
||||
|
||||
Y_SetVoteTimer();
|
||||
}
|
||||
|
||||
static boolean Y_CheckStageStrikeStatus(void)
|
||||
{
|
||||
INT32 i;
|
||||
UINT8 num_voters = 0;
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
if (Y_PlayerIDCanVote(i) == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
num_voters++;
|
||||
if (num_voters > 2)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_voters != 2)
|
||||
{
|
||||
// Someone joined or left. Stage striking is broken!
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vote.strike_loser == NULL || Y_PlayerIDCanVote(vote.strike_loser - players) == false)
|
||||
{
|
||||
// Loser is invalidated!
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vote.strike_winner == NULL || Y_PlayerIDCanVote(vote.strike_winner - players) == false)
|
||||
{
|
||||
// Winner is invalidated!
|
||||
return false;
|
||||
}
|
||||
|
||||
// Looks good, we can tick stage striking.
|
||||
return true;
|
||||
}
|
||||
|
||||
static void Y_TickVoteStageStrike(void)
|
||||
{
|
||||
INT32 i;
|
||||
|
||||
if (Y_CheckStageStrikeStatus() == false)
|
||||
{
|
||||
Y_ExitStageStrike();
|
||||
return;
|
||||
}
|
||||
|
||||
SINT8 the_only_level = VOTE_NOT_PICKED;
|
||||
for (i = 0; i < VOTE_NUM_LEVELS; i++)
|
||||
{
|
||||
if (g_votes_striked[i] == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (the_only_level != VOTE_NOT_PICKED)
|
||||
{
|
||||
// More than 1 valid level.
|
||||
// Unset and stop iterating.
|
||||
the_only_level = VOTE_NOT_PICKED;
|
||||
break;
|
||||
}
|
||||
|
||||
the_only_level = i;
|
||||
}
|
||||
|
||||
if (the_only_level != VOTE_NOT_PICKED)
|
||||
{
|
||||
vote.timer = 0;
|
||||
vote.strikeFinalize = STRIKE_DELAY_TIME;
|
||||
|
||||
if (vote.selectFinalize < SELECT_DELAY_TIME)
|
||||
{
|
||||
if (vote.selectFinalize == 0)
|
||||
{
|
||||
for (i = 0; i <= splitscreen; i++)
|
||||
{
|
||||
UINT8 p = g_localplayers[i];
|
||||
|
||||
if (p != (vote.strike_loser - players)
|
||||
&& p != (vote.strike_winner - players))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
y_vote_player *const player = &vote.players[i];
|
||||
y_vote_catcher *const catcher = &player->catcher;
|
||||
|
||||
player->selection = the_only_level;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
vote.selectFinalize++;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
D_PickVote( the_only_level );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vote.timer == 0)
|
||||
{
|
||||
if (vote.strikeFinalize < STRIKE_DELAY_TIME)
|
||||
{
|
||||
vote.strikeFinalize++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
vote.strikeFinalize = 0;
|
||||
}
|
||||
|
||||
if (vote.strikeFinalize >= STRIKE_DELAY_TIME)
|
||||
{
|
||||
// We didn't get their timeout strike net command.
|
||||
// Maybe they hacked their exe, or connection was
|
||||
// interrupted, or some other issue.
|
||||
|
||||
// Let's just strike a random stage for them.
|
||||
if (server && vote.strike_time_out == false)
|
||||
{
|
||||
INT32 rng = M_RandomKey(VOTE_NUM_LEVELS);
|
||||
for (i = 0; i < VOTE_NUM_LEVELS; i++)
|
||||
{
|
||||
if (g_votes_striked[i] == false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
rng++;
|
||||
if (rng >= VOTE_NUM_LEVELS)
|
||||
{
|
||||
rng = 0;
|
||||
}
|
||||
}
|
||||
|
||||
D_ModifyClientVote((vote.strike_turn == true) ? VOTE_TIMEOUT_WINNER : VOTE_TIMEOUT_LOSER, rng);
|
||||
}
|
||||
|
||||
vote.strike_time_out = true;
|
||||
}
|
||||
else if (vote.timer > 0)
|
||||
{
|
||||
vote.timer--;
|
||||
vote.selectFinalize = 0;
|
||||
vote.pickFinalize = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void Y_TickVoteSelection(void)
|
||||
{
|
||||
boolean everyone_voted = true;/* the default condition */
|
||||
|
|
@ -1463,6 +1893,22 @@ static void Y_TickVoteSelection(void)
|
|||
// Time's up, send our vote ASAP.
|
||||
if (vote.players[i].sentTimeOutVote == false)
|
||||
{
|
||||
// Move off of striked stages for the timeout vote.
|
||||
INT32 j;
|
||||
for (j = 0; j < VOTE_NUM_LEVELS; j++)
|
||||
{
|
||||
if (g_votes_striked[vote.players[i].selection] == false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
vote.players[i].selection++;
|
||||
if (vote.players[i].selection >= VOTE_NUM_LEVELS)
|
||||
{
|
||||
vote.players[i].selection = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Y_PlayerSendVote(i);
|
||||
vote.players[i].sentTimeOutVote = true;
|
||||
vote.players[i].delay = NEWTICRATE/7;
|
||||
|
|
@ -1514,12 +1960,27 @@ static void Y_TickVoteSelection(void)
|
|||
continue;
|
||||
}
|
||||
|
||||
if (players[i].bot == true && g_votes[i] == VOTE_NOT_PICKED)
|
||||
if (server && players[i].bot == true && Y_PlayerIDCanVoteRightNow(i) == true && g_votes[i] == VOTE_NOT_PICKED)
|
||||
{
|
||||
if (( M_RandomFixed() % 100 ) == 0)
|
||||
{
|
||||
// bots vote randomly
|
||||
D_ModifyClientVote(i, M_RandomKey(VOTE_NUM_LEVELS));
|
||||
INT32 rng = M_RandomKey(VOTE_NUM_LEVELS);
|
||||
for (i = 0; i < VOTE_NUM_LEVELS; i++)
|
||||
{
|
||||
if (g_votes_striked[i] == false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
rng++;
|
||||
if (rng >= VOTE_NUM_LEVELS)
|
||||
{
|
||||
rng = 0;
|
||||
}
|
||||
}
|
||||
|
||||
D_ModifyClientVote(i, rng);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1529,6 +1990,13 @@ static void Y_TickVoteSelection(void)
|
|||
}
|
||||
}
|
||||
|
||||
if (vote.stage_striking == true)
|
||||
{
|
||||
// Use the same selection logic, otherwise use separate ending logic.
|
||||
Y_TickVoteStageStrike();
|
||||
return;
|
||||
}
|
||||
|
||||
if (everyone_voted == true)
|
||||
{
|
||||
vote.timer = 0;
|
||||
|
|
@ -1649,6 +2117,7 @@ static void Y_InitVoteDrawing(void)
|
|||
INT32 i = 0, j = 0;
|
||||
|
||||
vote_draw.ruby_icon = W_CachePatchName("RUBYICON", PU_STATIC);
|
||||
vote_draw.strike_icon = W_CachePatchName("K_NOBLNS", PU_STATIC);
|
||||
|
||||
for (i = 0; i < PLANET_FRAMES; i++)
|
||||
{
|
||||
|
|
@ -1736,6 +2205,103 @@ static void Y_InitVoteDrawing(void)
|
|||
vote_draw.selectTransition = FRACUNIT;
|
||||
}
|
||||
|
||||
static boolean Y_DetermineStageStrike(void)
|
||||
{
|
||||
player_t *a = NULL;
|
||||
player_t *b = NULL;
|
||||
|
||||
UINT8 num_voters = 0;
|
||||
|
||||
INT32 i;
|
||||
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
if (Y_PlayerIDCanVote(i) == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
num_voters++;
|
||||
|
||||
// Just set the pointers for now, sort them later.
|
||||
if (a == NULL)
|
||||
{
|
||||
a = &players[i];
|
||||
}
|
||||
else if (b == NULL)
|
||||
{
|
||||
b = &players[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Too many players
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_voters != 2 || a == NULL || b == NULL)
|
||||
{
|
||||
// Requires exactly 2 of them.
|
||||
return false;
|
||||
}
|
||||
|
||||
UINT32 score_a = 0;
|
||||
UINT32 score_b = 0;
|
||||
|
||||
intertype_t scoring_type = Y_GetIntermissionType();
|
||||
switch (scoring_type)
|
||||
{
|
||||
case int_time:
|
||||
{
|
||||
score_a = UINT32_MAX - a->realtime;
|
||||
score_b = UINT32_MAX - b->realtime;
|
||||
break;
|
||||
}
|
||||
case int_score:
|
||||
{
|
||||
score_a = a->score;
|
||||
score_b = b->realtime;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// Invalid, exit now.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (a->pflags & PF_NOCONTEST)
|
||||
{
|
||||
score_a = 0;
|
||||
}
|
||||
|
||||
if (b->pflags & PF_NOCONTEST)
|
||||
{
|
||||
score_b = 0;
|
||||
}
|
||||
|
||||
if (score_a == score_b)
|
||||
{
|
||||
// TODO: should be a coin flip, but how
|
||||
// should the RNG for this be handled?
|
||||
score_a++;
|
||||
}
|
||||
|
||||
if (score_a > score_b)
|
||||
{
|
||||
vote.strike_loser = b;
|
||||
vote.strike_winner = a;
|
||||
}
|
||||
else
|
||||
{
|
||||
vote.strike_loser = a;
|
||||
vote.strike_winner = b;
|
||||
}
|
||||
|
||||
vote.stage_striking = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Y_StartVote(void)
|
||||
{
|
||||
INT32 i = 0;
|
||||
|
|
@ -1749,12 +2315,6 @@ void Y_StartVote(void)
|
|||
|
||||
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;
|
||||
|
||||
|
|
@ -1782,6 +2342,19 @@ void Y_StartVote(void)
|
|||
catcher->player = i;
|
||||
}
|
||||
|
||||
for (i = 0; i < VOTE_NUM_LEVELS; i++)
|
||||
{
|
||||
g_votes_striked[i] = false;
|
||||
}
|
||||
|
||||
Y_DetermineStageStrike();
|
||||
|
||||
#ifdef VOTE_TIME_WAIT_FOR_VOTE
|
||||
vote.timer = -1; // Timer is not set until the first vote is added
|
||||
#else
|
||||
Y_SetVoteTimer();
|
||||
#endif
|
||||
|
||||
Y_InitVoteDrawing();
|
||||
|
||||
vote.loaded = true;
|
||||
|
|
@ -1803,6 +2376,7 @@ static void Y_UnloadVoteData(void)
|
|||
}
|
||||
|
||||
UNLOAD(vote_draw.ruby_icon);
|
||||
UNLOAD(vote_draw.strike_icon);
|
||||
|
||||
for (i = 0; i < PLANET_FRAMES; i++)
|
||||
{
|
||||
|
|
@ -1941,4 +2515,25 @@ void Y_SetupVoteFinish(SINT8 pick, SINT8 level, SINT8 anger)
|
|||
vote.timer = -1;
|
||||
vote.selectFinalize = SELECT_DELAY_TIME;
|
||||
vote.pickFinalize = PICK_DELAY_TIME;
|
||||
vote.strikeFinalize = STRIKE_DELAY_TIME;
|
||||
}
|
||||
|
||||
//
|
||||
// Y_VoteContext
|
||||
//
|
||||
|
||||
enum
|
||||
{
|
||||
VOTE_CTX_NORMAL = 0,
|
||||
VOTE_CTX_DUEL,
|
||||
};
|
||||
|
||||
UINT8 Y_VoteContext(void)
|
||||
{
|
||||
if (vote.stage_striking == true)
|
||||
{
|
||||
return VOTE_CTX_DUEL;
|
||||
}
|
||||
|
||||
return VOTE_CTX_NORMAL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,6 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define VOTE_NUM_LEVELS (4)
|
||||
#define VOTE_NOT_PICKED (-1)
|
||||
|
||||
#define VOTE_MOD_ENCORE (0x01)
|
||||
|
||||
boolean Y_PlayerIDCanVote(const UINT8 playerId);
|
||||
|
|
@ -32,6 +29,7 @@ void Y_VoteTicker(void);
|
|||
void Y_StartVote(void);
|
||||
void Y_EndVote(void);
|
||||
void Y_SetupVoteFinish(SINT8 pick, SINT8 level, SINT8 anger);
|
||||
UINT8 Y_VoteContext(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
|
|
|
|||
|
|
@ -709,6 +709,8 @@ static int player_get(lua_State *L)
|
|||
lua_pushinteger(L, plr->checkskip);
|
||||
else if (fastcmp(field,"cheatchecknum"))
|
||||
lua_pushinteger(L, plr->cheatchecknum);
|
||||
else if (fastcmp(field,"duelscore"))
|
||||
lua_pushinteger(L, plr->duelscore);
|
||||
else if (fastcmp(field,"lastsidehit"))
|
||||
lua_pushinteger(L, plr->lastsidehit);
|
||||
else if (fastcmp(field,"lastlinehit"))
|
||||
|
|
@ -1312,6 +1314,8 @@ static int player_set(lua_State *L)
|
|||
plr->checkskip = (INT32)luaL_checkinteger(L, 3);
|
||||
else if (fastcmp(field,"cheatchecknum"))
|
||||
plr->cheatchecknum = (INT32)luaL_checkinteger(L, 3);
|
||||
else if (fastcmp(field,"duelscore"))
|
||||
plr->duelscore = (INT16)luaL_checkinteger(L, 3);
|
||||
else if (fastcmp(field,"lastsidehit"))
|
||||
plr->lastsidehit = (INT16)luaL_checkinteger(L, 3);
|
||||
else if (fastcmp(field,"lastlinehit"))
|
||||
|
|
|
|||
|
|
@ -1858,7 +1858,10 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
|
|||
&& (gamespeed != KARTSPEED_EASY)
|
||||
&& (player->tally.active == true)
|
||||
&& (player->tally.totalExp > 0) // Only true if not Time Attack
|
||||
&& (player->tally.exp >= player->tally.totalExp));
|
||||
&& (
|
||||
(player->tally.exp >= player->tally.totalExp)
|
||||
|| (K_InRaceDuel() && player->duelscore == DUELWINNINGSCORE)
|
||||
));
|
||||
case UCRP_FINISHALLPRISONS:
|
||||
return (battleprisons
|
||||
&& !(player->pflags & PF_NOCONTEST)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,17 @@ menuitem_t OPTIONS_Gameplay[] =
|
|||
NULL, {.cvar = &cv_kartbumpers}, 0, 0},
|
||||
|
||||
|
||||
|
||||
{IT_HEADER, "Duel...", NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Duel Time Limit", "How long it takes for Margin Boost to kick in (seconds).",
|
||||
NULL, {.cvar = &cv_dueltimelimit}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Duel Score Limit", "How many points a player must be ahead to win a Duel.",
|
||||
NULL, {.cvar = &cv_duelscorelimit}, 0, 0},
|
||||
|
||||
|
||||
{IT_SPACE | IT_DYBIGSPACE, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
|
|
|
|||
|
|
@ -9110,7 +9110,10 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
|
|||
&& (gamespeed != KARTSPEED_EASY)
|
||||
&& (newplayer->tally.active == true)
|
||||
&& (newplayer->tally.totalExp > 0) // Only true if not Time Attack
|
||||
&& (newplayer->tally.exp >= newplayer->tally.totalExp)
|
||||
&& (
|
||||
(newplayer->tally.exp >= newplayer->tally.totalExp) ||
|
||||
(K_InRaceDuel() && newplayer->duelscore == DUELWINNINGSCORE)
|
||||
)
|
||||
)
|
||||
{
|
||||
UINT8 pnum = (newplayer-players);
|
||||
|
|
|
|||
|
|
@ -291,6 +291,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
|
|||
WRITEUINT32(save->p, players[i].exp);
|
||||
WRITEINT32(save->p, players[i].gradingfactor);
|
||||
WRITEUINT16(save->p, players[i].gradingpointnum);
|
||||
WRITEINT16(save->p, players[i].duelscore);
|
||||
WRITEINT32(save->p, players[i].cheatchecknum);
|
||||
WRITEINT32(save->p, players[i].checkpointId);
|
||||
|
||||
|
|
@ -983,6 +984,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
|
|||
players[i].exp = READUINT32(save->p);
|
||||
players[i].gradingfactor = READINT32(save->p);
|
||||
players[i].gradingpointnum = READUINT16(save->p);
|
||||
players[i].duelscore = READINT16(save->p);
|
||||
players[i].cheatchecknum = READINT32(save->p);
|
||||
players[i].checkpointId = READINT32(save->p);
|
||||
|
||||
|
|
@ -6757,6 +6759,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending)
|
|||
{
|
||||
WRITEUINT16(save->p, g_voteLevels[i][0]);
|
||||
WRITEUINT16(save->p, g_voteLevels[i][1]);
|
||||
WRITEUINT8(save->p, g_votes_striked[i]);
|
||||
}
|
||||
|
||||
for (i = 0; i < VOTE_TOTAL; i++)
|
||||
|
|
@ -6834,6 +6837,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending)
|
|||
WRITESINT8(save->p, spbplace);
|
||||
WRITEUINT8(save->p, rainbowstartavailable);
|
||||
WRITEUINT8(save->p, inDuel);
|
||||
WRITEUINT8(save->p, overtimecheckpoints);
|
||||
|
||||
WRITEUINT32(save->p, introtime);
|
||||
WRITEUINT32(save->p, starttime);
|
||||
|
|
@ -7143,6 +7147,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading)
|
|||
{
|
||||
g_voteLevels[i][0] = READUINT16(save->p);
|
||||
g_voteLevels[i][1] = READUINT16(save->p);
|
||||
g_votes_striked[i] = (boolean)READUINT8(save->p);
|
||||
}
|
||||
|
||||
for (i = 0; i < VOTE_TOTAL; i++)
|
||||
|
|
@ -7216,6 +7221,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading)
|
|||
spbplace = READSINT8(save->p);
|
||||
rainbowstartavailable = (boolean)READUINT8(save->p);
|
||||
inDuel = (boolean)READUINT8(save->p);
|
||||
overtimecheckpoints = (boolean)READUINT8(save->p);
|
||||
|
||||
introtime = READUINT32(save->p);
|
||||
starttime = READUINT32(save->p);
|
||||
|
|
|
|||
54
src/p_spec.c
54
src/p_spec.c
|
|
@ -53,6 +53,7 @@
|
|||
#include "m_easing.h"
|
||||
#include "music.h"
|
||||
#include "k_battle.h" // battleprisons
|
||||
#include "k_endcam.h" // K_EndCameraIsFreezing()
|
||||
|
||||
// Not sure if this is necessary, but it was in w_wad.c, so I'm putting it here too -Shadow Hog
|
||||
#include <errno.h>
|
||||
|
|
@ -1995,7 +1996,7 @@ static void K_HandleLapIncrement(player_t *player)
|
|||
}
|
||||
|
||||
// finished race exit setup
|
||||
if (player->laps > numlaps)
|
||||
if (player->laps > numlaps && !K_InRaceDuel())
|
||||
{
|
||||
pflags_t applyflags = 0;
|
||||
if (specialstageinfo.valid == true)
|
||||
|
|
@ -2021,7 +2022,8 @@ static void K_HandleLapIncrement(player_t *player)
|
|||
: skins[player->skin].flags;
|
||||
if (skinflags & SF_IRONMAN)
|
||||
{
|
||||
SetRandomFakePlayerSkin(player, true, false);
|
||||
if ((player->laps == 1 && lapisfresh) || !K_InRaceDuel()) // We'll do this in K_CheckpointCrossAward if necessary.
|
||||
SetRandomFakePlayerSkin(player, true, false);
|
||||
}
|
||||
|
||||
// Always trust waypoints entering the first lap.
|
||||
|
|
@ -2064,24 +2066,32 @@ static void K_HandleLapIncrement(player_t *player)
|
|||
|
||||
if (rainbowstartavailable == true && player->mo->hitlag == 0)
|
||||
{
|
||||
S_StartSound(player->mo, sfx_s23c);
|
||||
player->startboost = 125;
|
||||
|
||||
K_SpawnDriftBoostExplosion(player, 4);
|
||||
K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false);
|
||||
K_SpawnAmps(player, 35, player->mo);
|
||||
|
||||
if (g_teamplay)
|
||||
if (K_InRaceDuel())
|
||||
{
|
||||
for (UINT8 j = 0; i < MAXPLAYERS; i++)
|
||||
K_SpawnDriftElectricSparks(player, player->skincolor, false);
|
||||
K_SpawnAmps(player, 20, player->mo);
|
||||
}
|
||||
else
|
||||
{
|
||||
S_StartSound(player->mo, sfx_s23c);
|
||||
player->startboost = 125;
|
||||
|
||||
K_SpawnDriftBoostExplosion(player, 4);
|
||||
K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false);
|
||||
K_SpawnAmps(player, (K_InRaceDuel()) ? 20 : 35, player->mo);
|
||||
|
||||
if (g_teamplay)
|
||||
{
|
||||
if (!playeringame[j] || players[j].spectator || !players[j].mo || P_MobjWasRemoved(players[j].mo))
|
||||
continue;
|
||||
if (!G_SameTeam(player, &players[j]))
|
||||
continue;
|
||||
if (player == &players[j])
|
||||
continue;
|
||||
K_SpawnAmps(&players[j], 10, player->mo);
|
||||
for (UINT8 j = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
if (!playeringame[j] || players[j].spectator || !players[j].mo || P_MobjWasRemoved(players[j].mo))
|
||||
continue;
|
||||
if (!G_SameTeam(player, &players[j]))
|
||||
continue;
|
||||
if (player == &players[j])
|
||||
continue;
|
||||
K_SpawnAmps(&players[j], 10, player->mo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2105,7 +2115,9 @@ static void K_HandleLapIncrement(player_t *player)
|
|||
}
|
||||
else if (P_IsDisplayPlayer(player))
|
||||
{
|
||||
if (numlaps > 1 && player->laps == numlaps) // final lap
|
||||
if (K_InRaceDuel())
|
||||
S_StartSound(NULL, sfx_s221);
|
||||
else if (numlaps > 1 && player->laps == numlaps) // final lap
|
||||
S_StartSound(NULL, sfx_s3k68);
|
||||
else if ((player->laps > 1) && (player->laps < numlaps)) // non-final lap
|
||||
S_StartSound(NULL, sfx_s221);
|
||||
|
|
@ -2118,7 +2130,7 @@ static void K_HandleLapIncrement(player_t *player)
|
|||
}
|
||||
else
|
||||
{
|
||||
if ((player->laps > numlaps) && (player->position == 1))
|
||||
if ((player->laps > numlaps) && (player->position == 1) && (!K_InRaceDuel()))
|
||||
{
|
||||
// opponent finished
|
||||
S_StartSound(NULL, sfx_s253);
|
||||
|
|
@ -4752,7 +4764,7 @@ void P_SetupSignExit(player_t *player, boolean tie)
|
|||
return;
|
||||
|
||||
// SRB2Kart: FINALLY, add in an alternative if no place is found
|
||||
if (player->mo && !P_MobjWasRemoved(player->mo))
|
||||
if (player->mo && !P_MobjWasRemoved(player->mo) && !K_EndCameraIsFreezing())
|
||||
{
|
||||
thing = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->floorz, MT_SIGN);
|
||||
thing->angle = bestAngle;
|
||||
|
|
|
|||
|
|
@ -1389,7 +1389,7 @@ void P_DoPlayerExit(player_t *player, pflags_t flags)
|
|||
void P_DoAllPlayersExit(pflags_t flags, boolean trygivelife)
|
||||
{
|
||||
UINT8 i;
|
||||
const boolean dofinishsound = (musiccountdown == 0);
|
||||
const boolean dofinishsound = (musiccountdown == 0) && (!K_InRaceDuel());
|
||||
|
||||
if (grandprixinfo.gp == false
|
||||
|| grandprixinfo.eventmode == GPEVENT_SPECIAL
|
||||
|
|
|
|||
|
|
@ -2211,6 +2211,35 @@ boolean Y_ShouldDoIntermission(void)
|
|||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Y_GetIntermissionType
|
||||
//
|
||||
// Returns the intermission type from the current gametype.
|
||||
//
|
||||
intertype_t Y_GetIntermissionType(void)
|
||||
{
|
||||
intertype_t ret = static_cast<intertype_t>(gametypes[gametype]->intermission);
|
||||
|
||||
if (ret == int_scoreortimeattack)
|
||||
{
|
||||
UINT8 i = 0, nump = 0;
|
||||
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
if (!playeringame[i] || players[i].spectator)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
nump++;
|
||||
}
|
||||
|
||||
ret = (nump < 2 ? int_time : int_score);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
//
|
||||
// Y_DetermineIntermissionType
|
||||
//
|
||||
|
|
@ -2225,21 +2254,7 @@ void Y_DetermineIntermissionType(void)
|
|||
return;
|
||||
}
|
||||
|
||||
// set initially
|
||||
intertype = static_cast<intertype_t>(gametypes[gametype]->intermission);
|
||||
|
||||
// special cases
|
||||
if (intertype == int_scoreortimeattack)
|
||||
{
|
||||
UINT8 i = 0, nump = 0;
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
if (!playeringame[i] || players[i].spectator)
|
||||
continue;
|
||||
nump++;
|
||||
}
|
||||
intertype = (nump < 2 ? int_time : int_score);
|
||||
}
|
||||
intertype = Y_GetIntermissionType();
|
||||
}
|
||||
|
||||
static UINT8 Y_PlayersBestPossiblePosition(player_t *const player)
|
||||
|
|
|
|||
|
|
@ -62,9 +62,6 @@ void Y_StartIntermission(void);
|
|||
void Y_MidIntermission(void);
|
||||
void Y_EndIntermission(void);
|
||||
|
||||
boolean Y_ShouldDoIntermission(void);
|
||||
void Y_DetermineIntermissionType(void);
|
||||
|
||||
void Y_PlayIntermissionMusic(void);
|
||||
|
||||
boolean Y_IntermissionPlayerLock(void);
|
||||
|
|
@ -79,6 +76,10 @@ typedef enum
|
|||
|
||||
extern intertype_t intertype;
|
||||
|
||||
boolean Y_ShouldDoIntermission(void);
|
||||
intertype_t Y_GetIntermissionType(void);
|
||||
void Y_DetermineIntermissionType(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue