diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 968667ef8..8cfba2d4c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -142,8 +142,8 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 k_profiles.c k_specialstage.c k_roulette.c - k_podium.c - k_rank.c + k_podium.cpp + k_rank.cpp k_vote.c k_serverstats.c k_zvote.c diff --git a/src/cvars.cpp b/src/cvars.cpp index 4acadb22d..4b0897dcf 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -802,7 +802,7 @@ consvar_t cv_kartdebugwaypoints = OnlineCheat("debugwaypoints", "Off").values({{ extern CV_PossibleValue_t numlaps_cons_t[]; void NumLaps_OnChange(void); -consvar_t cv_numlaps = OnlineCheat("numlaps", "Map default").values(numlaps_cons_t).onchange(NumLaps_OnChange).save().description("Race maps always have the same number of laps"); +consvar_t cv_numlaps = OnlineCheat("numlaps", "Map default").values(numlaps_cons_t).onchange(NumLaps_OnChange).description("Race maps always have the same number of laps"); consvar_t cv_restrictskinchange = OnlineCheat("restrictskinchange", "Yes").yes_no().description("Don't let players change their skin in the middle of gameplay"); consvar_t cv_spbtest = OnlineCheat("spbtest", "Off").on_off().description("SPB can never target a player"); diff --git a/src/g_game.c b/src/g_game.c index c59f9c635..e992930e3 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4199,7 +4199,10 @@ static void G_DoCompleted(void) if (intertype == int_none) { G_UpdateVisited(); - K_UpdateGPRank(); + if (grandprixinfo.gp == true) + { + K_UpdateGPRank(&grandprixinfo.rank); + } G_AfterIntermission(); } else @@ -5857,7 +5860,13 @@ char *G_BuildMapTitle(INT32 mapnum) char *title = NULL; if (!mapnum || mapnum > nummapheaders || !mapheaderinfo[mapnum-1]) + { +#ifdef PARANOIA I_Error("G_BuildMapTitle: Internal map ID %d not found (nummapheaders = %d)", mapnum-1, nummapheaders); +#else + return NULL; +#endif + } if (strcmp(mapheaderinfo[mapnum-1]->lvlttl, "")) { diff --git a/src/k_battle.c b/src/k_battle.c index 8b8918d3d..1304489a3 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -40,7 +40,23 @@ UINT8 numtargets = 0; // Capsules busted INT32 K_StartingBumperCount(void) { if (battleprisons) - return 0; // always 1 hit in Prison Break + { + if (grandprixinfo.gp) + { + switch (grandprixinfo.gamespeed) + { + case KARTSPEED_HARD: + return (grandprixinfo.masterbots == true) ? 0 : 1; + case KARTSPEED_NORMAL: + return 2; + case KARTSPEED_EASY: + return 3; + } + + } + + return 2; // Normal + } return cv_kartbumpers.value; } diff --git a/src/k_hud.c b/src/k_hud.c index 3378f7cf5..6500affbf 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1976,9 +1976,8 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT32 splitflags, U } } -static fixed_t K_DrawKartPositionNumPatch(UINT8 num, UINT8 *color, fixed_t x, fixed_t y, fixed_t scale, INT32 flags) +static fixed_t K_DrawKartPositionNumPatch(UINT8 num, UINT8 splitIndex, UINT8 *color, fixed_t x, fixed_t y, fixed_t scale, INT32 flags) { - const UINT8 splitIndex = (r_splitscreen > 0) ? 1 : 0; fixed_t w = FRACUNIT; fixed_t h = FRACUNIT; INT32 overlayFlags[2]; @@ -2023,14 +2022,76 @@ static fixed_t K_DrawKartPositionNumPatch(UINT8 num, UINT8 *color, fixed_t x, fi return x; } +void K_DrawKartPositionNumXY( + UINT8 num, + UINT8 splitIndex, + fixed_t fx, fixed_t fy, fixed_t scale, INT32 fflags, + tic_t counter, boolean subtract, + boolean exit, boolean lastLap, boolean losing + ) +{ + counter /= 3; // Alternate colors every three frames + + UINT8 *color = NULL; + if (exit && num == 1) + { + // 1st place winner? You get rainbows!! + color = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_POSNUM_BEST1 + (counter % 6), GTC_CACHE); + } + else if (exit || lastLap) + { + // On the final lap, or already won. + boolean useRedNums = losing; + + if (subtract) + { + // Subtracting RED will look BLUE, and vice versa. + useRedNums = !useRedNums; + } + + if (useRedNums == true) + { + color = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_POSNUM_LOSE1 + (counter % 3), GTC_CACHE); + } + else + { + color = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_POSNUM_WIN1 + (counter % 3), GTC_CACHE); + } + } + else + { + color = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_POSNUM, GTC_CACHE); + } + + if ((fflags & V_SNAPTORIGHT) == 0) + { + UINT8 adjustNum = num; + do + { + fixed_t w = SHORT(kp_positionnum[adjustNum % 10][0][splitIndex]->width) * scale; + fx += w; + adjustNum /= 10; + } while (adjustNum); + } + + // Draw the number + do + { + fx = K_DrawKartPositionNumPatch( + (num % 10), splitIndex, color, + fx, fy, scale, V_SPLITSCREEN|fflags + ); + num /= 10; + } while (num); +} + static void K_DrawKartPositionNum(UINT8 num) { - const tic_t counter = (leveltime / 3); // Alternate colors every three frames + UINT8 splitIndex = (r_splitscreen > 0) ? 1 : 0; fixed_t scale = FRACUNIT; fixed_t fx = 0, fy = 0; transnum_t trans = 0; INT32 fflags = 0; - UINT8 *color = NULL; if (stplyr->lives <= 0 && stplyr->playerstate == PST_DEAD) { @@ -2110,57 +2171,16 @@ static void K_DrawKartPositionNum(UINT8 num) fflags |= (trans << V_ALPHASHIFT); } - if (stplyr->exiting && num == 1) - { - // 1st place winner? You get rainbows!! - color = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_POSNUM_BEST1 + (counter % 6), GTC_CACHE); - } - else if (stplyr->laps >= numlaps || stplyr->exiting) - { - // On the final lap, or already won. - boolean useRedNums = K_IsPlayerLosing(stplyr); - - if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SUBTRACTNUM) == LF_SUBTRACTNUM) - { - // Subtracting RED will look BLUE, and vice versa. - useRedNums = !useRedNums; - } - - if (useRedNums == true) - { - color = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_POSNUM_LOSE1 + (counter % 3), GTC_CACHE); - } - else - { - color = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_POSNUM_WIN1 + (counter % 3), GTC_CACHE); - } - } - else - { - color = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_POSNUM, GTC_CACHE); - } - - if ((fflags & V_SNAPTORIGHT) == 0) - { - const UINT8 splitIndex = (r_splitscreen > 0) ? 1 : 0; - UINT8 adjustNum = num; - do - { - fixed_t w = SHORT(kp_positionnum[adjustNum % 10][0][splitIndex]->width) * scale; - fx += w; - adjustNum /= 10; - } while (adjustNum); - } - - // Draw the number - do - { - fx = K_DrawKartPositionNumPatch( - (num % 10), color, - fx, fy, scale, V_SPLITSCREEN|fflags - ); - num /= 10; - } while (num); + K_DrawKartPositionNumXY( + num, + splitIndex, + fx, fy, scale, fflags, + leveltime, + ((mapheaderinfo[gamemap - 1]->levelflags & LF_SUBTRACTNUM) == LF_SUBTRACTNUM), + stplyr->exiting, + (stplyr->laps >= numlaps), + K_IsPlayerLosing(stplyr) + ); } static boolean K_drawKartPositionFaces(void) diff --git a/src/k_hud.h b/src/k_hud.h index fb94b0734..c57a342dc 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -54,6 +54,14 @@ void K_drawButton(fixed_t x, fixed_t y, INT32 flags, patch_t *button[2], boolean void K_drawButtonAnim(INT32 x, INT32 y, INT32 flags, patch_t *button[2], tic_t animtic); void K_DrawSticker(INT32 x, INT32 y, INT32 width, INT32 flags, boolean isSmall); +void K_DrawKartPositionNumXY( + UINT8 num, + UINT8 splitIndex, + fixed_t fx, fixed_t fy, fixed_t scale, INT32 fflags, + tic_t counter, boolean subtract, + boolean exit, boolean lastLap, boolean losing +); + extern patch_t *kp_capsuletarget_arrow[2][2]; extern patch_t *kp_capsuletarget_icon[2]; extern patch_t *kp_capsuletarget_far[2][2]; diff --git a/src/k_kart.c b/src/k_kart.c index 3a5ce7606..fbfed0ddd 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3128,7 +3128,7 @@ mobj_t *K_GetGardenTop(player_t *player) static fixed_t K_FlameShieldDashVar(INT32 val) { // 1 second = 75% + 50% top speed - return (3*FRACUNIT/4) + (((val * FRACUNIT) / TICRATE) / 2); + return (3*FRACUNIT/4) + (((val * FRACUNIT) / TICRATE)); } INT16 K_GetSpindashChargeTime(player_t *player) @@ -4232,8 +4232,8 @@ void K_UpdateSliptideZipIndicator(player_t *player) mobj->angle = momentumAngle + ANGLE_90; P_SetScale(mobj, 3 * player->mo->scale / 2); - // No stored boost - if (player->sliptideZip == 0) + // No stored boost (or negligible enough that it might be a mistake) + if (player->sliptideZip <= HIDEWAVEDASHCHARGE) { mobj->renderflags |= RF_DONTDRAW; mobj->frame = 7; @@ -9892,7 +9892,7 @@ static void K_KartDrift(player_t *player, boolean onground) { if (!keepsliptide && K_IsLosingSliptideZip(player) && player->sliptideZip > 0) { - if (!S_SoundPlaying(player->mo, sfx_waved2)) + if (player->sliptideZip > HIDEWAVEDASHCHARGE && !S_SoundPlaying(player->mo, sfx_waved2)) S_StartSoundAtVolume(player->mo, sfx_waved2, 255); // Losing combo time, going to boost S_StopSoundByID(player->mo, sfx_waved1); S_StopSoundByID(player->mo, sfx_waved4); @@ -9954,7 +9954,7 @@ static void K_KartDrift(player_t *player, boolean onground) player->sliptideZipDelay = 0; S_StopSoundByID(player->mo, sfx_waved2); S_StopSoundByID(player->mo, sfx_waved4); - if (!S_SoundPlaying(player->mo, sfx_waved1)) + if (player->sliptideZip > HIDEWAVEDASHCHARGE && !S_SoundPlaying(player->mo, sfx_waved1)) S_StartSoundAtVolume(player->mo, sfx_waved1, 255); // Charging } @@ -10193,7 +10193,7 @@ static INT32 K_FlameShieldMax(player_t *player) UINT32 distv = 1024; // Pre no-scams: 2048 distv = distv * 16 / FLAMESHIELD_MAX; // Old distv was based on a 16-segment bar UINT8 numplayers = 0; - UINT32 scamradius = 2000; // How close is close enough that we shouldn't be allowed to scam 1st? + UINT32 scamradius = 1500; // How close is close enough that we shouldn't be allowed to scam 1st? UINT8 i; if (gametyperules & GTR_CIRCUIT) @@ -11709,29 +11709,31 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->flamelength < destlen) player->flamelength = min(destlen, player->flamelength + 7); // Allows gauge to grow quickly when first acquired. 120/16 = ~7 - flamemax = player->flamelength; - if (flamemax > 0) - flamemax += TICRATE; // leniency period + flamemax = player->flamelength + TICRATE; // TICRATE leniency period, but we block most effects at flamelength 0 down below if ((cmd->buttons & BT_ATTACK) && (player->pflags & PF_HOLDREADY)) { const INT32 incr = (gametyperules & GTR_CLOSERPLAYERS) ? 4 : 2; - - if (player->flamedash == 0) - { - S_StartSound(player->mo, sfx_s3k43); - K_PlayBoostTaunt(player->mo); - } - - player->flamedash += incr; player->flamemeter += incr; - if (!onground) + if (player->flamelength) { - P_Thrust( - player->mo, K_MomentumAngle(player->mo), - FixedMul(player->mo->scale, K_GetKartGameSpeedScalar(gamespeed)) - ); + + if (player->flamedash == 0) + { + S_StartSound(player->mo, sfx_s3k43); + K_PlayBoostTaunt(player->mo); + } + + player->flamedash += incr; + + if (!onground) + { + P_Thrust( + player->mo, K_MomentumAngle(player->mo), + FixedMul(player->mo->scale, K_GetKartGameSpeedScalar(gamespeed)) + ); + } } if (player->flamemeter > flamemax) @@ -12445,6 +12447,13 @@ tic_t K_TimeLimitForGametype(void) { if (battleprisons) { + if (grandprixinfo.gp) + { + if (grandprixinfo.masterbots) + return 15*TICRATE; + else if (grandprixinfo.gamespeed == KARTSPEED_EASY) + return 30*TICRATE; + } return 20*TICRATE; } diff --git a/src/k_kart.h b/src/k_kart.h index 7d0379760..56f9428bf 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -54,6 +54,10 @@ Make sure this matches the actual number of states #define RINGVOLUMEUSEPENALTY 15 #define RINGVOLUMEREGEN 3 +// Mispredicted turns can generate phantom sliptide inputs for a few tics. +// Delay the wavedash visuals until we're reasonably sure that it's a deliberate turn. +#define HIDEWAVEDASHCHARGE (60) + angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t yourspeed); boolean K_IsDuelItem(mobjtype_t type); diff --git a/src/k_menu.h b/src/k_menu.h index f2fd07acf..c02f29d50 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1158,6 +1158,7 @@ void M_DrawImageDef(void); void M_DrawCharacterSelect(void); boolean M_DrawCharacterSprite(INT16 x, INT16 y, INT16 skin, UINT8 spr2, UINT8 rotation, UINT32 frame, INT32 addflags, UINT8 *colormap); +void M_DrawCup(cupheader_t *cup, fixed_t x, fixed_t y, INT32 lockedTic, boolean isTrophy, UINT8 placement); void M_DrawCupSelect(void); void M_DrawLevelSelect(void); void M_DrawTimeAttack(void); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index ed8678f1c..e478e1bcf 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2601,11 +2601,98 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi return rankw; } +void M_DrawCup(cupheader_t *cup, fixed_t x, fixed_t y, INT32 lockedTic, boolean isTrophy, UINT8 placement) +{ + patch_t *patch = NULL; + UINT8 *colormap = NULL; + INT16 icony = 7; + char status = 'A'; + char monitor = '0'; + + if (isTrophy) + { + UINT16 col = SKINCOLOR_NONE; + + switch (placement) + { + case 0: + break; + case 1: + col = SKINCOLOR_GOLD; + status = 'B'; + break; + case 2: + col = SKINCOLOR_SILVER; + status = 'B'; + break; + case 3: + col = SKINCOLOR_BRONZE; + status = 'B'; + break; + default: + col = SKINCOLOR_BEIGE; + break; + } + + if (col != SKINCOLOR_NONE) + colormap = R_GetTranslationColormap(TC_RAINBOW, col, GTC_MENUCACHE); + else + colormap = NULL; + } + + if (cup == &dummy_lostandfound) + { + // No cup? Lost and found! + monitor = '0'; + } + else + { + if (cup->monitor < 10) + { + monitor = '0' + cup->monitor; + + if (monitor == '2') + { + icony = 5; // by default already 7px down, so this is really 2px further up + } + else if (monitor == '3') + { + icony = 6; + } + } + else + { + monitor = 'A' + (cup->monitor - 10); + if (monitor == 'X') + { + icony = 11; + } + } + } + + patch = W_CachePatchName(va("CUPMON%c%c", monitor, status), PU_CACHE); + V_DrawFixedPatch(x, y, FRACUNIT, 0, patch, colormap); + + if (cup == &dummy_lostandfound) + { + ; // Only ever placed on the list if valid + } + else if (lockedTic != 0) + { + patch_t *st = W_CachePatchName(va("ICONST0%d", (lockedTic % 4) + 1), PU_CACHE); + V_DrawFixedPatch(x + (8 * FRACUNIT), y + (icony * FRACUNIT), FRACUNIT, 0, st, NULL); + } + else + { + V_DrawFixedPatch(x + (8 * FRACUNIT), y + (icony * FRACUNIT), FRACUNIT, 0, W_CachePatchName(cup->icon, PU_CACHE), NULL); + V_DrawFixedPatch(x + (8 * FRACUNIT), y + (icony * FRACUNIT), FRACUNIT, 0, W_CachePatchName("CUPBOX", PU_CACHE), NULL); + } +} + void M_DrawCupSelect(void) { UINT8 i, j, temp = 0; INT16 x, y; - UINT8 *colormap = NULL; cupwindata_t *windata = NULL; levelsearch_t templevelsearch = levellist.levelsearch; // full copy @@ -2614,117 +2701,46 @@ void M_DrawCupSelect(void) for (j = 0; j < CUPMENU_ROWS; j++) { size_t id = (i + (j * CUPMENU_COLUMNS)) + (cupgrid.pageno * (CUPMENU_COLUMNS * CUPMENU_ROWS)); - patch_t *patch = NULL; - INT16 icony = 7; - char status = 'A'; - char monitor; if (!cupgrid.builtgrid[id]) break; templevelsearch.cup = cupgrid.builtgrid[id]; - if (cupgrid.grandprix - && (cv_dummygpdifficulty.value >= 0 && cv_dummygpdifficulty.value < KARTGP_MAX)) - { - UINT16 col = SKINCOLOR_NONE; - - windata = &templevelsearch.cup->windata[cv_dummygpdifficulty.value]; - - switch (windata->best_placement) - { - case 0: - break; - case 1: - col = SKINCOLOR_GOLD; - status = 'B'; - break; - case 2: - col = SKINCOLOR_SILVER; - status = 'B'; - break; - case 3: - col = SKINCOLOR_BRONZE; - status = 'B'; - break; - default: - col = SKINCOLOR_BEIGE; - break; - } - - if (col != SKINCOLOR_NONE) - colormap = R_GetTranslationColormap(TC_RAINBOW, col, GTC_MENUCACHE); - else - colormap = NULL; - } - - if (templevelsearch.cup == &dummy_lostandfound) - { - // No cup? Lost and found! - monitor = '0'; - } - else - { - if (templevelsearch.cup->monitor < 10) - { - monitor = '0' + templevelsearch.cup->monitor; - - if (monitor == '2') - { - icony = 5; // by default already 7px down, so this is really 2px further up - } - else if (monitor == '3') - { - icony = 6; - } - } - else - { - monitor = 'A' + (templevelsearch.cup->monitor - 10); - if (monitor == 'X') - { - icony = 11; - } - } - } - - patch = W_CachePatchName(va("CUPMON%c%c", monitor, status), PU_CACHE); - x = 14 + (i*42); y = 20 + (j*44) - (30*menutransition.tics); - V_DrawFixedPatch((x)*FRACUNIT, (y)<= 0 && cv_dummygpdifficulty.value < KARTGP_MAX)); + if (isGP) { - patch_t *st = W_CachePatchName(va("ICONST0%d", (cupgrid.previewanim % 4) + 1), PU_CACHE); - V_DrawScaledPatch(x + 8, y + icony, 0, st); + windata = &templevelsearch.cup->windata[cv_dummygpdifficulty.value]; } - else - { - V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName(templevelsearch.cup->icon, PU_CACHE)); - V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName("CUPBOX", PU_CACHE)); - if (cupgrid.grandprix == true + M_DrawCup( + templevelsearch.cup, + x * FRACUNIT, y * FRACUNIT, + (M_GetFirstLevelInList(&temp, &templevelsearch) == NEXTMAP_INVALID) ? ((cupgrid.previewanim % 4) + 1) : 0, + isGP, + windata ? windata->best_placement : 0 + ); + + if (cupgrid.grandprix == true && templevelsearch.cup == cupsavedata.cup && id != CUPMENU_CURSORID) - { - V_DrawScaledPatch(x + 32, y + 32, 0, W_CachePatchName("CUPBKUP1", PU_CACHE)); - } + { + V_DrawScaledPatch(x + 32, y + 32, 0, W_CachePatchName("CUPBKUP1", PU_CACHE)); + } - if (windata && windata->best_placement != 0) - { - M_DrawCupWinData( - x, - 8 + (j*100) - (30*menutransition.tics), - templevelsearch.cup, - cv_dummygpdifficulty.value, - (cupgrid.previewanim & 1), - false - ); - } + if (windata && windata->best_placement != 0) + { + M_DrawCupWinData( + x, + 8 + (j*100) - (30*menutransition.tics), + templevelsearch.cup, + cv_dummygpdifficulty.value, + (cupgrid.previewanim & 1), + false + ); } } } diff --git a/src/k_podium.c b/src/k_podium.c deleted file mode 100644 index c8764dd8a..000000000 --- a/src/k_podium.c +++ /dev/null @@ -1,614 +0,0 @@ -// DR. ROBOTNIK'S RING RACERS -//----------------------------------------------------------------------------- -// Copyright (C) by Sally "TehRealSalt" Cochenour -// Copyright (C) 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_podium.c -/// \brief Grand Prix podium cutscene - -#include "k_podium.h" - -#include "doomdef.h" -#include "d_main.h" -#include "d_netcmd.h" -#include "f_finale.h" -#include "g_game.h" -#include "hu_stuff.h" -#include "r_local.h" -#include "s_sound.h" -#include "i_time.h" -#include "i_video.h" -#include "v_video.h" -#include "w_wad.h" -#include "z_zone.h" -#include "i_system.h" -#include "i_threads.h" -#include "dehacked.h" -#include "g_input.h" -#include "console.h" -#include "m_random.h" -#include "m_misc.h" // moviemode functionality -#include "y_inter.h" -#include "m_cond.h" -#include "p_local.h" -#include "p_saveg.h" -#include "p_setup.h" -#include "st_stuff.h" // hud hiding -#include "fastcmp.h" - -#include "lua_hud.h" -#include "lua_hook.h" - -#include "k_menu.h" -#include "k_grandprix.h" -#include "k_rank.h" - -static struct podiumData_s -{ - boolean ranking; - gpRank_t rank; - gp_rank_e grade; - UINT8 state; - UINT8 delay; - UINT8 fade; -} podiumData; - -#define PODIUM_STATES (9) // TODO: enum when this actually gets made - -/*-------------------------------------------------- - boolean K_PodiumSequence(void) - - See header file for description. ---------------------------------------------------*/ -boolean K_PodiumSequence(void) -{ - return (gamestate == GS_CEREMONY); -} - -/*-------------------------------------------------- - boolean K_PodiumRanking(void) - - See header file for description. ---------------------------------------------------*/ -boolean K_PodiumRanking(void) -{ - return (gamestate == GS_CEREMONY && podiumData.ranking == true); -} - -/*-------------------------------------------------- - boolean K_PodiumGrade(void) - - See header file for description. ---------------------------------------------------*/ -gp_rank_e K_PodiumGrade(void) -{ - if (K_PodiumSequence() == false) - { - return 0; - } - - return podiumData.grade; -} - -/*-------------------------------------------------- - boolean K_PodiumHasEmerald(void) - - See header file for description. ---------------------------------------------------*/ -boolean K_PodiumHasEmerald(void) -{ - if (K_PodiumSequence() == false) - { - return false; - } - - return podiumData.rank.specialWon; -} - -/*-------------------------------------------------- - UINT8 K_GetPodiumPosition(player_t *player) - - See header file for description. ---------------------------------------------------*/ -UINT8 K_GetPodiumPosition(player_t *player) -{ - UINT8 position = 1; - INT32 i; - - for (i = 0; i < MAXPLAYERS; i++) - { - player_t *other = NULL; - if (playeringame[i] == false) - { - continue; - } - - other = &players[i]; - if (other->bot == false && other->spectator == true) - { - continue; - } - - if (other->score > player->score) - { - // Final score is the important part. - position++; - } - else if (other->score == player->score) - { - if (other->bot == false && player->bot == true) - { - // Bots are never as important as players. - position++; - } - else if (i < player - players) - { - // Port priority is the final tie breaker. - position++; - } - } - } - - return position; -} - -/*-------------------------------------------------- - static void K_SetPodiumWaypoint(player_t *const player, waypoint_t *const waypoint) - - Changes the player's current and next waypoints, for - use during the podium sequence. - - Input Arguments:- - player - The player to update the waypoints of. - waypoint - The new current waypoint. - - Return:- - None ---------------------------------------------------*/ -static void K_SetPodiumWaypoint(player_t *const player, waypoint_t *const waypoint) -{ - // Set the new waypoint. - player->currentwaypoint = waypoint; - - if ((waypoint == NULL) - || (waypoint->nextwaypoints == NULL) - || (waypoint->numnextwaypoints == 0U)) - { - // No waypoint, or no next waypoint. - player->nextwaypoint = NULL; - return; - } - - // Simply use the first available next waypoint. - // No need for split paths in these cutscenes. - player->nextwaypoint = waypoint->nextwaypoints[0]; -} - -/*-------------------------------------------------- - void K_InitializePodiumWaypoint(player_t *const player) - - See header file for description. ---------------------------------------------------*/ -void K_InitializePodiumWaypoint(player_t *const player) -{ - if ((player != NULL) && (player->mo != NULL)) - { - player->position = K_GetPodiumPosition(player); - - if (player->position > 0 && player->position <= MAXPLAYERS) - { - // Initialize our first waypoint to the one that - // matches our position. - K_SetPodiumWaypoint(player, K_GetWaypointFromID(player->position)); - } - else - { - // None does, so remove it if we happen to have one. - K_SetPodiumWaypoint(player, NULL); - } - } -} - -/*-------------------------------------------------- - void K_UpdatePodiumWaypoints(player_t *const player) - - See header file for description. ---------------------------------------------------*/ -void K_UpdatePodiumWaypoints(player_t *const player) -{ - if ((player != NULL) && (player->mo != NULL)) - { - if (player->currentwaypoint != NULL) - { - const fixed_t xydist = P_AproxDistance( - player->mo->x - player->currentwaypoint->mobj->x, - player->mo->y - player->currentwaypoint->mobj->y - ); - const fixed_t xyzdist = P_AproxDistance( - xydist, - player->mo->z - player->currentwaypoint->mobj->z - ); - //const fixed_t speed = P_AproxDistance(player->mo->momx, player->mo->momy); - - if (xyzdist <= player->mo->radius + player->currentwaypoint->mobj->radius) - { - // Reached waypoint, go to the next waypoint. - K_SetPodiumWaypoint(player, player->nextwaypoint); - } - } - } -} - -/*-------------------------------------------------- - boolean K_StartCeremony(void) - - See header file for description. ---------------------------------------------------*/ -boolean K_StartCeremony(void) -{ - if (grandprixinfo.gp == false) - { - return false; - } - - INT32 i; - INT32 podiumMapNum = NEXTMAP_INVALID; - - if (grandprixinfo.cup != NULL - && grandprixinfo.cup->cachedlevels[CUPCACHE_PODIUM] != NEXTMAP_INVALID) - { - podiumMapNum = grandprixinfo.cup->cachedlevels[CUPCACHE_PODIUM]; - } - else if (podiummap) - { - podiumMapNum = G_MapNumber(podiummap); - } - - if (podiumMapNum < nummapheaders - && mapheaderinfo[podiumMapNum] - && mapheaderinfo[podiumMapNum]->lumpnum != LUMPERROR) - { - gamemap = podiumMapNum+1; - - encoremode = grandprixinfo.encore; - - if (savedata.lives > 0) - { - K_LoadGrandPrixSaveGame(); - savedata.lives = 0; - } - - // Make sure all of the GAME OVER'd players can spawn - // and be present for the podium - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i]) - { - if (players[i].lives < 1) - players[i].lives = 1; - - if (players[i].bot) - players[i].spectator = false; - } - } - - G_SetGametype(GT_RACE); - G_DoLoadLevelEx(false, GS_CEREMONY); - wipegamestate = GS_CEREMONY; // I don't know what else to do here - - r_splitscreen = 0; // Only one screen for the ceremony - R_ExecuteSetViewSize(); - return true; - } - - return false; -} - -/*-------------------------------------------------- - void K_FinishCeremony(void) - - See header file for description. ---------------------------------------------------*/ -void K_FinishCeremony(void) -{ - if (K_PodiumSequence() == false) - { - return; - } - - podiumData.ranking = true; - - // Play the noise now (via G_UpdateVisited's concluding gamedata save) - prevmap = gamemap-1; - G_UpdateVisited(); -} - -/*-------------------------------------------------- - void K_ResetCeremony(void) - - See header file for description. ---------------------------------------------------*/ -void K_ResetCeremony(void) -{ - SINT8 i; - - memset(&podiumData, 0, sizeof(struct podiumData_s)); - - if (K_PodiumSequence() == false) - { - return; - } - - // Establish rank and grade for this play session. - podiumData.rank = grandprixinfo.rank; - podiumData.grade = K_CalculateGPGrade(&podiumData.rank); - - // Set up music for podium. - { - if (podiumData.rank.position <= 1) - mapmusrng = 2; - else if (podiumData.rank.position == 2 - || podiumData.rank.position == 3) - mapmusrng = 1; - else - mapmusrng = 0; - - while (mapmusrng >= max(1, mapheaderinfo[gamemap-1]->musname_size)) - mapmusrng--; - - mapmusflags |= MUSIC_RELOADRESET; - } - - if (!grandprixinfo.cup) - { - return; - } - - // Write grade, position, and emerald-having-ness for later sessions! - i = (grandprixinfo.masterbots) ? KARTGP_MASTER : grandprixinfo.gamespeed; - - // All results populate downwards in difficulty. This prevents someone - // who's just won on Normal from feeling obligated to complete Easy too. - for (; i >= 0; i--) - { - boolean anymerit = false; - - if ((grandprixinfo.cup->windata[i].best_placement == 0) // First run - || (podiumData.rank.position <= grandprixinfo.cup->windata[i].best_placement)) // Later, better run - { - grandprixinfo.cup->windata[i].best_placement = podiumData.rank.position; - - // The following will not occur in unmodified builds, but pre-emptively sanitise gamedata if someone just changes MAXPLAYERS and calls it a day - if (grandprixinfo.cup->windata[i].best_placement > 0x0F) - grandprixinfo.cup->windata[i].best_placement = 0x0F; - - anymerit = true; - } - - if (podiumData.grade >= grandprixinfo.cup->windata[i].best_grade) - { - grandprixinfo.cup->windata[i].best_grade = podiumData.grade; - anymerit = true; - } - - if (podiumData.rank.specialWon == true) - { - grandprixinfo.cup->windata[i].got_emerald = true; - anymerit = true; - } - - if (anymerit == true) - { - grandprixinfo.cup->windata[i].best_skin.id = podiumData.rank.skin; - grandprixinfo.cup->windata[i].best_skin.unloaded = NULL; - } - } - - // Save before playing the noise - G_SaveGameData(); -} - -/*-------------------------------------------------- - void K_CeremonyTicker(boolean run) - - See header file for description. ---------------------------------------------------*/ -void K_CeremonyTicker(boolean run) -{ - // don't trigger if doing anything besides idling - if (gameaction != ga_nothing || gamestate != GS_CEREMONY) - { - return; - } - - P_TickAltView(&titlemapcam); - - if (titlemapcam.mobj != NULL) - { - camera[0].x = titlemapcam.mobj->x; - camera[0].y = titlemapcam.mobj->y; - camera[0].z = titlemapcam.mobj->z; - camera[0].angle = titlemapcam.mobj->angle; - camera[0].aiming = titlemapcam.mobj->pitch; - camera[0].subsector = titlemapcam.mobj->subsector; - } - - if (podiumData.ranking == false) - { - return; - } - - if (run == true) - { - if (podiumData.fade < 16) - { - podiumData.fade++; - } - else - { - if (podiumData.state < PODIUM_STATES) - { - podiumData.delay++; - - if (podiumData.delay > TICRATE/2) - { - podiumData.state++; - podiumData.delay = 0; - } - } - else if (podiumData.delay == TICRATE) - { - if (!menuactive && M_MenuConfirmPressed(0)) - { - podiumData.delay++; - } - } - else - { - if (++podiumData.delay == 2*TICRATE) - { - if (grandprixinfo.gp == true - && grandprixinfo.cup != NULL - && grandprixinfo.cup->playcredits == true) - { - nextmap = NEXTMAP_CREDITS; - } - else - { - nextmap = NEXTMAP_TITLE; - } - - G_EndGame(); - return; - } - } - } - } -} - -/*-------------------------------------------------- - void K_CeremonyDrawer(void) - - See header file for description. ---------------------------------------------------*/ -void K_CeremonyDrawer(void) -{ - if (podiumData.ranking == true) - { - char gradeChar = '?'; - INT32 x = 64; - INT32 y = 48; - INT32 i; - - switch (podiumData.grade) - { - case GRADE_E: { gradeChar = 'E'; break; } - case GRADE_D: { gradeChar = 'D'; break; } - case GRADE_C: { gradeChar = 'C'; break; } - case GRADE_B: { gradeChar = 'B'; break; } - case GRADE_A: { gradeChar = 'A'; break; } - case GRADE_S: { gradeChar = 'S'; break; } - default: { break; } - } - - V_DrawFadeScreen(0xFF00, podiumData.fade); - - for (i = 0; i <= podiumData.state; i++) - { - switch (i) - { - case 1: - { - V_DrawString(x, y, 0, - va("POS: %d / %d", podiumData.rank.position, RANK_NEUTRAL_POSITION) - ); - break; - } - case 2: - { - V_DrawString(x, y, 0, - va("PTS: %d / %d", podiumData.rank.winPoints, podiumData.rank.totalPoints) - ); - break; - } - case 3: - { - V_DrawString(x, y, 0, - va("LAPS: %d / %d", podiumData.rank.laps, podiumData.rank.totalLaps) - ); - break; - } - case 4: - { - V_DrawString(x, y, 0, - va("CONTINUES: %d", podiumData.rank.continuesUsed) - ); - break; - } - case 5: - { - V_DrawString(x, y, 0, - va("PRISONS: %d / %d", podiumData.rank.prisons, podiumData.rank.totalPrisons) - ); - break; - } - case 6: - { - V_DrawString(x, y, 0, - va("RINGS: %d / %d", podiumData.rank.rings, podiumData.rank.totalRings) - ); - break; - } - case 7: - { - const char *emeraldstr = "???"; - if (gamedata->everseenspecial == true) - { - emeraldstr = - (grandprixinfo.gp == true - && grandprixinfo.cup != NULL - && grandprixinfo.cup->emeraldnum > 0) - ? "EMERALD" - : "PRIZE"; - } - - V_DrawString(x, y, 0, - va("%s: %s", - emeraldstr, - (podiumData.rank.specialWon == true) ? "YES" : "NO") - ); - break; - } - case 8: - { - V_DrawString(x, y + 10, V_YELLOWMAP, - va(" ** FINAL GRADE: %c", gradeChar) - ); - break; - } - default: - { - break; - } - } - - y += 10; - } - } - - // See d_main.c and V_DrawCustomFadeScreen for the hacks that prevents this being here - /*if (timeinmap < 16) - { - // Level fade-in - V_DrawCustomFadeScreen(((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), 31-(timeinmap*2)); - }*/ - - if (podiumData.state == PODIUM_STATES) - { - Y_DrawIntermissionButton(TICRATE - podiumData.delay, podiumData.delay - TICRATE); - } -} diff --git a/src/k_podium.cpp b/src/k_podium.cpp new file mode 100644 index 000000000..daada2ec6 --- /dev/null +++ b/src/k_podium.cpp @@ -0,0 +1,1136 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) by Sally "TehRealSalt" Cochenour +// Copyright (C) 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_podium.c +/// \brief Grand Prix podium cutscene + +#include "k_podium.h" + +#include "doomdef.h" +#include "d_main.h" +#include "d_netcmd.h" +#include "f_finale.h" +#include "g_game.h" +#include "hu_stuff.h" +#include "r_local.h" +#include "s_sound.h" +#include "i_time.h" +#include "i_video.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" +#include "i_system.h" +#include "i_threads.h" +#include "dehacked.h" +#include "g_input.h" +#include "console.h" +#include "m_random.h" +#include "m_misc.h" // moviemode functionality +#include "y_inter.h" +#include "m_cond.h" +#include "p_local.h" +#include "p_saveg.h" +#include "p_setup.h" +#include "st_stuff.h" // hud hiding +#include "fastcmp.h" + +#include "lua_hud.h" +#include "lua_hook.h" + +#include "k_menu.h" +#include "k_grandprix.h" +#include "k_rank.h" + +#include "v_draw.hpp" + +#include "k_hud.h" + +#include + +typedef enum +{ + PODIUM_ST_CONGRATS_SLIDEIN, + PODIUM_ST_CONGRATS_SLIDEUP, + PODIUM_ST_DATA_SLIDEIN, + PODIUM_ST_DATA_PAUSE, + PODIUM_ST_LEVEL_APPEAR, + PODIUM_ST_LEVEL_PAUSE, + PODIUM_ST_GRADE_APPEAR, + PODIUM_ST_GRADE_VOICE, + PODIUM_ST_DONE, + PODIUM_ST_EXIT, +} podium_state_e; + +static struct podiumData_s +{ + boolean ranking; + gpRank_t rank; + gp_rank_e grade; + + podium_state_e state; + INT32 delay; + INT32 transition, transitionTime; + + UINT8 displayLevels; + sfxenum_t gradeVoice; + + cupheader_t *cup; + + char header[64]; + + void Init(void); + void NextLevel(void); + void Tick(void); + void Draw(void); +} g_podiumData; + +void podiumData_s::Init(void) +{ + if (grandprixinfo.cup != nullptr) + { + rank = grandprixinfo.rank; + cup = grandprixinfo.cup; + } + else + { + // construct fake rank for testing podium + // directly from the editor + + size_t cupID = M_RandomRange(0, numkartcupheaders-1); + cup = kartcupheaders; + for (size_t i = 0; i < cupID; i++) + { + if (cup == nullptr) + { + break; + } + + cup = cup->next; + } + + memset(&rank, 0, sizeof(gpRank_t)); + rank.skin = players[consoleplayer].skin; + + rank.numPlayers = 1; //M_RandomRange(1, MAXSPLITSCREENPLAYERS); + rank.totalPlayers = K_GetGPPlayerCount(rank.numPlayers); + + rank.position = M_RandomRange(1, 4); + + // Fake totals + rank.numLevels = 8; + + constexpr INT32 numRaces = 5; + for (INT32 i = 0; i < rank.numPlayers; i++) + { + rank.totalPoints += numRaces * K_CalculateGPRankPoints(i + 1, rank.totalPlayers); + } + rank.totalRings = numRaces * rank.numPlayers * 20; + + UINT32 laps = numRaces * 3; + for (INT32 i = 0; i < rank.numPlayers+1; i++) + { + rank.totalLaps += laps; + } + + // Randomized winnings + INT32 rgs = 0; + INT32 prs = 0; + INT32 tprs = 0; + + rank.winPoints = M_RandomRange(0, rank.totalPoints); + rank.laps = M_RandomRange(0, rank.totalLaps); + rank.specialWon = M_Random() & 1; + + for (INT32 i = 0; i < rank.numLevels; i++) + { + gpRank_level_t *const lvl = &rank.levels[i]; + UINT8 specialWinner = 0; + + lvl->id = M_RandomRange(4, nummapheaders); + + lvl->event = GPEVENT_NONE; + switch (i) + { + case 2: + case 5: + { + lvl->event = GPEVENT_BONUS; + break; + } + case 7: + { + lvl->event = GPEVENT_SPECIAL; + specialWinner = M_RandomRange(0, rank.numPlayers); + break; + } + } + + lvl->time = M_RandomRange(50*TICRATE, 210*TICRATE); + + lvl->totalLapPoints = M_RandomRange(2, 5) * 2; + + lvl->totalPrisons = M_RandomRange(1, 10); + tprs += lvl->totalPrisons; + + UINT16 pprs = 0; + + for (INT32 j = 0; j < rank.numPlayers; j++) + { + gpRank_level_perplayer_t *const dta = &lvl->perPlayer[j]; + + dta->position = M_RandomRange(1, rank.totalPlayers); + + if (lvl->event == GPEVENT_NONE) + { + dta->rings = M_RandomRange(0, 20); + rgs += dta->rings; + + dta->lapPoints = M_RandomRange(0, lvl->totalLapPoints); + } + + if (lvl->event == GPEVENT_BONUS) + { + dta->prisons = M_RandomRange(0, lvl->totalPrisons); + pprs = std::max(pprs, dta->prisons); + } + + if (lvl->event == GPEVENT_SPECIAL) + { + dta->gotSpecialPrize = (j+1 == specialWinner); + dta->grade = GRADE_E; + } + else + { + dta->grade = static_cast(M_RandomRange(static_cast(GRADE_E), static_cast(GRADE_A))); + } + } + + prs += pprs; + } + + rank.rings = rgs; + rank.prisons = prs; + rank.totalPrisons = tprs; + } + + grade = K_CalculateGPGrade(&rank); + + delay = TICRATE/2; + + transition = 0; + transitionTime = TICRATE/2; + + header[0] = '\0'; + + if (rank.position > RANK_NEUTRAL_POSITION) + { + snprintf( + header, sizeof header, + "NO GOOD..." + ); + } + else + { + snprintf( + header, sizeof header, + "CONGRATULATIONS" + ); + } + + header[sizeof header - 1] = '\0'; + + displayLevels = 0; + + gradeVoice = sfx_None; + + // It'd be neat to add all of the grade sounds, + // but not this close to release + if (rank.position > RANK_NEUTRAL_POSITION || grade < GRADE_C) + { + gradeVoice = skins[rank.skin].soundsid[S_sfx[sfx_klose].skinsound]; + } + else + { + gradeVoice = skins[rank.skin].soundsid[S_sfx[sfx_kwin].skinsound]; + } +} + +void podiumData_s::NextLevel(void) +{ + state = PODIUM_ST_LEVEL_APPEAR; + displayLevels++; + delay = TICRATE/7; +} + +void podiumData_s::Tick(void) +{ + if (delay > 0) + { + delay--; + return; + } + + if (transition < FRACUNIT) + { + if (transitionTime <= 0) + { + transition = FRACUNIT; + return; + } + + transition += FRACUNIT / transitionTime; + if (transition > FRACUNIT) + { + transition = FRACUNIT; + } + return; + } + + switch (state) + { + case PODIUM_ST_CONGRATS_SLIDEIN: + { + state = PODIUM_ST_CONGRATS_SLIDEUP; + transition = 0; + transitionTime = TICRATE/2; + delay = TICRATE/2; + break; + } + case PODIUM_ST_CONGRATS_SLIDEUP: + { + state = PODIUM_ST_DATA_SLIDEIN; + transition = 0; + transitionTime = TICRATE/2; + delay = TICRATE/5; + break; + } + case PODIUM_ST_DATA_SLIDEIN: + { + state = PODIUM_ST_DATA_PAUSE; + delay = TICRATE/5; + break; + } + case PODIUM_ST_DATA_PAUSE: + { + NextLevel(); + break; + } + case PODIUM_ST_LEVEL_APPEAR: + { + S_StopSoundByNum(sfx_mbs5b); + S_StartSound(nullptr, (displayLevels >= rank.numLevels) ? sfx_mbs70 : sfx_mbs5b); + + state = PODIUM_ST_LEVEL_PAUSE; + delay = TICRATE/2; + break; + } + case PODIUM_ST_LEVEL_PAUSE: + { + if (displayLevels < rank.numLevels) + { + NextLevel(); + } + else + { + state = PODIUM_ST_GRADE_APPEAR; + transition = 0; + transitionTime = TICRATE/7; + delay = TICRATE/2; + } + break; + } + case PODIUM_ST_GRADE_APPEAR: + { + S_StartSound(nullptr, sfx_rank); + state = PODIUM_ST_GRADE_VOICE; + delay = TICRATE/2; + break; + } + case PODIUM_ST_GRADE_VOICE: + { + if (cv_kartvoices.value) + { + S_StartSound(nullptr, gradeVoice); + } + state = PODIUM_ST_DONE; + delay = 5*TICRATE; + break; + } + case PODIUM_ST_DONE: + { + if (menuactive == false && M_MenuConfirmPressed(0) == true) + { + state = PODIUM_ST_EXIT; + delay = 2*TICRATE; + } + break; + } + case PODIUM_ST_EXIT: + { + if (grandprixinfo.gp == true + && grandprixinfo.cup != nullptr + && grandprixinfo.cup->playcredits == true) + { + nextmap = NEXTMAP_CREDITS; + } + else + { + nextmap = NEXTMAP_TITLE; + } + + G_EndGame(); + return; + } + } +} + +void podiumData_s::Draw(void) +{ + INT32 i; + + const float transition_f = FixedToFloat(transition); + const float transition_i = 1.0 - transition_f; + + srb2::Draw drawer = srb2::Draw(0, 0); + + INT32 fade = 5; + if (state == PODIUM_ST_CONGRATS_SLIDEIN) + { + fade = (5 * transition_f); + } + + V_DrawFadeFill( + 0, 0, + vid.width, vid.height, + V_NOSCALESTART, + 31, fade + ); + + constexpr INT32 header_height = 36; + constexpr INT32 header_offset = -16; + constexpr INT32 header_centered = (BASEVIDHEIGHT * 0.5) - header_height - header_offset; + + switch (state) + { + case PODIUM_ST_CONGRATS_SLIDEIN: + Y_DrawIntermissionHeader( + (BASEVIDWIDTH * transition_i * FRACUNIT), + (header_centered + header_offset) * FRACUNIT, + false, header, 0, false + ); + break; + + case PODIUM_ST_CONGRATS_SLIDEUP: + Y_DrawIntermissionHeader( + 0, + ((header_centered * transition_i) + header_offset) * FRACUNIT, + false, header, 0, false + ); + break; + + default: + Y_DrawIntermissionHeader( + 0, + header_offset * FRACUNIT, + false, header, 0, false + ); + break; + } + + const boolean singlePlayer = (rank.numPlayers == 1); + player_t *bestHuman = &players[consoleplayer]; + + if (singlePlayer == false) + { + UINT8 bestPos = UINT8_MAX; + + for (i = 0; i < rank.numPlayers; i++) + { + // BLEH BLEH, skincolor isn't saved to GP results, so I can't use the same values that get set. ANNOYING. + if (players[i].position < bestPos) + { + bestHuman = &players[i]; + bestPos = players[i].position; + } + } + } + + srb2::Draw drawer_winner = drawer.xy(16, 16); + if (state >= PODIUM_ST_DATA_SLIDEIN) + { + if (state == PODIUM_ST_DATA_SLIDEIN) + { + drawer_winner = drawer_winner.x( transition_i * -BASEVIDWIDTH ); + } + + drawer_winner + .colormap(bestHuman->skin, static_cast(bestHuman->skincolor)) + .patch(faceprefix[bestHuman->skin][FACE_WANTED]); + + if (cup != nullptr) + { + srb2::Draw drawer_trophy = drawer.xy(272, 10); + + if (state == PODIUM_ST_DATA_SLIDEIN) + { + drawer_trophy = drawer_trophy.x( transition_i * BASEVIDWIDTH ); + } + + M_DrawCup( + cup, drawer_trophy.x() * FRACUNIT, drawer_trophy.y() * FRACUNIT, + 0, true, + (rank.position >= 1 && rank.position <= 3) ? rank.position : 0 + ); + } + } + + if (state >= PODIUM_ST_LEVEL_APPEAR) + { + srb2::Draw drawer_line = drawer_winner.xy(80, 28); + + for (i = 0; i <= displayLevels; i++) + { + srb2::Draw drawer_perplayer = drawer_line; + gpRank_level_t *lvl = nullptr; + + if (i > 0) + { + lvl = &rank.levels[i - 1]; + + if (lvl->id > 0) + { + char *title = G_BuildMapTitle(lvl->id); + if (title) + { + drawer_perplayer + .align(srb2::Draw::Align::kRight) + .font(srb2::Draw::Font::kThin) + .text(title); + + Z_Free(title); + } + } + } + + INT32 p; + for (p = 0; p < rank.numPlayers; p++) + { + player_t *const player = &players[displayplayers[p]]; + + if (lvl == nullptr) + { + if (singlePlayer == false) + { + drawer_perplayer + .xy(12, -2) + .colormap(player->skin, static_cast(player->skincolor)) + .patch(faceprefix[rank.skin][FACE_MINIMAP]); + + drawer_perplayer + .xy(26, 0) + .font(srb2::Draw::Font::kConsole) + .text(va("%c", ('A' + p))); + } + } + else + { + gpRank_level_perplayer_t *const dta = &lvl->perPlayer[p]; + + srb2::Draw drawer_rank = drawer_perplayer.xy(2, 0); + + if (lvl->event != GPEVENT_SPECIAL) + { + drawer_rank + .xy(0, -1) + .colormap( static_cast(K_GetGradeColor(dta->grade)) ) + .patch(va("R_CUPRN%c", K_GetGradeChar(dta->grade))); + } + + srb2::Draw drawer_gametype = drawer_rank.xy(18, 0); + + switch (lvl->event) + { + case GPEVENT_BONUS: + { + drawer_gametype + .xy(0, 1) + .patch("K_CAPICO"); + + drawer_gametype + .xy(22, 1) + .align(srb2::Draw::Align::kCenter) + .font(srb2::Draw::Font::kPing) + .text(va("%d/%d", dta->prisons, lvl->totalPrisons)); + break; + } + case GPEVENT_SPECIAL: + { + srb2::Draw drawer_emerald = drawer_gametype; + UINT8 emeraldNum = 0; + + if (cup != nullptr) + { + emeraldNum = cup->emeraldnum; + } + + boolean useWhiteFrame = ((leveltime & 1) || !dta->gotSpecialPrize); + patch_t *emeraldPatch = nullptr; + skincolornum_t emeraldColor = SKINCOLOR_NONE; + + if (emeraldNum == 0) + { + emeraldPatch = static_cast( W_CachePatchName("K_BLNA", PU_CACHE) ); + } + else + { + emeraldColor = static_cast( SKINCOLOR_CHAOSEMERALD1 + ((emeraldNum - 1) % 7) ); + + std::string emeraldName; + if (emeraldNum > 7) + { + emeraldName = (useWhiteFrame ? "K_SUPER2" : "K_SUPER1"); + } + else + { + emeraldName = (useWhiteFrame ? "K_EMERC" : "K_EMERW"); + } + + emeraldPatch = static_cast( W_CachePatchName(emeraldName.c_str(), PU_CACHE) ); + } + + if (dta->gotSpecialPrize) + { + if (emeraldColor != SKINCOLOR_NONE) + { + drawer_emerald = drawer_emerald.colormap( emeraldColor ); + } + } + else + { + drawer_emerald = drawer_emerald.colormap( TC_BLINK, SKINCOLOR_BLACK ); + } + + drawer_emerald + .xy(6 - (emeraldPatch->width * 0.5), 0) + .patch(emeraldPatch); + break; + } + default: + { + drawer_gametype + .xy(0, 1) + .patch("K_SPTLAP"); + + drawer_gametype + .xy(22, 1) + .align(srb2::Draw::Align::kCenter) + .font(srb2::Draw::Font::kPing) + .text(va("%d/%d", dta->lapPoints, lvl->totalLapPoints)); + break; + } + } + + if (singlePlayer) + { + srb2::Draw drawer_rings = drawer_gametype.xy(36, 0); + + if (lvl->event == GPEVENT_NONE) + { + drawer_rings + .xy(0, -1) + .patch("K_SRING1"); + + drawer_rings + .xy(22, 1) + .colormap(TC_RAINBOW, SKINCOLOR_YELLOW) + .align(srb2::Draw::Align::kCenter) + .font(srb2::Draw::Font::kPing) + .text(va("%d", dta->rings)); + } + + srb2::Draw drawer_timer = drawer_rings.xy(36, 0); + + drawer_timer + .xy(0, 0) + .patch("K_STTIMS"); + + drawer_timer + .xy(32, 1) + .align(srb2::Draw::Align::kCenter) + .font(srb2::Draw::Font::kPing) + .text(va( + "%i'%02i\"%02i", + G_TicsToMinutes(lvl->time, true), + G_TicsToSeconds(lvl->time), + G_TicsToCentiseconds(lvl->time) + )); + } + } + + drawer_perplayer = drawer_perplayer.x(56); + } + + drawer_line = drawer_line.y(12); + } + } + + if ((state == PODIUM_ST_GRADE_APPEAR && delay == 0) + || state >= PODIUM_ST_GRADE_VOICE) + { + char grade_letter = K_GetGradeChar( static_cast(grade) ); + + patch_t *grade_img = static_cast( W_CachePatchName(va("R_FINRN%c", grade_letter), PU_CACHE) ); + srb2::Draw grade_drawer = drawer + .xy(BASEVIDWIDTH * 0.5, BASEVIDHEIGHT - 2.0 - (grade_img->height * 0.5)) + .colormap( static_cast(K_GetGradeColor( static_cast(grade) )) ); + + float sc = 1.0; + if (state == PODIUM_ST_GRADE_APPEAR) + { + sc += transition_i * 8.0; + } + + grade_drawer + .xy(-grade_img->width * 0.5 * sc, -grade_img->height * 0.5 * sc) + .scale(sc) + .patch(grade_img); + } + + if (state >= PODIUM_ST_DATA_SLIDEIN) + { + K_DrawKartPositionNumXY( + rank.position, 1, + (drawer_winner.x() + 36) * FRACUNIT, (drawer_winner.y() + 2) * FRACUNIT, + FRACUNIT, drawer_winner.flags(), + leveltime, + ((mapheaderinfo[gamemap - 1]->levelflags & LF_SUBTRACTNUM) == LF_SUBTRACTNUM), + true, + true, + (rank.position > 3) + ); + } +} + +/*-------------------------------------------------- + boolean K_PodiumSequence(void) + + See header file for description. +--------------------------------------------------*/ +boolean K_PodiumSequence(void) +{ + return (gamestate == GS_CEREMONY); +} + +/*-------------------------------------------------- + boolean K_PodiumRanking(void) + + See header file for description. +--------------------------------------------------*/ +boolean K_PodiumRanking(void) +{ + return (gamestate == GS_CEREMONY && g_podiumData.ranking == true); +} + +/*-------------------------------------------------- + boolean K_PodiumGrade(void) + + See header file for description. +--------------------------------------------------*/ +gp_rank_e K_PodiumGrade(void) +{ + if (K_PodiumSequence() == false) + { + return GRADE_E; + } + + return g_podiumData.grade; +} + +/*-------------------------------------------------- + boolean K_PodiumHasEmerald(void) + + See header file for description. +--------------------------------------------------*/ +boolean K_PodiumHasEmerald(void) +{ + if (K_PodiumSequence() == false) + { + return false; + } + + return g_podiumData.rank.specialWon; +} + +/*-------------------------------------------------- + UINT8 K_GetPodiumPosition(player_t *player) + + See header file for description. +--------------------------------------------------*/ +UINT8 K_GetPodiumPosition(player_t *player) +{ + UINT8 position = 1; + INT32 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *other = NULL; + if (playeringame[i] == false) + { + continue; + } + + other = &players[i]; + if (other->bot == false && other->spectator == true) + { + continue; + } + + if (other->score > player->score) + { + // Final score is the important part. + position++; + } + else if (other->score == player->score) + { + if (other->bot == false && player->bot == true) + { + // Bots are never as important as players. + position++; + } + else if (i < player - players) + { + // Port priority is the final tie breaker. + position++; + } + } + } + + return position; +} + +/*-------------------------------------------------- + static void K_SetPodiumWaypoint(player_t *const player, waypoint_t *const waypoint) + + Changes the player's current and next waypoints, for + use during the podium sequence. + + Input Arguments:- + player - The player to update the waypoints of. + waypoint - The new current waypoint. + + Return:- + None +--------------------------------------------------*/ +static void K_SetPodiumWaypoint(player_t *const player, waypoint_t *const waypoint) +{ + // Set the new waypoint. + player->currentwaypoint = waypoint; + + if ((waypoint == NULL) + || (waypoint->nextwaypoints == NULL) + || (waypoint->numnextwaypoints == 0U)) + { + // No waypoint, or no next waypoint. + player->nextwaypoint = NULL; + return; + } + + // Simply use the first available next waypoint. + // No need for split paths in these cutscenes. + player->nextwaypoint = waypoint->nextwaypoints[0]; +} + +/*-------------------------------------------------- + void K_InitializePodiumWaypoint(player_t *const player) + + See header file for description. +--------------------------------------------------*/ +void K_InitializePodiumWaypoint(player_t *const player) +{ + if ((player != NULL) && (player->mo != NULL)) + { + player->position = K_GetPodiumPosition(player); + + if (player->position > 0 && player->position <= MAXPLAYERS) + { + // Initialize our first waypoint to the one that + // matches our position. + K_SetPodiumWaypoint(player, K_GetWaypointFromID(player->position)); + } + else + { + // None does, so remove it if we happen to have one. + K_SetPodiumWaypoint(player, NULL); + } + } +} + +/*-------------------------------------------------- + void K_UpdatePodiumWaypoints(player_t *const player) + + See header file for description. +--------------------------------------------------*/ +void K_UpdatePodiumWaypoints(player_t *const player) +{ + if ((player != NULL) && (player->mo != NULL)) + { + if (player->currentwaypoint != NULL) + { + const fixed_t xydist = P_AproxDistance( + player->mo->x - player->currentwaypoint->mobj->x, + player->mo->y - player->currentwaypoint->mobj->y + ); + const fixed_t xyzdist = P_AproxDistance( + xydist, + player->mo->z - player->currentwaypoint->mobj->z + ); + //const fixed_t speed = P_AproxDistance(player->mo->momx, player->mo->momy); + + if (xyzdist <= player->mo->radius + player->currentwaypoint->mobj->radius) + { + // Reached waypoint, go to the next waypoint. + K_SetPodiumWaypoint(player, player->nextwaypoint); + } + } + } +} + +/*-------------------------------------------------- + boolean K_StartCeremony(void) + + See header file for description. +--------------------------------------------------*/ +boolean K_StartCeremony(void) +{ + if (grandprixinfo.gp == false) + { + return false; + } + + INT32 i; + INT32 podiumMapNum = NEXTMAP_INVALID; + + if (grandprixinfo.cup != NULL + && grandprixinfo.cup->cachedlevels[CUPCACHE_PODIUM] != NEXTMAP_INVALID) + { + podiumMapNum = grandprixinfo.cup->cachedlevels[CUPCACHE_PODIUM]; + } + else if (podiummap) + { + podiumMapNum = G_MapNumber(podiummap); + } + + if (podiumMapNum < nummapheaders + && mapheaderinfo[podiumMapNum] + && mapheaderinfo[podiumMapNum]->lumpnum != LUMPERROR) + { + gamemap = podiumMapNum+1; + + encoremode = grandprixinfo.encore; + + if (savedata.lives > 0) + { + K_LoadGrandPrixSaveGame(); + savedata.lives = 0; + } + + // Make sure all of the GAME OVER'd players can spawn + // and be present for the podium + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + if (players[i].lives < 1) + players[i].lives = 1; + + if (players[i].bot) + players[i].spectator = false; + } + } + + G_SetGametype(GT_RACE); + G_DoLoadLevelEx(false, GS_CEREMONY); + wipegamestate = GS_CEREMONY; // I don't know what else to do here + + r_splitscreen = 0; // Only one screen for the ceremony + R_ExecuteSetViewSize(); + return true; + } + + return false; +} + +/*-------------------------------------------------- + void K_FinishCeremony(void) + + See header file for description. +--------------------------------------------------*/ +void K_FinishCeremony(void) +{ + if (K_PodiumSequence() == false) + { + return; + } + + g_podiumData.ranking = true; + + // Play the noise now (via G_UpdateVisited's concluding gamedata save) + prevmap = gamemap-1; + G_UpdateVisited(); +} + +/*-------------------------------------------------- + void K_ResetCeremony(void) + + See header file for description. +--------------------------------------------------*/ +void K_ResetCeremony(void) +{ + SINT8 i; + + memset(&g_podiumData, 0, sizeof(struct podiumData_s)); + + if (K_PodiumSequence() == false) + { + return; + } + + // Establish rank and grade for this play session. + g_podiumData.Init(); + + // Set up music for podium. + { + if (g_podiumData.rank.position == 1) + { + mapmusrng = 2; + } + else if (g_podiumData.rank.position <= RANK_NEUTRAL_POSITION) + { + mapmusrng = 1; + } + else + { + mapmusrng = 0; + } + + while (mapmusrng >= std::max(1, mapheaderinfo[gamemap-1]->musname_size)) + { + mapmusrng--; + } + + mapmusflags |= MUSIC_RELOADRESET; + } + + if (!grandprixinfo.cup) + { + return; + } + + // Write grade, position, and emerald-having-ness for later sessions! + i = (grandprixinfo.masterbots) ? KARTGP_MASTER : grandprixinfo.gamespeed; + + // All results populate downwards in difficulty. This prevents someone + // who's just won on Normal from feeling obligated to complete Easy too. + for (; i >= 0; i--) + { + boolean anymerit = false; + + if ((grandprixinfo.cup->windata[i].best_placement == 0) // First run + || (g_podiumData.rank.position <= grandprixinfo.cup->windata[i].best_placement)) // Later, better run + { + grandprixinfo.cup->windata[i].best_placement = g_podiumData.rank.position; + + // The following will not occur in unmodified builds, but pre-emptively sanitise gamedata if someone just changes MAXPLAYERS and calls it a day + if (grandprixinfo.cup->windata[i].best_placement > 0x0F) + { + grandprixinfo.cup->windata[i].best_placement = 0x0F; + } + + anymerit = true; + } + + if (g_podiumData.grade >= grandprixinfo.cup->windata[i].best_grade) + { + grandprixinfo.cup->windata[i].best_grade = g_podiumData.grade; + anymerit = true; + } + + if (g_podiumData.rank.specialWon == true) + { + grandprixinfo.cup->windata[i].got_emerald = true; + anymerit = true; + } + + if (anymerit == true) + { + grandprixinfo.cup->windata[i].best_skin.id = g_podiumData.rank.skin; + grandprixinfo.cup->windata[i].best_skin.unloaded = NULL; + } + } + + // Save before playing the noise + G_SaveGameData(); +} + +/*-------------------------------------------------- + void K_CeremonyTicker(boolean run) + + See header file for description. +--------------------------------------------------*/ +void K_CeremonyTicker(boolean run) +{ + // don't trigger if doing anything besides idling + if (gameaction != ga_nothing || gamestate != GS_CEREMONY) + { + return; + } + + P_TickAltView(&titlemapcam); + + if (titlemapcam.mobj != NULL) + { + camera[0].x = titlemapcam.mobj->x; + camera[0].y = titlemapcam.mobj->y; + camera[0].z = titlemapcam.mobj->z; + camera[0].angle = titlemapcam.mobj->angle; + camera[0].aiming = titlemapcam.mobj->pitch; + camera[0].subsector = titlemapcam.mobj->subsector; + } + + if (g_podiumData.ranking == false) + { + return; + } + + if (run == true) + { + g_podiumData.Tick(); + } +} + +/*-------------------------------------------------- + void K_CeremonyDrawer(void) + + See header file for description. +--------------------------------------------------*/ +void K_CeremonyDrawer(void) +{ + if (g_podiumData.ranking == false) + { + // not ready to draw. + return; + } + + g_podiumData.Draw(); + + if (g_podiumData.state >= PODIUM_ST_DONE) + { + Y_DrawIntermissionButton(0, 0); + } +} diff --git a/src/k_podium.h b/src/k_podium.h index df0dbf2e1..2153d547b 100644 --- a/src/k_podium.h +++ b/src/k_podium.h @@ -80,6 +80,7 @@ gp_rank_e K_PodiumGrade(void); Return:- true if the Emerald/Prize was collected during the GP, otherwise false. --------------------------------------------------*/ + boolean K_PodiumHasEmerald(void); diff --git a/src/k_rank.c b/src/k_rank.cpp similarity index 78% rename from src/k_rank.c rename to src/k_rank.cpp index ddb58d9e0..77c3095a7 100644 --- a/src/k_rank.c +++ b/src/k_rank.cpp @@ -139,6 +139,7 @@ static void RankCapsules_LoadTextmap(void) } } +#if 0 /*-------------------------------------------------- static void RankCapsules_LoadThingsLump(UINT8 *data) @@ -175,6 +176,7 @@ static void RankCapsules_LoadThingsLump(UINT8 *data) } } } +#endif /*-------------------------------------------------- static boolean RankCapsules_LoadMapData(const virtres_t *virt) @@ -225,7 +227,11 @@ static boolean RankCapsules_LoadMapData(const virtres_t *virt) } else { +#if 0 RankCapsules_LoadThingsLump(virtthings->data); +#else + CONS_Printf("binary maps SMELL!!!!!\n"); +#endif } return true; @@ -263,15 +269,16 @@ static UINT32 RankCapsules_CountFromMap(const virtres_t *virt) See header file for description. --------------------------------------------------*/ -void K_InitGrandPrixRank(gpRank_t *rankData) +void gpRank_t::Init(void) { UINT8 numHumans = 0; UINT32 laps = 0; INT32 i; - memset(rankData, 0, sizeof(gpRank_t)); + memset(this, 0, sizeof(gpRank_t)); + skin = MAXSKINS; - if (grandprixinfo.cup == NULL) + if (grandprixinfo.cup == nullptr) { return; } @@ -288,20 +295,20 @@ void K_InitGrandPrixRank(gpRank_t *rankData) } // Calculate players - rankData->players = numHumans; - rankData->totalPlayers = K_GetGPPlayerCount(numHumans); + numPlayers = numHumans; + totalPlayers = K_GetGPPlayerCount(numHumans); // Initialize to the neutral value. - rankData->position = RANK_NEUTRAL_POSITION; + position = RANK_NEUTRAL_POSITION; // Calculate total of points // (Should this account for all coop players?) for (i = 0; i < numHumans; i++) { - rankData->totalPoints += grandprixinfo.cup->numlevels * K_CalculateGPRankPoints(i + 1, rankData->totalPlayers); + totalPoints += grandprixinfo.cup->numlevels * K_CalculateGPRankPoints(i + 1, totalPlayers); } - rankData->totalRings = grandprixinfo.cup->numlevels * numHumans * 20; + totalRings = grandprixinfo.cup->numlevels * numHumans * 20; for (i = 0; i < grandprixinfo.cup->numlevels; i++) { @@ -315,7 +322,7 @@ void K_InitGrandPrixRank(gpRank_t *rankData) // +1, since 1st place laps are worth 2 pts. for (i = 0; i < numHumans+1; i++) { - rankData->totalLaps += laps; + totalLaps += laps; } // Search through all of the cup's bonus levels @@ -339,42 +346,97 @@ void K_InitGrandPrixRank(gpRank_t *rankData) continue; } - rankData->totalPrisons += RankCapsules_CountFromMap(virt); + totalPrisons += RankCapsules_CountFromMap(virt); vres_Free(virt); } } } +void K_InitGrandPrixRank(gpRank_t *rankData) +{ + rankData->Init(); +} + /*-------------------------------------------------- - void K_UpdateGPRank(void) + void K_UpdateGPRank(gpRank_t *rankData) See header file for description. --------------------------------------------------*/ -void K_UpdateGPRank(void) +void gpRank_t::Update(void) { - if (grandprixinfo.gp != true) - return; + gpRank_level_t *const lvl = &levels[numLevels]; + + prisons += numtargets; + + position = MAXPLAYERS; + skin = MAXSKINS; + + lvl->id = gamemap; + + if (grandprixinfo.gp == true) + { + lvl->event = grandprixinfo.eventmode; + } + else + { + lvl->event = (gametype != GT_RACE) ? GPEVENT_BONUS : GPEVENT_NONE; + } + + lvl->time = UINT32_MAX; + lvl->totalLapPoints = K_RaceLapCount(gamemap - 1) * 2; + lvl->totalPrisons = maptargets; UINT8 i; - - grandprixinfo.rank.prisons += numtargets; - grandprixinfo.rank.position = MAXPLAYERS; - grandprixinfo.rank.skin = MAXSKINS; - for (i = 0; i < MAXPLAYERS; i++) { - if (!playeringame[i] - || players[i].spectator == true - || players[i].bot == true) + if (playeringame[i] == false + || players[i].spectator == true + || players[i].bot == true) + { continue; + } - UINT8 podiumposition = K_GetPodiumPosition(&players[i]); - if (podiumposition >= grandprixinfo.rank.position) // port priority - continue; + player_t *const player = &players[i]; - grandprixinfo.rank.position = podiumposition; - grandprixinfo.rank.skin = players[i].skin; + UINT8 podiumPosition = K_GetPodiumPosition(player); + if (podiumPosition < position) // port priority + { + position = podiumPosition; + skin = player->skin; + } } + + for (i = 0; i < numPlayers; i++) + { + if (playeringame[displayplayers[i]] == false + || players[displayplayers[i]].spectator == true + || players[displayplayers[i]].bot == true) + { + continue; + } + + const player_t *player = &players[displayplayers[i]]; // TODO: needs looked at for online GP + gpRank_level_perplayer_t *const dta = &lvl->perPlayer[i]; + + if (player->realtime < lvl->time) + { + lvl->time = player->realtime; + } + + dta->position = player->tally.position; + dta->rings = player->tally.rings; + dta->lapPoints = player->tally.laps; + dta->prisons = player->tally.prisons; + dta->gotSpecialPrize = !!!(player->pflags & PF_NOCONTEST); + dta->grade = static_cast(player->tally.rank); + } + + numLevels++; +} + +void K_UpdateGPRank(gpRank_t *rankData) +{ + rankData->Update(); } /*-------------------------------------------------- @@ -389,7 +451,7 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData) if (cv_debugrank.value >= 2) { - return GRADE_E + (cv_debugrank.value - 2); + return static_cast(GRADE_E + (cv_debugrank.value - 2)); } } @@ -400,7 +462,7 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData) 17*FRACUNIT/20 // A: 85% or higher }; - gp_rank_e retGrade = GRADE_E; + INT32 retGrade = GRADE_E; const INT32 positionWeight = 150; const INT32 pointsWeight = 100; @@ -459,7 +521,7 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData) retGrade++; } - return retGrade; + return static_cast(retGrade); } /*-------------------------------------------------- @@ -489,3 +551,32 @@ UINT16 K_GetGradeColor(gp_rank_e grade) return SKINCOLOR_NONE; } + +/*-------------------------------------------------- + char K_GetGradeChar(gp_rank_e grade) + + See header file for description. +--------------------------------------------------*/ +char K_GetGradeChar(gp_rank_e grade) +{ + switch (grade) + { + case GRADE_E: + return 'E'; + case GRADE_D: + return 'D'; + case GRADE_C: + return 'C'; + case GRADE_B: + return 'B'; + case GRADE_A: + return 'A'; + case GRADE_S: + return 'S'; + default: + break; + } + + return '?'; +} + diff --git a/src/k_rank.h b/src/k_rank.h index 6d4887218..c50e49a82 100644 --- a/src/k_rank.h +++ b/src/k_rank.h @@ -16,14 +16,30 @@ #include "doomdef.h" #include "doomstat.h" -#ifdef __cplusplus -extern "C" { -#endif - // Please also see P_ArchiveMisc +struct gpRank_level_perplayer_t +{ + UINT8 position; + UINT8 rings; + UINT16 lapPoints; + UINT16 prisons; + boolean gotSpecialPrize; + gp_rank_e grade; +}; + +struct gpRank_level_t +{ + UINT16 id; + INT32 event; + UINT32 time; + UINT16 totalLapPoints; + UINT16 totalPrisons; + gpRank_level_perplayer_t perPlayer[MAXSPLITSCREENPLAYERS]; +}; + struct gpRank_t { - UINT8 players; + UINT8 numPlayers; UINT8 totalPlayers; UINT8 position; @@ -44,8 +60,20 @@ struct gpRank_t UINT32 totalRings; boolean specialWon; + + UINT8 numLevels; + gpRank_level_t levels[8]; + +#ifdef __cplusplus + void Init(void); + void Update(void); +#endif }; +#ifdef __cplusplus +extern "C" { +#endif + // gp_rank_e was once defined here, but moved to doomstat.h to prevent circular dependency // 3rd place is neutral, anything below is a penalty @@ -68,18 +96,13 @@ void K_InitGrandPrixRank(gpRank_t *rankData); /*-------------------------------------------------- - void K_UpdateGPRank(void) + void K_UpdateGPRank(gpRank_t *rankData) Updates the best ranking across all human players. - - Input Arguments:- - N/A - - Return:- - N/A --------------------------------------------------*/ -void K_UpdateGPRank(void); + +void K_UpdateGPRank(gpRank_t *rankData); /*-------------------------------------------------- @@ -109,9 +132,25 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData); Return:- skincolor ID representing the achieved grade. --------------------------------------------------*/ + UINT16 K_GetGradeColor(gp_rank_e grade); +/*-------------------------------------------------- + char K_GetGradeChar(gp_rank_e grade) + + Maps grades to a letter for strings. + + Input Arguments:- + grade - gp_rank_e representing an achieved ranking. + + Return:- + ASCII character for the grade. +--------------------------------------------------*/ + +char K_GetGradeChar(gp_rank_e grade); + + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_tally.cpp b/src/k_tally.cpp index 6201a2aa4..af1413141 100644 --- a/src/k_tally.cpp +++ b/src/k_tally.cpp @@ -1337,32 +1337,9 @@ void level_tally_t::Draw(void) || state == TALLY_ST_GRADE_VOICE || state == TALLY_ST_DONE) { - const char *grade_letter = "X"; - switch (rank) - { - case GRADE_E: - grade_letter = "E"; - break; - case GRADE_D: - grade_letter = "D"; - break; - case GRADE_C: - grade_letter = "C"; - break; - case GRADE_B: - grade_letter = "B"; - break; - case GRADE_A: - grade_letter = "A"; - break; - case GRADE_S: - grade_letter = "S"; - break; - default: - break; - } + char grade_letter = K_GetGradeChar( static_cast(rank) ); - patch_t *grade_img = static_cast( W_CachePatchName(va("R_FINR%s%s", (r_splitscreen ? "S" : "N"), grade_letter), PU_CACHE) ); + patch_t *grade_img = static_cast( W_CachePatchName(va("R_FINR%c%c", (r_splitscreen ? 'S' : 'N'), grade_letter), PU_CACHE) ); srb2::Draw grade_drawer = drawer .xy(v_width * 0.5, v_height - (2.0 * frac) - (grade_img->height * 0.5)) .colormap( static_cast(K_GetGradeColor( static_cast(rank) )) ); diff --git a/src/p_inter.c b/src/p_inter.c index 8839d247d..41bd59149 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1087,7 +1087,17 @@ static void P_AddBrokenPrison(mobj_t *target, mobj_t *inflictor, mobj_t *source) S_StartSound(NULL, sfx_s221); if (timelimitintics) { - extratimeintics += 10*TICRATE; + UINT16 bonustime = 10*TICRATE; + + if (grandprixinfo.gp) + { + if (grandprixinfo.masterbots) + bonustime = 8*TICRATE; + else if (grandprixinfo.gamespeed == KARTSPEED_EASY) + bonustime = 15*TICRATE; + } + + extratimeintics += bonustime; secretextratime = TICRATE/2; } @@ -2495,6 +2505,11 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, P_SetPlayerMobjState(player->mo, player->mo->info->deathstate); + if (player->sliptideZipIndicator && !P_MobjWasRemoved(player->sliptideZipIndicator)) + P_RemoveMobj(player->sliptideZipIndicator); + if (player->stumbleIndicator && !P_MobjWasRemoved(player->stumbleIndicator)) + P_RemoveMobj(player->stumbleIndicator); + if (type == DMG_TIMEOVER) { if (gametyperules & GTR_CIRCUIT) diff --git a/src/p_saveg.c b/src/p_saveg.c index 064d8fdc4..a1c3d1f64 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -5747,7 +5747,7 @@ static inline void P_ArchiveMisc(savebuffer_t *save) // Rank information { - WRITEUINT8(save->p, grandprixinfo.rank.players); + WRITEUINT8(save->p, grandprixinfo.rank.numPlayers); WRITEUINT8(save->p, grandprixinfo.rank.totalPlayers); WRITEUINT8(save->p, grandprixinfo.rank.position); @@ -5768,6 +5768,19 @@ static inline void P_ArchiveMisc(savebuffer_t *save) WRITEUINT32(save->p, grandprixinfo.rank.totalRings); WRITEUINT8(save->p, (UINT8)grandprixinfo.rank.specialWon); + + /* + WRITEUINT8(save->p, grandprixinfo.rank.numLevels); + + for (i = 0; i < grandprixinfo.rank.stages; i++) + { + UINT8 j; + for (j = 0; j < grandprixinfo.rank.numPlayers; j++) + { + + } + } + */ } // Marathon information @@ -5910,7 +5923,7 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) // Rank information { - grandprixinfo.rank.players = READUINT8(save->p); + grandprixinfo.rank.numPlayers = READUINT8(save->p); grandprixinfo.rank.totalPlayers = READUINT8(save->p); grandprixinfo.rank.position = READUINT8(save->p); diff --git a/src/p_user.c b/src/p_user.c index ac159ba4d..edcf1e382 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2279,6 +2279,17 @@ static void P_UpdatePlayerAngle(player_t *player) // With a full slam on the analog stick, how far could we steer in either direction? INT16 steeringRight = K_UpdateSteeringValue(player->steering, KART_FULLTURN); INT16 steeringLeft = K_UpdateSteeringValue(player->steering, -KART_FULLTURN); + + // When entering/leaving drifts, allow all legal turns with no easing. + // This is the hardest case for the turn solver, because your handling properties on + // client side are very different than your handling properties on server side—at least, + // until your drift status makes the full round-trip and is reflected in your gamestate. + if (player->drift && abs(player->drift) < 5) + { + steeringRight = KART_FULLTURN; + steeringLeft = -KART_FULLTURN; + } + angle_t maxTurnRight = K_GetKartTurnValue(player, steeringRight) << TICCMD_REDUCE; angle_t maxTurnLeft = K_GetKartTurnValue(player, steeringLeft) << TICCMD_REDUCE; diff --git a/src/typedef.h b/src/typedef.h index d4c2d5e40..248acc03b 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -229,6 +229,8 @@ TYPEDEF (t_floor_t); TYPEDEF (waypoint_t); // k_rank.h +TYPEDEF (gpRank_level_perplayer_t); +TYPEDEF (gpRank_level_t); TYPEDEF (gpRank_t); // k_tally.h diff --git a/src/y_inter.c b/src/y_inter.c index 61b9d5013..bc7264a57 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -283,7 +283,10 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) { // Okay, player scores have been set now - we can calculate GP-relevant material. { - K_UpdateGPRank(); + if (grandprixinfo.gp == true) + { + K_UpdateGPRank(&grandprixinfo.rank); + } // See also G_GetNextMap, M_DrawPause data.showrank = false;