diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 81599a21f..8c347e19b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -151,6 +151,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 k_powerup.cpp k_hitlag.c k_dialogue.cpp + k_tally.cpp music.cpp music_manager.cpp ) diff --git a/src/d_player.h b/src/d_player.h index 0abeb1951..5e57cfc92 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -32,6 +32,9 @@ // the player struct stores a waypoint for racing #include "k_waypoint.h" +// struct to store tally screen data on +#include "k_tally.h" + #ifdef __cplusplus extern "C" { #endif @@ -287,7 +290,6 @@ typedef enum khud_taunthorns, // Used to specifically stop taunt horn spam // Battle - khud_cardanimation, // Used to determine the position of some full-screen Battle Mode graphics khud_yougotem, // "You Got Em" gfx when hitting someone as a karma player via a method that gets you back in the game instantly // Tricks @@ -827,6 +829,8 @@ struct player_t sonicloopvars_t loop; roundconditions_t roundconditions; powerupvars_t powerup; + + level_tally_t tally; }; // WARNING FOR ANYONE ABOUT TO ADD SOMETHING TO THE PLAYER STRUCT, G_PlayerReborn WANTS YOU TO SUFFER diff --git a/src/deh_tables.c b/src/deh_tables.c index f2f819901..0eed55fcf 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -6281,7 +6281,6 @@ const char *const KARTHUD_LIST[] = { "VOICES", "TAUNTVOICES", - "CARDANIMATION", "YOUGOTEM", }; diff --git a/src/doomstat.h b/src/doomstat.h index fbbd72e3a..7b519819a 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -743,9 +743,6 @@ extern tic_t starttime; extern const tic_t bulbtime; extern UINT8 numbulbs; -extern tic_t raceexittime; -#define MUSICCOUNTDOWNMAX (raceexittime - (TICRATE/2)) - extern INT32 hyudorotime; extern INT32 stealtime; extern INT32 sneakertime; diff --git a/src/g_game.c b/src/g_game.c index 69bbd2370..66a6bf0a2 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -250,8 +250,6 @@ tic_t starttime = 3; const tic_t bulbtime = TICRATE/2; UINT8 numbulbs = 1; -tic_t raceexittime = 7*TICRATE + (TICRATE/2); - INT32 hyudorotime = 7*TICRATE; INT32 stealtime = TICRATE/2; INT32 sneakertime = TICRATE + (TICRATE/3); @@ -1933,7 +1931,7 @@ void G_Ticker(boolean run) { Music_Play("intermission"); } - else if (musiccountdown == (MUSICCOUNTDOWNMAX - (3*TICRATE)/2)) + else if (musiccountdown == MUSIC_COUNTDOWN_MAX - TALLY_TIME) { P_EndingMusic(); } @@ -1968,6 +1966,9 @@ static inline void G_PlayerFinishLevel(INT32 player) memset(&p->respawn, 0, sizeof (p->respawn)); p->spectatorReentry = 0; // Clean up any pending re-entry forbiddings + + // Init player tally if we didn't get one set up in advance. + K_InitPlayerTally(p); } // @@ -2000,7 +2001,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT32 cheatchecknum; INT32 exiting; INT32 khudfinish; - INT32 khudcardanimation; INT16 totalring; UINT8 laps; UINT8 latestlap; @@ -2049,6 +2049,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) roundconditions_t roundconditions; boolean saveroundconditions; + level_tally_t tally; + boolean tallyactive; + // This needs to be first, to permit it to wipe extra information jointime = players[player].jointime; if (jointime <= 1) @@ -2148,10 +2151,10 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) roundscore = 0; exiting = 0; khudfinish = 0; - khudcardanimation = 0; cheatchecknum = 0; saveroundconditions = false; + tallyactive = false; } else { @@ -2186,16 +2189,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) roundscore = players[player].roundscore; exiting = players[player].exiting; - if (exiting > 0) - { - khudfinish = players[player].karthud[khud_finish]; - khudcardanimation = players[player].karthud[khud_cardanimation]; - } - else - { - khudfinish = 0; - khudcardanimation = 0; - } + khudfinish = (exiting > 0) ? players[player].karthud[khud_finish] : 0; cheatchecknum = players[player].cheatchecknum; @@ -2203,6 +2197,12 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) memcpy(&roundconditions, &players[player].roundconditions, sizeof (roundconditions)); saveroundconditions = true; + + tallyactive = players[player].tally.active; + if (tallyactive) + { + tally = players[player].tally; + } } spectatorReentry = (betweenmaps ? 0 : players[player].spectatorReentry); @@ -2281,7 +2281,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->cheatchecknum = cheatchecknum; p->exiting = exiting; p->karthud[khud_finish] = khudfinish; - p->karthud[khud_cardanimation] = khudcardanimation; p->laps = laps; p->latestlap = latestlap; @@ -2324,6 +2323,11 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) if (saveroundconditions) memcpy(&p->roundconditions, &roundconditions, sizeof (p->roundconditions)); + if (tallyactive == true) + { + p->tally = tally; + } + // See above comment about refcount consistency. p->ringShooter = ringShooter; p->hoverhyudoro = hoverhyudoro; @@ -2863,24 +2867,11 @@ void G_BeginLevelExit(void) g_exit.losing = true; g_exit.retry = false; - if (grandprixinfo.gp == true) - { - UINT8 i; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator) - { - K_PlayerFinishGrandPrix(&players[i]); - } - } - } - if (!G_GametypeUsesLives() || skipstats != 0) { g_exit.losing = false; // never force a retry } - else if (specialstageinfo.valid == true || (gametyperules & GTR_BOSS)) + else { UINT8 i; @@ -2896,10 +2887,6 @@ void G_BeginLevelExit(void) } } } - else if (grandprixinfo.gp == true && grandprixinfo.eventmode == GPEVENT_NONE) - { - g_exit.losing = (grandprixinfo.wonround != true); - } if (g_exit.losing) { @@ -2920,13 +2907,11 @@ void G_BeginLevelExit(void) } } - if (g_exit.losing && specialstageinfo.valid) + exitcountdown = TICRATE; + + if (grandprixinfo.gp == true) { - exitcountdown = TICRATE; - } - else - { - exitcountdown = raceexittime+1; + grandprixinfo.wonround = !g_exit.losing; } if (g_exit.losing) @@ -2958,7 +2943,7 @@ void G_FinishExitLevel(void) { // We were in a Special Stage. // We can still progress to the podium when we game over here. - const boolean special = grandprixinfo.gp == true && grandprixinfo.eventmode == GPEVENT_SPECIAL; + const boolean special = grandprixinfo.gp == true && grandprixinfo.cup != NULL && grandprixinfo.eventmode == GPEVENT_SPECIAL; if (!netgame && !special) { @@ -4006,7 +3991,7 @@ void G_GetNextMap(void) // static void G_DoCompleted(void) { - INT32 i, j = 0; + INT32 i; if (modeattacking && pausedelay) pausedelay = 0; @@ -4053,30 +4038,62 @@ static void G_DoCompleted(void) for (i = 0; i < MAXPLAYERS; i++) { - if (playeringame[i]) + if (playeringame[i] == false) { - // Exitlevel shouldn't get you the points - if (!players[i].exiting && !(players[i].pflags & PF_NOCONTEST)) - { - clientPowerAdd[i] = 0; - - if (players[i].bot) - { - K_FakeBotResults(&players[i]); - } - else - { - players[i].pflags |= PF_NOCONTEST; - - if (P_IsLocalPlayer(&players[i])) - { - j++; - } - } - } - - G_PlayerFinishLevel(i); // take away cards and stuff + continue; } + + player_t *const player = &players[i]; + + // Exitlevel shouldn't get you the points + if (player->exiting == false && (player->pflags & PF_NOCONTEST) == 0) + { + clientPowerAdd[i] = 0; + + if (player->bot == true) + { + K_FakeBotResults(player); + } + else + { + player->pflags |= PF_NOCONTEST; + } + } + + if (grandprixinfo.gp == true && grandprixinfo.wonround == true && player->exiting == true) + { + if (player->bot == true) + { + // Bots are going to get harder... :) + K_IncreaseBotDifficulty(player); + } + else if (K_IsPlayerLosing(player) == false) + { + // Increase your total rings + INT32 ringtotal = player->hudrings; + if (ringtotal > 0) + { + if (ringtotal > 20) + ringtotal = 20; + player->totalring += ringtotal; + grandprixinfo.rank.rings += ringtotal; + } + + if (grandprixinfo.eventmode == GPEVENT_NONE) + { + grandprixinfo.rank.winPoints += K_CalculateGPRankPoints(player->position, grandprixinfo.rank.totalPlayers); + grandprixinfo.rank.laps += player->lapPoints; + } + else if (grandprixinfo.eventmode == GPEVENT_SPECIAL) + { + grandprixinfo.rank.specialWon = true; + } + + P_GivePlayerLives(player, player->xtralife); + } + } + + G_PlayerFinishLevel(i); // take away cards and stuff } if (automapactive) diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 51f694c43..23e5758fd 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -789,17 +789,6 @@ void K_PlayerLoseLife(player_t *player) player->lives--; player->pflags |= PF_LOSTLIFE; - -#if 0 - if (player->lives <= 0) - { - if (P_IsLocalPlayer(player)) - { - S_StopMusic(); - S_ChangeMusicInternal("gmover", false); - } - } -#endif } /*-------------------------------------------------- @@ -842,63 +831,6 @@ boolean K_CanChangeRules(boolean allowdemos) return true; } -/*-------------------------------------------------- - void K_PlayerFinishGrandPrix(player_t *player); - - See header file for description. ---------------------------------------------------*/ -void K_PlayerFinishGrandPrix(player_t *player) -{ - if (grandprixinfo.wonround == true) - { - // This was already completed. - return; - } - - if (player->exiting == false) - { - // You did not finish - return; - } - - if (player->bot) - { - // Bots are going to get harder... :) - K_IncreaseBotDifficulty(player); - return; - } - - if (K_IsPlayerLosing(player)) - { - return; - } - - // YOU WIN - grandprixinfo.wonround = true; - - // Increase your total rings - INT32 ringtotal = player->hudrings; - if (ringtotal > 0) - { - if (ringtotal > 20) - ringtotal = 20; - player->totalring += ringtotal; - grandprixinfo.rank.rings += ringtotal; - } - - if (grandprixinfo.eventmode == GPEVENT_NONE) - { - grandprixinfo.rank.winPoints += K_CalculateGPRankPoints(player->position, grandprixinfo.rank.totalPlayers); - grandprixinfo.rank.laps += player->lapPoints; - } - else if (grandprixinfo.eventmode == GPEVENT_SPECIAL) - { - grandprixinfo.rank.specialWon = true; - } - - P_GivePlayerLives(player, player->xtralife); -} - /*-------------------------------------------------- boolean K_BotDefaultSpectator(player_t *player); diff --git a/src/k_grandprix.h b/src/k_grandprix.h index 8839eff16..05887dac5 100644 --- a/src/k_grandprix.h +++ b/src/k_grandprix.h @@ -191,21 +191,6 @@ void K_PlayerLoseLife(player_t *player); boolean K_CanChangeRules(boolean allowdemos); -/*-------------------------------------------------- - void K_PlayerFinishGrandPrix(player_t *player); - - Increases rank and bot difficulties, wins the round. - - Input Arguments:- - player - Player to do this for. - - Return:- - None ---------------------------------------------------*/ - -void K_PlayerFinishGrandPrix(player_t *player); - - /*-------------------------------------------------- boolean K_BotDefaultSpectator(void) diff --git a/src/k_hud.c b/src/k_hud.c index e6f88d224..146cc99d5 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -2023,6 +2023,11 @@ static void K_DrawKartPositionNum(INT32 num) INT32 fflags = 0; UINT8 *color = NULL; + if (stplyr->lives <= 0 && stplyr->playerstate == PST_DEAD) + { + return; + } + if (leveltime < (starttime + NUMTRANSMAPS)) { trans = (starttime + NUMTRANSMAPS) - leveltime; @@ -2033,7 +2038,7 @@ static void K_DrawKartPositionNum(INT32 num) return; } - if (stplyr->positiondelay || stplyr->exiting) + if (stplyr->positiondelay > 0 || K_PlayerTallyActive(stplyr) == true) { const UINT8 delay = (stplyr->exiting) ? POS_DELAY_TIME : stplyr->positiondelay; const fixed_t add = (scale * 3) >> ((r_splitscreen == 1) ? 1 : 2); @@ -2128,7 +2133,7 @@ static void K_DrawKartPositionNum(INT32 num) { K_DrawKartPositionNumPatch( 0, color, - fx, fy, scale, V_SLIDEIN|V_SPLITSCREEN|fflags + fx, fy, scale, V_SPLITSCREEN|fflags ); return; @@ -2143,7 +2148,7 @@ static void K_DrawKartPositionNum(INT32 num) fx = K_DrawKartPositionNumPatch( (num % 10), color, - fx, fy, scale, V_SLIDEIN|V_SPLITSCREEN|fflags + fx, fy, scale, V_SPLITSCREEN|fflags ); num /= 10; } @@ -4606,6 +4611,11 @@ static void K_drawKartStartCountdown(void) { INT32 pnum = 0; + if (stplyr->lives <= 0 && stplyr->playerstate == PST_DEAD) + { + return; + } + if (stplyr->karthud[khud_fault] != 0) { K_drawKartFinish(false); @@ -4654,115 +4664,6 @@ static void K_drawKartStartCountdown(void) } } -static void K_drawBattleFullscreen(void) -{ - INT32 x = BASEVIDWIDTH/2; - INT32 y = -64+(stplyr->karthud[khud_cardanimation]); // card animation goes from 0 to 164, 164 is the middle of the screen - INT32 splitflags = V_SNAPTOTOP; // I don't feel like properly supporting non-green resolutions, so you can have a misuse of SNAPTO instead - fixed_t scale = FRACUNIT; - boolean drawcomebacktimer = true; // lazy hack because it's cleaner in the long run. - - if (!LUA_HudEnabled(hud_battlecomebacktimer)) - drawcomebacktimer = false; - - if (r_splitscreen) - { - if ((r_splitscreen == 1 && stplyr == &players[displayplayers[1]]) - || (r_splitscreen > 1 && (stplyr == &players[displayplayers[2]] - || (stplyr == &players[displayplayers[3]] && r_splitscreen > 2)))) - { - y = 232-(stplyr->karthud[khud_cardanimation]/2); - splitflags = V_SNAPTOBOTTOM; - } - else - y = -32+(stplyr->karthud[khud_cardanimation]/2); - - if (r_splitscreen > 1) - { - scale /= 2; - - if (stplyr == &players[displayplayers[1]] - || (stplyr == &players[displayplayers[3]] && r_splitscreen > 2)) - x = 3*BASEVIDWIDTH/4; - else - x = BASEVIDWIDTH/4; - } - else - { - if (stplyr->exiting) - { - if (stplyr == &players[displayplayers[1]]) - x = BASEVIDWIDTH-96; - else - x = 96; - } - else - scale /= 2; - } - } - - if (stplyr->exiting) - { - if (stplyr == &players[displayplayers[0]]) - V_DrawFadeScreen(0xFF00, 16); - if (exitcountdown <= (11*TICRATE)/2 && !stplyr->spectator) - { - patch_t *p = kp_battlecool; - - if (K_IsPlayerLosing(stplyr)) - p = kp_battlelose; - else if (stplyr->position == 1 && (!battleprisons || numtargets >= maptargets)) - p = kp_battlewin; - - V_DrawFixedPatch(x<karmadelay && !stplyr->spectator && drawcomebacktimer) - { - UINT16 t = stplyr->karmadelay/(10*TICRATE); - INT32 txoff, adjust = (r_splitscreen > 1) ? 4 : 6; // normal string is 8, kart string is 12, half of that for ease - INT32 ty = (BASEVIDHEIGHT/2)+66; - - txoff = adjust; - - while (t) - { - txoff += adjust; - t /= 10; - } - - if (r_splitscreen) - { - if (r_splitscreen > 1) - ty = (BASEVIDHEIGHT/4)+33; - if ((r_splitscreen == 1 && stplyr == &players[displayplayers[1]]) - || (stplyr == &players[displayplayers[2]] && r_splitscreen > 1) - || (stplyr == &players[displayplayers[3]] && r_splitscreen > 2)) - ty += (BASEVIDHEIGHT/2); - } - else - V_DrawFadeScreen(0xFF00, 16); - - if (!comebackshowninfo) - V_DrawFixedPatch(x< 1) - V_DrawString(x-txoff, ty, 0, va("%d", stplyr->karmadelay/TICRATE)); - else - { - V_DrawFixedPatch(x<karmadelay/TICRATE)); - } - } - - // FREE PLAY? - K_drawKartFreePlay(); -} - static void K_drawKartFirstPerson(void) { static INT32 pnum[4], turn[4], drift[4]; @@ -5520,7 +5421,6 @@ static void K_DrawGPRankDebugger(void) void K_drawKartHUD(void) { boolean islonesome = false; - boolean battlefullscreen = false; boolean freecam = demo.freecam; //disable some hud elements w/ freecam UINT8 viewnum = R_GetViewNumber(); @@ -5539,13 +5439,7 @@ void K_drawKartHUD(void) return; } - battlefullscreen = (!(gametyperules & GTR_CIRCUIT) - && (stplyr->exiting - || (((gametyperules & GTR_KARMA) && (stplyr->karmadelay > 0)) - && !(stplyr->pflags & PF_ELIMINATED) - && stplyr->playerstate == PST_LIVE))); - - if (!demo.title && (!battlefullscreen || r_splitscreen)) + if (!demo.title) { // Draw the CHECK indicator before the other items, so it's overlapped by everything else if (LUA_HudEnabled(hud_check)) // delete lua when? @@ -5569,13 +5463,6 @@ void K_drawKartHUD(void) K_drawKartMinimap(); } - if (battlefullscreen && !freecam) - { - if (LUA_HudEnabled(hud_battlefullscreen)) - K_drawBattleFullscreen(); - return; - } - // Draw the item window if (LUA_HudEnabled(hud_item) && !freecam) { @@ -5638,6 +5525,11 @@ void K_drawKartHUD(void) { boolean gametypeinfoshown = false; + if (K_PlayerTallyActive(stplyr) == true) + { + K_DrawPlayerTally(); + } + if (LUA_HudEnabled(hud_position)) { if (bossinfo.valid) diff --git a/src/k_kart.c b/src/k_kart.c index 7c2f4ca7a..10269b818 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -47,6 +47,7 @@ #include "k_podium.h" #include "k_powerup.h" #include "k_hitlag.h" +#include "k_tally.h" #include "music.h" // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: @@ -7277,7 +7278,7 @@ static void K_UpdateEngineSounds(player_t *player) INT32 targetsnd = 0; INT32 i; - if (leveltime < 8 || player->spectator || gamestate != GS_LEVEL) + if (leveltime < 8 || player->spectator || gamestate != GS_LEVEL || player->exiting) { // Silence the engines, and reset sound number while we're at it. player->karthud[khud_enginesnd] = 0; @@ -7419,6 +7420,11 @@ static void K_UpdateInvincibilitySounds(player_t *player) // It's just a convenient name for things that don't stop during hitlag. void K_KartPlayerHUDUpdate(player_t *player) { + if (K_PlayerTallyActive(player) == true) + { + K_TickPlayerTally(player); + } + if (player->karthud[khud_lapanimation]) player->karthud[khud_lapanimation]--; @@ -7536,29 +7542,6 @@ void K_KartPlayerHUDUpdate(player_t *player) } else player->karthud[khud_finish] = 0; - - if ((gametyperules & GTR_BUMPERS) && (player->exiting || player->karmadelay)) - { - if (player->exiting) - { - if (exitcountdown < (11*TICRATE)/2) - player->karthud[khud_cardanimation] += ((164-player->karthud[khud_cardanimation])/8)+1; - } - else - { - if (player->karmadelay < 6*TICRATE) - player->karthud[khud_cardanimation] -= ((164-player->karthud[khud_cardanimation])/8)+1; - else if (player->karmadelay < 9*TICRATE) - player->karthud[khud_cardanimation] += ((164-player->karthud[khud_cardanimation])/8)+1; - } - - if (player->karthud[khud_cardanimation] > 164) - player->karthud[khud_cardanimation] = 164; - if (player->karthud[khud_cardanimation] < 0) - player->karthud[khud_cardanimation] = 0; - } - else - player->karthud[khud_cardanimation] = 0; } #undef RINGANIM_DELAYMAX @@ -12245,7 +12228,7 @@ UINT32 K_PointLimitForGametype(void) return 0; } - if ((gametyperules & battleRules) == battleRules) + if ((gametyperules & battleRules) == battleRules) // why isn't this just another GTR_?? { INT32 i; diff --git a/src/k_tally.cpp b/src/k_tally.cpp new file mode 100644 index 000000000..359ffb657 --- /dev/null +++ b/src/k_tally.cpp @@ -0,0 +1,1374 @@ +// 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_tally.cpp +/// \brief End of level tally screen animations + +#include + +#include "k_tally.h" + +#include "k_kart.h" +#include "k_rank.h" +#include "k_grandprix.h" +#include "k_race.h" +#include "k_battle.h" +#include "k_boss.h" +#include "k_specialstage.h" +#include "k_hud.h" +#include "doomstat.h" +#include "g_game.h" +#include "p_local.h" +#include "r_main.h" +#include "r_skins.h" +#include "v_video.h" +#include "v_draw.hpp" +#include "z_zone.h" +#include "y_inter.h" +#include "m_easing.h" +#include "s_sound.h" +#include "st_stuff.h" + +boolean level_tally_t::UseBonuses(void) +{ + if ((gametyperules & GTR_SPECIALSTART) || (grandprixinfo.gp == true && grandprixinfo.eventmode == GPEVENT_SPECIAL)) + { + // Special Stage -- the only bonus for these is completing it or not. + return false; + } + + // No bonuses / ranking in FREE PLAY or Time Attack + return (K_TimeAttackRules() == false || grandprixinfo.gp == true); +} + +void level_tally_t::DetermineBonuses(void) +{ + std::vector temp_bonuses; + + // Figure out a set of two bonuses to use + // for this gametype's ranking, depending + // on what rules are activated and how important + // they are. + + // The end of the vector is prioritized + // more than the beginning of the vector, + // so this is basically ranked choice + // starting from least important to + // most important. + + if (UseBonuses() == true) + { + if (grandprixinfo.gp == true && (gametypes[gt]->rules & GTR_SPHERES) == 0) + { + // Ring is a throw-away bonus, just meant to + // encourage people to earn more lives, + // so only have it in GP and with lowest priority. + temp_bonuses.push_back(TALLY_BONUS_RING); + } + + if (totalPrisons == 0) // These are only for regular Battle, not Prison Break + { + if ((gametypes[gt]->rules & GTR_POWERSTONES) == GTR_POWERSTONES) + { + // Give a consolation bonus for people who + // almost won by getting all Chaos Emeralds. + temp_bonuses.push_back(TALLY_BONUS_POWERSTONES); + } + + if ((gametypes[gt]->rules & GTR_POINTLIMIT) == GTR_POINTLIMIT) + { + // Give a consolation bonus for getting + // close to the point limit. + temp_bonuses.push_back(TALLY_BONUS_SCORE); + } + } + + if (totalLaps > 0) + { + // Give circuit gamemodes a consolation bonus + // for getting good placements on each lap. + temp_bonuses.push_back(TALLY_BONUS_LAP); + } + + if (totalPrisons > 0) + { + // If prisons exist, then this means that it is + // the entire point of the mode, so make it + // the most important. + temp_bonuses.push_back(TALLY_BONUS_PRISON); + } + } + + // Take the two most important, and put them in our actual list. + for (int i = 0; i < TALLY_WINDOW_SIZE; i++) + { + if (temp_bonuses.empty() == true) + { + bonuses[i] = TALLY_BONUS_NA; // No bonus to add... + } + else + { + bonuses[i] = temp_bonuses.back(); + temp_bonuses.pop_back(); + +#if 0 + switch (bonuses[i]) + { + default: + displayBonus[i] = 0; + break; + } +#else + displayBonus[i] = 0; +#endif + } + } +} + +void level_tally_t::DetermineStatistics(void) +{ + std::vector temp_stats; + + // Same thing as DetermineBonuses, but for the + // for-fun stats that don't do anything. + + if (grandprixinfo.gp == true) + { + // Show the player's total rings. + // This one is special and also + // shows lives gained. + temp_stats.push_back(TALLY_STAT_TOTALRINGS); + } + + // Maybe there will be a situation in the + // future where we DON'T want to show time? + temp_stats.push_back(TALLY_STAT_TIME); + + // Take the two most important, and put them in our actual list. + for (int i = 0; i < TALLY_WINDOW_SIZE; i++) + { + if (temp_stats.empty() == true) + { + stats[i] = TALLY_STAT_NA; // No stat to add... + } + else + { + stats[i] = temp_stats.back(); + temp_stats.pop_back(); + + switch (stats[i]) + { + case TALLY_STAT_TOTALRINGS: + displayStat[i] = ringPool; + break; + default: + displayStat[i] = 0; + break; + } + } + } +} + +INT32 level_tally_t::CalculateGrade(void) +{ + static const fixed_t gradePercents[GRADE_A] = { + 7*FRACUNIT/20, // D: 35% or higher + 10*FRACUNIT/20, // C: 50% or higher + 14*FRACUNIT/20, // B: 70% or higher + 17*FRACUNIT/20 // A: 85% or higher + }; + INT32 retGrade = GRADE_E; // gp_rank_e + + INT32 bonusWeights[TALLY_WINDOW_SIZE]; + for (int i = 0; i < TALLY_WINDOW_SIZE; i++) + { + switch (bonuses[i]) + { + case TALLY_BONUS_RING: + { + bonusWeights[i] = 20; + break; + } + case TALLY_BONUS_SCORE: + { + bonusWeights[i] = 100; + break; + } + case TALLY_BONUS_LAP: + case TALLY_BONUS_PRISON: + case TALLY_BONUS_POWERSTONES: + { + bonusWeights[i] = 150; + break; + } + default: + { + bonusWeights[i] = 0; + break; + } + } + } + + const INT32 positionWeight = (position > 0 && numPlayers > 2) ? 100 : 0; + const INT32 total = positionWeight + bonusWeights[0] + bonusWeights[1]; + + INT32 ours = 0; + fixed_t percent = 0; + + if (position > 0 && numPlayers > 2) + { + const INT32 sc = (position - 1); + const INT32 loser = ((numPlayers + 1) / 2) - 1; // number of winner positions + ours += ((loser - sc) * positionWeight) / loser; + } + + for (int i = 0; i < TALLY_WINDOW_SIZE; i++) + { + switch (bonuses[i]) + { + case TALLY_BONUS_RING: + { + ours += (rings * bonusWeights[i]) / 20; + break; + } + case TALLY_BONUS_LAP: + { + ours += (laps * bonusWeights[i]) / std::max(1, static_cast(totalLaps)); + break; + } + case TALLY_BONUS_PRISON: + { + ours += (prisons * bonusWeights[i]) / std::max(1, static_cast(totalPrisons)); + break; + } + case TALLY_BONUS_SCORE: + { + if (pointLimit != 0) + { + ours += (points * bonusWeights[i]) / std::max(1, static_cast(abs(pointLimit))); + } + break; + } + case TALLY_BONUS_POWERSTONES: + { + ours += (powerStones * bonusWeights[i]) / 7; + break; + } + default: + { + break; + } + } + } + + percent = FixedDiv(ours, total); + + for (retGrade = GRADE_E; retGrade < GRADE_A; retGrade++) + { + if (percent < gradePercents[retGrade]) + { + break; + } + } + + return retGrade; +} + +void level_tally_t::Init(player_t *player) +{ + if (active == true) + { + return; + } + + active = true; + owner = player; + gt = gametype; + + const boolean game_over = ((player->pflags & PF_LOSTLIFE) == PF_LOSTLIFE); + + time = std::min(static_cast(player->realtime), (100 * 60 * TICRATE) - 1); + ringPool = player->totalring; + + position = numPlayers = 0; + rings = 0; + laps = totalLaps = 0; + points = pointLimit = 0; + powerStones = 0; + + rank = GRADE_E; + + if (player->spectator == false && player->bot == false && game_over == false) + { + if (K_TimeAttackRules() == false && K_Cooperative() == false) + { + position = (player->pflags & PF_NOCONTEST) ? (MAXPLAYERS+1) : player->position; + + for (int i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == true && players[i].spectator == false) + { + numPlayers++; + } + } + } + + if ((gametypes[gt]->rules & GTR_SPHERES) == 0) + { + if (player->hudrings > 0) // Don't count negative rings + { + rings = player->hudrings; + } + } + + if ((gametypes[gt]->rules & GTR_CIRCUIT) == GTR_CIRCUIT) + { + laps = player->lapPoints; + + if (inDuel == true) + { + totalLaps = K_RaceLapCount(gamemap); + } + else + { + totalLaps = K_RaceLapCount(gamemap) * 2; + } + } + + if (battleprisons) + { + prisons = numtargets; + totalPrisons = maptargets; + } + + if ((gametypes[gt]->rules & GTR_POINTLIMIT) == GTR_POINTLIMIT) + { + points = player->roundscore; + pointLimit = g_pointlimit; + + if (pointLimit == 0) + { + // Get max from players in server. + // This is set as a negative number + // to communicate to not show it as x/y + // when drawing it on the HUD. + for (int i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == true && players[i].spectator == false) + { + pointLimit = std::min(pointLimit, static_cast(-players[i].roundscore)); + } + } + } + } + + if ((gametypes[gt]->rules & GTR_POWERSTONES) == GTR_POWERSTONES) + { + powerStones = K_NumEmeralds(player); + } + + DetermineStatistics(); + DetermineBonuses(); + + rank = CalculateGrade(); + } + + header[0] = '\0'; + gotThru = false; + + if (player->spectator == false) + { + if (game_over == true) + { + if (player->lives <= 0) + { + snprintf( + header, sizeof header, + "GAME OVER" + ); + } + else + { + snprintf( + header, sizeof header, + "TRY AGAIN" + ); + } + } + else if ((player->pflags & PF_NOCONTEST) == 0) + { + gotThru = true; + + if (player->skin < numskins) + { + snprintf( + header, sizeof header, + "%s", skins[player->skin].realname + ); + } + + if (roundqueue.size > 0 && roundqueue.roundnum > 0 + && (grandprixinfo.gp == false || grandprixinfo.eventmode == GPEVENT_NONE)) + { + roundNum = roundqueue.roundnum; + } + } + else + { + snprintf( + header, sizeof header, + "NO CONTEST..." + ); + } + } + else + { + if (roundqueue.size > 0 && roundqueue.roundnum > 0 + && (grandprixinfo.gp == false || grandprixinfo.eventmode == GPEVENT_NONE)) + { + snprintf( + header, sizeof header, + "%s", skins[player->skin].realname + ); + + roundNum = roundqueue.roundnum; + } + else if (bossinfo.valid == true && bossinfo.enemyname) + { + snprintf( + header, sizeof header, + "%s", bossinfo.enemyname + ); + } + else if (battleprisons == true) + { + snprintf( + header, sizeof header, + "PRISON BREAK" + ); + } + else + { + snprintf( + header, sizeof header, + "%s STAGE", + gametypes[gametype]->name + ); + } + } + + header[sizeof header - 1] = '\0'; + + // Only show grade if there were any bonuses + showGrade = (position > 0); + if (showGrade == false) + { + for (int i = 0; i < TALLY_WINDOW_SIZE; i++) + { + if (bonuses[i] != TALLY_BONUS_NA) + { + showGrade = true; + break; + } + } + } + + lineCount = 0; + for (int i = 0; i < TALLY_WINDOW_SIZE; i++) + { + if (stats[i] != TALLY_STAT_NA) + { + lineCount++; + } + if (bonuses[i] != TALLY_BONUS_NA) + { + lineCount++; + } + } + + gradeVoice = sfx_None; + if (showGrade == true) + { + // It'd be neat to add all of the grade sounds, + // but not this close to release + if (rank < GRADE_C) + { + gradeVoice = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_klose].skinsound]; + } + else + { + gradeVoice = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_kwin].skinsound]; + } + } + + delay = TALLY_TIME; // sync up with musiccountdown + + if (game_over == true) + { + // set up game over instead + state = TALLY_ST_GAMEOVER_SLIDEIN; + showGrade = false; + } + else + { + state = TALLY_ST_GOTTHRU_SLIDEIN; + } + + hudSlide = 0; + + transition = 0; + transitionTime = TICRATE/2; + + done = (player->spectator == true || player->bot == true); + + if (specialstageinfo.valid == true && (player->pflags & PF_NOCONTEST) == PF_NOCONTEST) + { + // No tally when losing special stages + state = TALLY_ST_IGNORE; + delay = 0; + } +} + +void level_tally_t::NewLine(void) +{ + state = TALLY_ST_TEXT_APPEAR; + lines++; + //S_StartSound(NULL, sfx_mbs5b); + //transition = 0; + //transitionTime = TICRATE/5; + delay = TICRATE/5; +} + +boolean level_tally_t::IncrementLine(void) +{ + UINT8 count = lines; + + INT32 *value = nullptr; + INT32 dest = 0; + + INT32 amount = 1; + INT32 freq = 2; + + boolean lives_check = false; + + for (int i = 0; i < TALLY_WINDOW_SIZE; i++) + { + if (count == 0) + { + break; + } + + if (stats[i] == TALLY_STAT_NA) + { + break; + } + + value = &displayStat[i]; + lives_check = (stats[i] == TALLY_STAT_TOTALRINGS); + + switch (stats[i]) + { + case TALLY_STAT_TIME: + dest = time; + amount = 111; + freq = 0; + break; + case TALLY_STAT_TOTALRINGS: + dest = ringPool + rings; + amount = 1; + freq = 1; + break; + default: + dest = 0; + amount = 1; + freq = 2; + break; + } + + count--; + } + + for (int i = 0; i < TALLY_WINDOW_SIZE; i++) + { + if (count == 0) + { + break; + } + + if (bonuses[i] == TALLY_BONUS_NA) + { + break; + } + + value = &displayBonus[i]; + lives_check = false; + + switch (bonuses[i]) + { + case TALLY_BONUS_RING: + dest = rings; + amount = 1; + freq = 1; + break; + case TALLY_BONUS_LAP: + dest = laps; + amount = 1; + freq = 4; + break; + case TALLY_BONUS_PRISON: + dest = prisons; + amount = 1; + freq = 4; + break; + case TALLY_BONUS_SCORE: + dest = points; + amount = 11; + freq = 2; + break; + case TALLY_BONUS_POWERSTONES: + dest = powerStones; + amount = 1; + freq = 4; + break; + default: + dest = 0; + amount = 1; + freq = 2; + break; + } + + count--; + } + + if (count > 0) + { + // No more lines to update. + return true; + } + + if (*value == dest) + { + // We've reached our destination + return true; + } + + const INT32 prevVal = *value; + const boolean playSounds = P_IsDisplayPlayer(owner); + + if (playSounds == true && tickSound == 0) + { + S_StopSoundByNum(sfx_mbs5f); + S_StartSound(NULL, sfx_mbs5f); + tickSound = 3; + } + + if (*value > dest) + { + *value -= amount; + + if (*value < dest) + { + *value = dest; + } + } + else + { + *value += amount; + + if (*value > dest) + { + *value = dest; + } + } + + if (lives_check == true) + { + const UINT8 lifethreshold = 20; + const UINT8 oldExtra = prevVal / lifethreshold; + const UINT8 extra = *value / lifethreshold; + + // Handle extra life sound & blinking + if (extra > oldExtra) + { + xtraBlink = TICRATE; + + if (playSounds == true) + { + S_StopSoundByNum(sfx_cdfm73); + S_StartSound(NULL, sfx_cdfm73); + } + } + } + + delay = freq; + return false; +} + +void level_tally_t::Tick(void) +{ + if (hudSlide < TICRATE/4) + { + hudSlide++; + } + + if (tickSound > 0) + { + tickSound--; + } + + if (xtraBlink > 0) + { + xtraBlink--; + } + + if (delay > 0) + { + delay--; + return; + } + + if (transition < FRACUNIT) + { + if (transitionTime <= 0) + { + transition = FRACUNIT; + return; + } + + transition += FRACUNIT / transitionTime; + if (transition > FRACUNIT) + { + transition = FRACUNIT; + } + return; + } + + const boolean playSounds = P_IsDisplayPlayer(owner); + + switch (state) + { + case TALLY_ST_GOTTHRU_SLIDEIN: + { + state = TALLY_ST_GOTTHRU_SLIDEUP; + transition = 0; + transitionTime = TICRATE/2; + delay = TICRATE/2; + break; + } + case TALLY_ST_GOTTHRU_SLIDEUP: + { + state = TALLY_ST_BOXES_SLIDEIN; + transition = 0; + transitionTime = TICRATE/2; + delay = TICRATE/5; + break; + } + case TALLY_ST_BOXES_SLIDEIN: + { + NewLine(); + break; + } + case TALLY_ST_TEXT_APPEAR: + { + if (IncrementLine() == true) + { + if (playSounds) + { + S_StopSoundByNum(sfx_mbs5b); + S_StartSound(NULL, (lines >= lineCount) ? sfx_mbs70 : sfx_mbs5b); + } + + state = TALLY_ST_TEXT_PAUSE; + delay = TICRATE/2; + } + break; + } + case TALLY_ST_TEXT_PAUSE: + { + if (lines < lineCount) + { + NewLine(); + } + else + { + if (showGrade == true) + { + state = TALLY_ST_GRADE_APPEAR; + transition = 0; + transitionTime = TICRATE/7; + delay = TICRATE/2; + } + else + { + state = TALLY_ST_DONE; + delay = 5*TICRATE; + } + } + break; + } + case TALLY_ST_GRADE_APPEAR: + { + if (playSounds) + { + S_StartSound(NULL, sfx_rank); + } + state = TALLY_ST_GRADE_VOICE; + delay = TICRATE/2; + break; + } + case TALLY_ST_GRADE_VOICE: + { + if (playSounds && cv_kartvoices.value) + { + S_StartSound(NULL, gradeVoice); + } + state = TALLY_ST_DONE; + delay = 5*TICRATE; + break; + } + case TALLY_ST_GAMEOVER_SLIDEIN: + { + state = TALLY_ST_GAMEOVER_LIVES; + delay = TICRATE; + break; + } + case TALLY_ST_GAMEOVER_LIVES: + { + state = TALLY_ST_GAMEOVER_DONE; + delay = 4*TICRATE; + break; + } + case TALLY_ST_DONE: + case TALLY_ST_GAMEOVER_DONE: + case TALLY_ST_IGNORE: + { + done = true; + break; + } + + default: + { + // error occured, silently fix + state = TALLY_ST_DONE; + break; + } + } +} + +void level_tally_t::Draw(void) +{ + if (state == TALLY_ST_IGNORE) + { + return; + } + + const float transition_f = FixedToFloat(transition); + const float transition_i = 1.0 - transition_f; + + const float frac = (r_splitscreen ? 0.5 : 1.0); + + INT32 v_width = BASEVIDWIDTH; + INT32 v_height = BASEVIDHEIGHT; + if (r_splitscreen > 0) + { + v_height /= 2; + } + if (r_splitscreen > 1) + { + v_width /= 2; + } + + SINT8 h_transition_sign = 1; + if (r_splitscreen > 1) + { + if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) + { + h_transition_sign = -h_transition_sign; + } + } + else if (r_splitscreen > 0) + { + if (stplyr == &players[displayplayers[1]]) + { + h_transition_sign = -h_transition_sign; + } + } + + srb2::Draw drawer = (srb2::Draw(0, 0)) + .flags(V_SPLITSCREEN) + .clipx(0, v_width) + .clipy(0, v_height); + + + INT32 fade = 5; + if (state == TALLY_ST_GOTTHRU_SLIDEIN + || state == TALLY_ST_GAMEOVER_SLIDEIN) + { + fade = (5 * transition_f); + } + V_DrawFadeFill( + 0, 0, + v_width, v_height, + V_SPLITSCREEN, + 31, fade + ); + + const INT32 header_width = (r_splitscreen ? (BASEVIDWIDTH * 0.5) : BASEVIDWIDTH); + const INT32 header_x = (v_width - header_width) * 0.5; + + const INT32 header_height = 36 * frac; + const INT32 header_centered = (v_height * 0.5) - header_height; + + switch (state) + { + case TALLY_ST_GAMEOVER_SLIDEIN: + case TALLY_ST_GAMEOVER_LIVES: + case TALLY_ST_GAMEOVER_DONE: + case TALLY_ST_GOTTHRU_SLIDEIN: + Y_DrawIntermissionHeader( + (header_x * FRACUNIT) + (v_width * transition_i * FRACUNIT * h_transition_sign), + header_centered * FRACUNIT, + gotThru, header, roundNum, (r_splitscreen > 0) + ); + break; + + case TALLY_ST_GOTTHRU_SLIDEUP: + Y_DrawIntermissionHeader( + header_x * FRACUNIT, + header_centered * transition_i * FRACUNIT, + gotThru, header, roundNum, (r_splitscreen > 0) + ); + break; + + default: + Y_DrawIntermissionHeader( + header_x * FRACUNIT, + 0, + gotThru, header, roundNum, (r_splitscreen > 0) + ); + break; + } + + if (state == TALLY_ST_GAMEOVER_SLIDEIN + || state == TALLY_ST_GAMEOVER_LIVES + || state == TALLY_ST_GAMEOVER_DONE) + { + if (owner->lives > 0) + { + srb2::Draw lives_drawer = drawer + .xy( + (v_width * 0.5) + (v_width * transition_i * h_transition_sign) - 8.0, + header_centered + (header_height * 1.5) + 4.0 + ); + + const skincolornum_t color = static_cast(owner->skincolor); + lives_drawer + .colormap(owner->skin, color) + .patch(faceprefix[owner->skin][FACE_RANK]); + + UINT8 lives_num = owner->lives; + if (state == TALLY_ST_GAMEOVER_SLIDEIN) + { + lives_num++; + } + else if (state == TALLY_ST_GAMEOVER_LIVES) + { + lives_num++; + + if (((delay / 5) & 1) == 0) + { + lives_num = 0; + } + } + + if (lives_num > 0) + { + lives_drawer + .xy(17.0, 4.0) + .patch( va("OPPRNK%02d", lives_num) ); + } + } + + return; + } + + if (state != TALLY_ST_GOTTHRU_SLIDEIN + && state != TALLY_ST_GOTTHRU_SLIDEUP) + { + UINT8 numBoxes = 0; + boolean drawStats = false; + if (stats[0] != TALLY_STAT_NA) + { + numBoxes++; + drawStats = true; + } + if (bonuses[0] != TALLY_BONUS_NA) + { + numBoxes++; + } + + patch_t *box_fg = static_cast( W_CachePatchName(va("RNKBLK%sA", (r_splitscreen ? "4" : "1")), PU_CACHE) ); + patch_t *box_bg = static_cast( W_CachePatchName(va("RNKBLK%sB", (r_splitscreen ? "4" : "1")), PU_CACHE) ); + + patch_t *sticker = static_cast( W_CachePatchName((r_splitscreen ? "K_SPDMBG" : "K_STTIME"), PU_CACHE) ); + const float sticker_offset = (r_splitscreen ? 0.0 : 3.0); + + patch_t *egg_sticker = static_cast( W_CachePatchName("EGGSTKR", PU_CACHE) ); + + srb2::Draw drawer_box = drawer.xy( + (v_width * 0.5) - (box_bg->width * 0.5), + ((v_height * 0.5) + (8.0 * frac)) - ((box_bg->height * 0.5) * numBoxes) + ); + + UINT8 displayLines = lines; + + for (int b = 0; b < numBoxes; b++) + { + srb2::Draw drawer_box_offset = drawer_box; + + switch (state) + { + case TALLY_ST_BOXES_SLIDEIN: + drawer_box_offset = drawer_box_offset.x( ((b & 1) ? 1 : -1) * transition_i * v_width * h_transition_sign ); + break; + default: + break; + } + + drawer_box_offset + .colormap(SKINCOLOR_BLUE) + .flags(V_ADD) + .patch(box_bg); + + drawer_box_offset + .patch(box_fg); + + srb2::Draw drawer_text = drawer_box_offset + .xy(11.0 * frac, 6.0 * frac) + .font(r_splitscreen ? srb2::Draw::Font::kPing : srb2::Draw::Font::kTimer); + + UINT8 boxLines = 0; + if (drawStats == true) + { + for (int i = 0; i < TALLY_WINDOW_SIZE; i++) + { + if (stats[i] != TALLY_STAT_NA) + { + boxLines++; + } + } + } + else + { + for (int i = 0; i < TALLY_WINDOW_SIZE; i++) + { + if (bonuses[i] != TALLY_BONUS_NA) + { + boxLines++; + } + } + } + + drawer_text = drawer_text.y(17.0 * frac * (TALLY_WINDOW_SIZE - boxLines) * 0.5); + + for (int i = 0; i < TALLY_WINDOW_SIZE; i++) + { + if (displayLines == 0) + { + break; + } + + const char *bonus_code = "XX"; + if (drawStats == true) + { + if (stats[i] == TALLY_STAT_NA) + { + continue; + } + + switch (stats[i]) + { + case TALLY_STAT_TIME: + bonus_code = "TM"; + break; + case TALLY_STAT_TOTALRINGS: + bonus_code = "TR"; + break; + default: + break; + } + } + else + { + if (bonuses[i] == TALLY_BONUS_NA) + { + continue; + } + + switch (bonuses[i]) + { + case TALLY_BONUS_RING: + bonus_code = "RB"; + break; + case TALLY_BONUS_LAP: + bonus_code = "LA"; + break; + case TALLY_BONUS_PRISON: + bonus_code = "PR"; + break; + case TALLY_BONUS_SCORE: + bonus_code = "ST"; + break; + case TALLY_BONUS_POWERSTONES: + bonus_code = "EM"; + break; + default: + break; + } + } + + if (r_splitscreen == 0) + { + drawer_text + .xy(100.0 * frac, -2.0 * frac) + .patch(egg_sticker); + } + + drawer_text + .y(-1.0 * frac) + .patch(va("BNS%sP_%s", (r_splitscreen ? "4" : "1"), bonus_code)); + + drawer_text + .xy((197.0 * frac) - (sticker->width * 0.5), -sticker_offset) + .patch(sticker); + + if (drawStats == true) + { + switch (stats[i]) + { + case TALLY_STAT_TIME: + { + INT32 work_minutes = displayStat[i] / (60 * TICRATE); + INT32 work_seconds = displayStat[i] / TICRATE % 60; + INT32 work_tics = G_TicsToCentiseconds(displayStat[i]); + + drawer_text + .x(197.0 * frac) + .align(srb2::Draw::Align::kCenter) + .text(va( + "%d%d'%d%d\"%d%d", + work_minutes / 10, + work_minutes % 10, + work_seconds / 10, + work_seconds % 10, + work_tics / 10, + work_tics % 10 + )); + break; + } + case TALLY_STAT_TOTALRINGS: + { + drawer_text + .x(184.0 * frac) + .align(srb2::Draw::Align::kCenter) + .text(va("%d", displayStat[i])); + + srb2::Draw lives_drawer = drawer_text + .xy(221.0 * frac, -1.0 * frac); + + const skincolornum_t color = static_cast(owner->skincolor); + lives_drawer + .colormap(owner->skin, color) + .patch(faceprefix[owner->skin][r_splitscreen ? FACE_MINIMAP : FACE_RANK]); + + const UINT8 lifethreshold = 20; + const UINT8 oldExtra = ringPool / lifethreshold; + const UINT8 extra = displayStat[i] / lifethreshold; + const INT32 increase = (extra - oldExtra); + + UINT8 lives_num = owner->lives + increase; + if (xtraBlink > 0 && (xtraBlink & 1) == 0 && increase > 0) + { + lives_num = 0; + } + + if (lives_num > 0) + { + if (r_splitscreen) + { + lives_drawer + .xy(6.0, 2.0) + .align(srb2::Draw::Align::kLeft) + .text(va("%d", lives_num)); + } + else + { + lives_drawer + .xy(17.0, 4.0) + .patch( va("OPPRNK%02d", lives_num) ); + } + } + + break; + } + default: + { + drawer_text + .x(197.0 * frac) + .align(srb2::Draw::Align::kCenter) + .text(va("%d", displayStat[i])); + break; + } + } + } + else + { + switch (bonuses[i]) + { + case TALLY_BONUS_RING: + { + drawer_text + .x(197.0 * frac) + .align(srb2::Draw::Align::kCenter) + .text(va("%d / 20", displayBonus[i])); + break; + } + case TALLY_BONUS_LAP: + { + drawer_text + .x(197.0 * frac) + .align(srb2::Draw::Align::kCenter) + .text(va("%d / %d", displayBonus[i], totalLaps)); + break; + } + case TALLY_BONUS_PRISON: + { + drawer_text + .x(197.0 * frac) + .align(srb2::Draw::Align::kCenter) + .text(va("%d / %d", displayBonus[i], totalPrisons)); + break; + } + case TALLY_BONUS_SCORE: + { + if (pointLimit > 0) + { + drawer_text + .x(197.0 * frac) + .align(srb2::Draw::Align::kCenter) + .text(va("%d / %d", displayBonus[i], pointLimit)); + } + else + { + drawer_text + .x(197.0 * frac) + .align(srb2::Draw::Align::kCenter) + .text(va("%d", displayBonus[i])); + } + break; + } + case TALLY_BONUS_POWERSTONES: + { + drawer_text + .x(197.0 * frac) + .align(srb2::Draw::Align::kCenter) + .text(va("%d / 7", displayBonus[i])); + break; + } + default: + { + drawer_text + .x(197.0 * frac) + .align(srb2::Draw::Align::kCenter) + .text(va("%d", displayBonus[i])); + break; + } + } + } + + drawer_text = drawer_text.y(17.0 * frac); + displayLines--; + } + drawStats = false; + + drawer_box = drawer_box.y(box_bg->height); + } + } + + if (showGrade == false) + { + return; + } + + if ((state == TALLY_ST_GRADE_APPEAR && delay == 0) + || 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; + } + + patch_t *grade_img = static_cast( W_CachePatchName(va("R_FINR%s%s", (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) )) ); + + float sc = 1.0; + if (state == TALLY_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); + } +} + +void K_InitPlayerTally(player_t *player) +{ + player->tally.Init(player); +} + +void K_TickPlayerTally(player_t *player) +{ + player->tally.Tick(); +} + +void K_DrawPlayerTally(void) +{ + stplyr->tally.Draw(); +} + +boolean K_PlayerTallyActive(player_t *player) +{ + return player->tally.active; //(player->exiting || (player->pflags & PF_NOCONTEST)); +} diff --git a/src/k_tally.h b/src/k_tally.h new file mode 100644 index 000000000..0cf03e8ca --- /dev/null +++ b/src/k_tally.h @@ -0,0 +1,126 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) by Kart Krew +// Copyright (C) by Sally "TehRealSalt" Cochenour +// +// 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_tally.h +/// \brief End of level tally screen animations + +#ifndef __K_TALLY_H__ +#define __K_TALLY_H__ + +#include "typedef.h" +#include "doomtype.h" +#include "doomdef.h" +#include "sounds.h" + +#define TALLY_WINDOW_SIZE (2) + +#define TALLY_TIME (3*TICRATE) +#define MUSIC_COUNTDOWN_MAX (TALLY_TIME + 8*TICRATE) + +typedef enum +{ + TALLY_STAT_NA, + TALLY_STAT_TIME, + TALLY_STAT_TOTALRINGS, +} tally_stat_e; + +typedef enum +{ + TALLY_BONUS_NA, + TALLY_BONUS_RING, + TALLY_BONUS_LAP, + TALLY_BONUS_PRISON, + TALLY_BONUS_SCORE, + TALLY_BONUS_POWERSTONES, +} tally_bonus_e; + +typedef enum +{ + TALLY_ST_IGNORE, + + TALLY_ST_GOTTHRU_SLIDEIN, + TALLY_ST_GOTTHRU_SLIDEUP, + TALLY_ST_BOXES_SLIDEIN, + TALLY_ST_TEXT_APPEAR, + TALLY_ST_TEXT_PAUSE, + TALLY_ST_GRADE_APPEAR, + TALLY_ST_GRADE_VOICE, + TALLY_ST_DONE, + + TALLY_ST_GAMEOVER_SLIDEIN, + TALLY_ST_GAMEOVER_LIVES, + TALLY_ST_GAMEOVER_DONE, +} tally_state_e; + +struct level_tally_t +{ + boolean active; + player_t *owner; + + UINT16 gt; + boolean gotThru; + char header[64]; + UINT8 roundNum; + sfxenum_t gradeVoice; + + // Stats + INT32 time; + UINT16 ringPool; + tally_stat_e stats[TALLY_WINDOW_SIZE]; + + // Possible grade metrics + UINT8 position, numPlayers; + UINT8 rings; + UINT16 laps, totalLaps; + UINT16 prisons, totalPrisons; + INT32 points, pointLimit; + UINT8 powerStones; + tally_bonus_e bonuses[TALLY_WINDOW_SIZE]; + INT32 rank; // FIXME: should be gp_rank_e, weird circular dependency happened + + // Animations + tally_state_e state; + INT32 hudSlide; + INT32 delay; + INT32 transition, transitionTime; + UINT8 lines, lineCount; + INT32 displayStat[TALLY_WINDOW_SIZE]; + INT32 displayBonus[TALLY_WINDOW_SIZE]; + UINT8 tickSound; + UINT8 xtraBlink; + boolean showGrade; + boolean done; + +#ifdef __cplusplus + boolean UseBonuses(void); + void DetermineBonuses(void); + void DetermineStatistics(void); + INT32 CalculateGrade(void); + void Init(player_t *player); + void NewLine(void); + boolean IncrementLine(void); + void Tick(void); + void Draw(void); +#endif +}; + +#ifdef __cplusplus +extern "C" { +#endif + +void K_InitPlayerTally(player_t *player); +void K_TickPlayerTally(player_t *player); +void K_DrawPlayerTally(void); +boolean K_PlayerTallyActive(player_t *player); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __K_TALLY_H__ diff --git a/src/lua_hud.h b/src/lua_hud.h index 20a6e6549..722bca50e 100644 --- a/src/lua_hud.h +++ b/src/lua_hud.h @@ -34,8 +34,6 @@ enum hud { hud_check, // "CHECK" f-zero indicator hud_minirankings, // Rankings to the left hud_battlebumpers, // mini rankings battle bumpers. - hud_battlefullscreen, // battle huge text (WAIT, WIN, LOSE ...) + karma comeback time - hud_battlecomebacktimer, // comeback timer in battlefullscreen. separated for ease of use. hud_wanted, hud_speedometer, hud_freeplay, diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index a95ff78c2..ad560dd1e 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -52,8 +52,6 @@ static const char *const hud_disable_options[] = { "check", // "CHECK" f-zero indicator "minirankings", // Gametype rankings to the left "battlerankingsbumpers", // bumper drawer for battle. Useful if you want to make a custom battle gamemode without bumpers being involved. - "battlefullscreen", // battlefullscreen func (WAIT, ATTACK OR PROTECT ...) - "battlecomebacktimer", // come back timer in battlefullscreen "wanted", "speedometer", "freeplay", diff --git a/src/p_inter.c b/src/p_inter.c index 5ea667290..9d0869509 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1567,6 +1567,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget if (!target->player->exiting) { target->player->pflags |= PF_NOCONTEST; + K_InitPlayerTally(target->player); } } break; diff --git a/src/p_saveg.c b/src/p_saveg.c index bcebba7ae..48bbc31ed 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -647,6 +647,52 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT16(save->p, players[i].powerup.superTimer); WRITEUINT16(save->p, players[i].powerup.barrierTimer); WRITEUINT16(save->p, players[i].powerup.rhythmBadgeTimer); + + // level_tally_t + WRITEUINT8(save->p, players[i].tally.active); + if (players[i].tally.active) + { + WRITEUINT16(save->p, players[i].tally.gt); + WRITEUINT8(save->p, players[i].tally.gotThru); + WRITESTRINGN(save->p, players[i].tally.header, 63); + WRITEUINT8(save->p, players[i].tally.roundNum); + WRITEINT32(save->p, players[i].tally.gradeVoice); + + WRITEINT32(save->p, players[i].tally.time); + WRITEUINT16(save->p, players[i].tally.ringPool); + for (q = 0; q < TALLY_WINDOW_SIZE; q++) + WRITEINT32(save->p, players[i].tally.stats[q]); + + WRITEUINT8(save->p, players[i].tally.position); + WRITEUINT8(save->p, players[i].tally.numPlayers); + WRITEUINT8(save->p, players[i].tally.rings); + WRITEUINT16(save->p, players[i].tally.laps); + WRITEUINT16(save->p, players[i].tally.totalLaps); + WRITEUINT16(save->p, players[i].tally.prisons); + WRITEUINT16(save->p, players[i].tally.totalPrisons); + WRITEINT32(save->p, players[i].tally.points); + WRITEINT32(save->p, players[i].tally.pointLimit); + WRITEUINT8(save->p, players[i].tally.powerStones); + for (q = 0; q < TALLY_WINDOW_SIZE; q++) + WRITEINT32(save->p, players[i].tally.bonuses[q]); + WRITEINT32(save->p, players[i].tally.rank); + + WRITEINT32(save->p, players[i].tally.state); + WRITEINT32(save->p, players[i].tally.hudSlide); + WRITEINT32(save->p, players[i].tally.delay); + WRITEINT32(save->p, players[i].tally.transition); + WRITEINT32(save->p, players[i].tally.transitionTime); + WRITEUINT8(save->p, players[i].tally.lines); + WRITEUINT8(save->p, players[i].tally.lineCount); + for (q = 0; q < TALLY_WINDOW_SIZE; q++) + WRITEINT32(save->p, players[i].tally.displayStat[q]); + for (q = 0; q < TALLY_WINDOW_SIZE; q++) + WRITEINT32(save->p, players[i].tally.displayBonus[q]); + WRITEUINT8(save->p, players[i].tally.tickSound); + WRITEUINT8(save->p, players[i].tally.xtraBlink); + WRITEUINT8(save->p, players[i].tally.showGrade); + WRITEUINT8(save->p, players[i].tally.done); + } } } @@ -1085,6 +1131,56 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].powerup.barrierTimer = READUINT16(save->p); players[i].powerup.rhythmBadgeTimer = READUINT16(save->p); + // level_tally_t + players[i].tally.active = READUINT8(save->p); + if (players[i].tally.active) + { + players[i].tally.owner = &players[i]; + players[i].tally.gt = READUINT16(save->p); + players[i].tally.gotThru = (boolean)READUINT8(save->p); + + READSTRINGN(save->p, players[i].tally.header, 63); + players[i].tally.header[63] = '\0'; + + players[i].tally.roundNum = READUINT8(save->p); + players[i].tally.gradeVoice = READINT32(save->p); + + players[i].tally.time = READINT32(save->p); + players[i].tally.ringPool = READUINT16(save->p); + for (q = 0; q < TALLY_WINDOW_SIZE; q++) + players[i].tally.stats[q] = READINT32(save->p); + + players[i].tally.position = READUINT8(save->p); + players[i].tally.numPlayers = READUINT8(save->p); + players[i].tally.rings = READUINT8(save->p); + players[i].tally.laps = READUINT16(save->p); + players[i].tally.totalLaps = READUINT16(save->p); + players[i].tally.prisons = READUINT16(save->p); + players[i].tally.totalPrisons = READUINT16(save->p); + players[i].tally.points = READINT32(save->p); + players[i].tally.pointLimit = READINT32(save->p); + players[i].tally.powerStones = READUINT8(save->p); + for (q = 0; q < TALLY_WINDOW_SIZE; q++) + players[i].tally.bonuses[q] = READINT32(save->p); + players[i].tally.rank = READINT32(save->p); + + players[i].tally.state = READINT32(save->p); + players[i].tally.hudSlide = READINT32(save->p); + players[i].tally.delay = READINT32(save->p); + players[i].tally.transition = READINT32(save->p); + players[i].tally.transitionTime = READINT32(save->p); + players[i].tally.lines = READUINT8(save->p); + players[i].tally.lineCount = READUINT8(save->p); + for (q = 0; q < TALLY_WINDOW_SIZE; q++) + players[i].tally.displayStat[q] = READINT32(save->p); + for (q = 0; q < TALLY_WINDOW_SIZE; q++) + players[i].tally.displayBonus[q] = READINT32(save->p); + players[i].tally.tickSound = READUINT8(save->p); + players[i].tally.xtraBlink = READUINT8(save->p); + players[i].tally.showGrade = (boolean)READUINT8(save->p); + players[i].tally.done = (boolean)READUINT8(save->p); + } + //players[i].viewheight = P_GetPlayerViewHeight(players[i]); // scale cannot be factored in at this point } } diff --git a/src/p_spec.c b/src/p_spec.c index 46229159a..8563d19c5 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2037,7 +2037,7 @@ static void K_HandleLapIncrement(player_t *player) if (nump > 1 && K_IsPlayerLosing(player) == false) { - if (nump > 2 && player->position == 1) // 1st place in 1v1 uses thumbs up + if (inDuel == false && player->position == 1) // 1st place in 1v1 uses thumbs up { player->lapPoints += 2; } @@ -4482,7 +4482,7 @@ static void P_SetupSignObject(mobj_t *sign, mobj_t *pmo, boolean error) P_SetMobjState(sign, S_SIGN_POLE); sign->movefactor = sign->z; - sign->z += (768*sign->scale) * P_MobjFlip(sign); + sign->z += (576*sign->scale) * P_MobjFlip(sign); sign->movecount = 1; sign->extravalue1 = AngleFixed(sign->angle) >> FRACBITS; diff --git a/src/p_tick.c b/src/p_tick.c index 8637b6e8c..cc0659dba 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -942,7 +942,6 @@ void P_Ticker(boolean run) if (racecountdown > 1) racecountdown--; - const fixed_t darkdelta = FRACUNIT/50; const fixed_t maxdark = FRACUNIT/7; if (darktimer) // dark or darkening @@ -960,12 +959,35 @@ void P_Ticker(boolean run) darkness = 0; } - if (exitcountdown > 1) + if (exitcountdown >= 1) { - exitcountdown--; - if (server && exitcountdown == 1) + boolean run_exit_countdown = true; + for (i = 0; i < MAXPLAYERS; i++) { - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + if (playeringame[i] == false) + { + continue; + } + + player_t *player = &players[i]; + if (K_PlayerTallyActive(player) == true && player->tally.done == false) + { + run_exit_countdown = false; + break; + } + } + + if (run_exit_countdown == true) + { + if (exitcountdown > 1) + { + exitcountdown--; + } + + if (server && exitcountdown == 1) + { + SendNetXCmd(XD_EXITLEVEL, NULL, 0); + } } } diff --git a/src/p_user.c b/src/p_user.c index 3adf41c4b..54e05f0f1 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -65,6 +65,7 @@ #include "g_party.h" #include "k_profiles.h" #include "music.h" +#include "k_tally.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" @@ -1278,10 +1279,10 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) K_PlayerLoseLife(player); } - if (P_IsLocalPlayer(player) && !specialout) + if (P_IsLocalPlayer(player) && !specialout && musiccountdown == 0) { Music_StopAll(); - musiccountdown = MUSICCOUNTDOWNMAX; + musiccountdown = MUSIC_COUNTDOWN_MAX; } player->exiting = 1; @@ -1307,7 +1308,6 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) if (extra > oldExtra) { - S_StartSound(NULL, sfx_cdfm73); player->xtralife = (extra - oldExtra); } } @@ -1317,26 +1317,6 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) { K_UpdateAllPlayerPositions(); - if (cv_kartvoices.value && !(gametyperules & GTR_SPECIALSTART)) - { - if (P_IsDisplayPlayer(player)) - { - sfxenum_t sfx_id; - if (losing) - sfx_id = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_klose].skinsound]; - else - sfx_id = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_kwin].skinsound]; - S_StartSound(NULL, sfx_id); - } - else - { - if (losing) - S_StartSound(player->mo, sfx_klose); - else - S_StartSound(player->mo, sfx_kwin); - } - } - if (P_CheckRacers() && !exitcountdown) { G_BeginLevelExit(); @@ -1348,6 +1328,8 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) } } + K_InitPlayerTally(player); + if (demo.playback == false) { if (modeattacking == true) @@ -1373,8 +1355,6 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) if (player == &players[consoleplayer]) demo.savebutton = leveltime; } - - player->karthud[khud_cardanimation] = 0; // srb2kart: reset battle animation } // @@ -1384,7 +1364,6 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) void P_DoAllPlayersExit(pflags_t flags, boolean trygivelife) { UINT8 i; - boolean givenlife = false; const boolean dofinishsound = (musiccountdown == 0); for (i = 0; i < MAXPLAYERS; i++) @@ -1406,7 +1385,6 @@ void P_DoAllPlayersExit(pflags_t flags, boolean trygivelife) } P_GivePlayerLives(&players[i], 1); - givenlife = true; } if (!dofinishsound) @@ -1429,12 +1407,6 @@ void P_DoAllPlayersExit(pflags_t flags, boolean trygivelife) // Everyone finish sound S_StartSound(NULL, sfx_s3k6a); } - - if (givenlife) - { - // Life sound - S_StartSound(NULL, sfx_cdfm73); - } } // @@ -3744,16 +3716,18 @@ void P_DoTimeOver(player_t *player) P_DamageMobj(player->mo, NULL, NULL, 1, DMG_TIMEOVER); } - if (P_IsLocalPlayer(player)) + if (P_IsLocalPlayer(player) && musiccountdown == 0) { Music_StopAll(); - musiccountdown = MUSICCOUNTDOWNMAX; + musiccountdown = MUSIC_COUNTDOWN_MAX; } if (!exitcountdown) { G_BeginLevelExit(); } + + K_InitPlayerTally(player); } // SRB2Kart: These are useful functions, but we aren't using them yet. @@ -3984,6 +3958,27 @@ void P_PlayerThink(player_t *player) player->playerstate = PST_DEAD; } + if (G_GametypeUsesLives() == true && player->lives <= 0 && player->playerstate != PST_DEAD) + { + player->mo->flags &= ~(MF_SOLID|MF_SHOOTABLE); // does not block + P_UnsetThingPosition(player->mo); + player->mo->flags |= MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; + P_SetThingPosition(player->mo); + player->mo->standingslope = NULL; + player->mo->terrain = NULL; + player->mo->pmomz = 0; + + player->playerstate = PST_DEAD; + + // respawn from where you died + player->respawn.pointx = player->mo->x; + player->respawn.pointy = player->mo->y; + player->respawn.pointz = player->mo->z; + + player->pflags |= PF_LOSTLIFE|PF_ELIMINATED|PF_NOCONTEST; + K_InitPlayerTally(player); + } + // Erasing invalid player pointers { #define PlayerPointerErase(field) \ diff --git a/src/sounds.c b/src/sounds.c index dcff205cf..18bfa26d9 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1208,6 +1208,8 @@ sfxinfo_t S_sfx[NUMSFX] = {"dashr", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Regular Dash Ring {"rainbr", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Rainbow Dash Ring + {"rank", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Rank slam + // SRB2Kart - Engine sounds // Engine class A {"krta00", false, 48, 65, -1, NULL, 0, -1, -1, LUMPERROR, ""}, diff --git a/src/sounds.h b/src/sounds.h index 176dfc541..b9b0616e0 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1277,6 +1277,8 @@ typedef enum sfx_dashr, sfx_rainbr, + sfx_rank, + // Next up: UNIQUE ENGINE SOUNDS! Hoooooo boy... // Engine class A - Low Speed, Low Weight sfx_krta00, diff --git a/src/typedef.h b/src/typedef.h index 735728ae9..fd84e99e7 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -221,6 +221,9 @@ TYPEDEF (waypoint_t); // k_rank.h TYPEDEF (gpRank_t); +// k_tally.h +TYPEDEF (level_tally_t); + // k_zvote.h TYPEDEF (midVote_t); TYPEDEF (midVoteGUI_t); diff --git a/src/v_draw.cpp b/src/v_draw.cpp index c6ab93c59..37cbd7568 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -91,7 +91,7 @@ Chain::Clipper::Clipper(const Chain& chain) FloatToFixed(chain.clipy1_), FloatToFixed(chain.clipx2_ - chain.clipx1_), FloatToFixed(chain.clipy2_ - chain.clipy1_), - 0 + chain.flags_ ); } @@ -121,6 +121,9 @@ int Draw::font_to_fontno(Font font) case Font::kPing: return PINGF_FONT; + + case Font::kTimer: + return TIMER_FONT; } return TINY_FONT; diff --git a/src/v_draw.hpp b/src/v_draw.hpp index 13ba47c29..aef98fc33 100644 --- a/src/v_draw.hpp +++ b/src/v_draw.hpp @@ -36,6 +36,7 @@ public: kFreeplay, kZVote, kPing, + kTimer, }; enum class Align @@ -99,6 +100,7 @@ public: public: float x() const { return x_; } float y() const { return y_; } + INT32 flags() const { return flags_; } // Methods add relative to the current state Chain& x(float x); @@ -188,6 +190,7 @@ public: float x() const { return chain_.x(); } float y() const { return chain_.y(); } + INT32 flags() const { return chain_.flags(); } #define METHOD(Name) \ template \ diff --git a/src/v_video.cpp b/src/v_video.cpp index e5bc68a3f..9e679607a 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -575,6 +575,7 @@ void V_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 du { const tic_t length = TICRATE/4; tic_t timer = lt_exitticker; + if (K_CheckBossIntro() == true || G_IsTitleCardAvailable() == false) { if (leveltime <= 16) @@ -583,6 +584,11 @@ void V_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 du timer = leveltime-16; } + if (stplyr->tally.hudSlide != 0) + { + timer = length - stplyr->tally.hudSlide; + } + if (timer < length) { if ((options & (V_SNAPTORIGHT|V_SNAPTOLEFT|V_SPLITSCREEN)) != 0) @@ -1988,10 +1994,10 @@ INT32 V_CenteredTitleCardStringOffset(const char *str, boolean p4) return Internal_TitleCardStringOffset(str, p4); } -// V_DrawTitleCardScreen. +// V_DrawTitleCardStringFixed. // see v_video.h's prototype for more information. // -void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold, boolean p4) +void V_DrawTitleCardStringFixed(fixed_t x, fixed_t y, fixed_t scale, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold, boolean p4) { int bg_font = GTOL_FONT; int fg_font = GTFN_FONT; @@ -2018,11 +2024,12 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole patch_t *pp; patch_t *ol; - x -= 2; // Account for patch width... + x -= 2 * scale; // Account for patch width... if (flags & V_SNAPTORIGHT) - x -= V_TitleCardStringWidth(str, p4); - + { + x -= V_TitleCardStringWidth(str, p4) * scale; + } for (;;ch++, i++) { @@ -2038,7 +2045,7 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole if (*ch == '\n') { xoffs = x; - yoffs += p4 ? 18 : 32; + yoffs += (p4 ? 18 : 32) * scale; continue; } @@ -2051,7 +2058,7 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole // check if character exists, if not, it's a space. if (c < 0 || c >= LT_FONTSIZE || !fontv[fg_font].font[(INT32)c]) { - xoffs += p4 ? 5 : 10; + xoffs += (p4 ? 5 : 10) * scale; continue; } @@ -2105,11 +2112,11 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole if (scalex && ol && pp) { //CONS_Printf("%d\n", (INT32)c); - V_DrawStretchyFixedPatch((x + xoffs)*FRACUNIT + offs, (y+yoffs)*FRACUNIT, abs(scalex), FRACUNIT, flags|flipflag, ol, NULL); - V_DrawStretchyFixedPatch((x + xoffs)*FRACUNIT + offs, (y+yoffs)*FRACUNIT, abs(scalex), FRACUNIT, flags|flipflag, pp, NULL); + V_DrawStretchyFixedPatch((x + xoffs) + offs, (y+yoffs), FixedMul(abs(scalex), scale), scale, flags|flipflag, ol, NULL); + V_DrawStretchyFixedPatch((x + xoffs) + offs, (y+yoffs), FixedMul(abs(scalex), scale), scale, flags|flipflag, pp, NULL); } - xoffs += pp->width - (p4 ? 3 : 5); + xoffs += (pp->width - (p4 ? 3 : 5)) * scale; } } diff --git a/src/v_video.h b/src/v_video.h index 5c7ef5358..03c06c71f 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -347,8 +347,10 @@ void V_DrawRightAlignedThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, con // threshold: when the letters start disappearing (leave to 0 to disable) (both are INT32 in case you supply negative values...) // NOTE: This function ignores most conventional string flags (V_RETURN8, V_FORCEUPPERCASE ...) // NOTE: This font only works with uppercase letters. -void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold, boolean p4); - +void V_DrawTitleCardStringFixed(fixed_t x, fixed_t y, fixed_t scale, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold, boolean p4); +#define V_DrawTitleCardString( x,y,str,flags,bossmode,timer,threshold,p4 ) \ + V_DrawTitleCardStringFixed ((x)< 1) + { + V_SetClipRect( + 0, + 0, + v_width << FRACBITS, + BASEVIDHEIGHT << FRACBITS, + V_SPLITSCREEN + ); + } + + // Header bar + patch_t *rtpbr = W_CachePatchName((small ? "R_RTPB4" : "R_RTPBR"), PU_PATCH); + V_DrawFixedPatch((20 * frac) + x, (24 * frac) + y, FRACUNIT, small_flag, rtpbr, NULL); + + fixed_t headerx, headery, headerwidth = 0; + + if (gotthrough) + { + headerx = (51 * frac); + headery = (7 * frac); + } + else + { + headerwidth = V_TitleCardStringWidth(headerstring, small); + + headerx = (v_width - headerwidth) * (FRACUNIT / 2); + headery = 17 * frac; + } + + // Draw round numbers + if (roundnum > 0 && roundnum <= 10) + { + patch_t *roundpatch = + W_CachePatchName( + va("TT_RN%s%d", (small ? "S" : "D"), roundnum), + PU_PATCH + ); + + fixed_t roundx = (v_width * 3 * FRACUNIT) / 4; + + if (headerwidth != 0) + { + const fixed_t roundoffset = (8 * frac) + (roundpatch->width * FRACUNIT); + + roundx = headerx + roundoffset; + headerx -= roundoffset/2; + } + + V_DrawFixedPatch(x + roundx, (39 * frac) + y, FRACUNIT, small_flag, roundpatch, NULL); + } + + V_DrawTitleCardStringFixed(x + headerx, y + headery, FRACUNIT, headerstring, small_flag, false, 0, 0, small); + + if (gotthrough) + { + // GOT THROUGH ROUND + patch_t *gthro = W_CachePatchName((small ? "R_GTHR4" : "R_GTHRO"), PU_PATCH); + V_DrawFixedPatch((50 * frac) + x, (42 * frac) + y, FRACUNIT, small_flag, gthro, NULL); + } + + V_ClearClipRect(); +} + // // Y_IntermissionDrawer // @@ -1488,54 +1558,7 @@ void Y_IntermissionDrawer(void) } // Draw the header bar - { - // Header bar - patch_t *rtpbr = W_CachePatchName("R_RTPBR", PU_PATCH); - V_DrawMappedPatch(20 + x, 24, 0, rtpbr, NULL); - - INT32 headerx, headery, headerwidth = 0; - - if (data.gotthrough) - { - // GOT THROUGH ROUND - patch_t *gthro = W_CachePatchName("R_GTHRO", PU_PATCH); - V_DrawMappedPatch(50 + x, 42, 0, gthro, NULL); - - headerx = 51; - headery = 7; - } - else - { - headerwidth = V_TitleCardStringWidth(data.headerstring, false); - - headerx = (BASEVIDWIDTH - headerwidth)/2; - headery = 17; - } - - // Draw round numbers - if (data.roundnum > 0 && data.roundnum <= 10) - { - patch_t *roundpatch = - W_CachePatchName( - va("TT_RND%d", data.roundnum), - PU_PATCH - ); - - INT32 roundx = 240; - - if (headerwidth != 0) - { - const INT32 roundoffset = 8 + SHORT(roundpatch->width); - - roundx = headerx + roundoffset; - headerx -= roundoffset/2; - } - - V_DrawMappedPatch(x + roundx, 39, 0, roundpatch, NULL); - } - - V_DrawTitleCardString(x + headerx, headery, data.headerstring, 0, false, 0, 0, false); - } + Y_DrawIntermissionHeader(x << FRACBITS, 0, data.gotthrough, data.headerstring, data.roundnum, false); // Returns early if there's no players to draw Y_PlayerStandingsDrawer(&data, x); diff --git a/src/y_inter.h b/src/y_inter.h index 577ac78d1..c23909cf6 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -45,6 +45,7 @@ typedef struct INT32 linemeter; // For GP only } y_data_t; +void Y_DrawIntermissionHeader(INT32 x, INT32 y, boolean gotthrough, const char *headerstring, UINT8 roundnum, boolean small); void Y_IntermissionDrawer(void); void Y_Ticker(void);