// SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 2004-2016 by Sonic Team Junior. // // 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 y_inter.c /// \brief Tally screens, or "Intermissions" as they were formally called in Doom #include "doomdef.h" #include "doomstat.h" #include "d_main.h" #include "f_finale.h" #include "g_game.h" #include "hu_stuff.h" #include "i_net.h" #include "i_video.h" #include "p_tick.h" #include "r_defs.h" #include "r_things.h" #include "s_sound.h" #include "st_stuff.h" #include "v_video.h" #include "w_wad.h" #include "y_inter.h" #include "z_zone.h" #include "m_menu.h" #include "m_misc.h" #include "i_system.h" #include "p_setup.h" #include "r_local.h" #include "p_local.h" #include "m_cond.h" // condition sets #include "m_random.h" // P_RandomKey #include "g_input.h" // PLAYER1INPUTDOWN #ifdef HWRENDER #include "hardware/hw_main.h" #endif #ifdef PC_DOS #include // for snprintf int snprintf(char *str, size_t n, const char *fmt, ...); //int vsnprintf(char *str, size_t n, const char *fmt, va_list ap); #endif typedef struct { char patch[9]; INT32 points; UINT8 display; } y_bonus_t; typedef union { struct { char passed1[21]; // KNUCKLES GOT / CRAWLA HONCHO char passed2[16]; // THROUGH THE ACT / PASSED THE ACT INT32 passedx1; INT32 passedx2; y_bonus_t bonuses[4]; patch_t *bonuspatches[4]; SINT8 gotperfbonus; // Used for visitation flags. UINT32 score, total; // fake score, total UINT32 tics; // time patch_t *ttlnum; // act number being displayed patch_t *ptotal; // TOTAL UINT8 gotlife; // Number of extra lives obtained } coop; struct { char passed1[29]; // KNUCKLES GOT / CRAWLA HONCHO char passed2[17]; // A CHAOS EMERALD / GOT THEM ALL! char passed3[15]; // CAN NOW BECOME char passed4[SKINNAMESIZE+7]; // SUPER CRAWLA HONCHO INT32 passedx1; INT32 passedx2; INT32 passedx3; INT32 passedx4; y_bonus_t bonus; patch_t *bonuspatch; patch_t *pscore; // SCORE UINT32 score; // fake score // Continues UINT8 continues; patch_t *pcontinues; INT32 *playerchar; // Continue HUD UINT8 *playercolor; UINT8 gotlife; // Number of extra lives obtained } spec; struct { UINT32 scores[MAXPLAYERS]; // Winner's score UINT8 *color[MAXPLAYERS]; // Winner's color # boolean spectator[MAXPLAYERS]; // Spectator list INT32 *character[MAXPLAYERS]; // Winner's character # INT32 num[MAXPLAYERS]; // Winner's player # char *name[MAXPLAYERS]; // Winner's name patch_t *result; // RESULT patch_t *blueflag; patch_t *redflag; // int_ctf uses this struct too. INT32 numplayers; // Number of players being displayed char levelstring[40]; // holds levelnames up to 32 characters // SRB2kart int increase[MAXPLAYERS]; //how much did the score increase by? int time[MAXPLAYERS]; //Tournament Time } match; struct { UINT8 *color[MAXPLAYERS]; // Winner's color # INT32 *character[MAXPLAYERS]; // Winner's character # INT32 num[MAXPLAYERS]; // Winner's player # char name[MAXPLAYERS][9]; // Winner's name UINT32 times[MAXPLAYERS]; UINT32 rings[MAXPLAYERS]; UINT32 maxrings[MAXPLAYERS]; UINT32 monitors[MAXPLAYERS]; UINT32 scores[MAXPLAYERS]; UINT32 points[MAXPLAYERS]; INT32 numplayers; // Number of players being displayed char levelstring[40]; // holds levelnames up to 32 characters } competition; } y_data; static y_data data; // graphics static patch_t *bgpatch = NULL; // INTERSCR static patch_t *widebgpatch = NULL; // INTERSCW static patch_t *bgtile = NULL; // SPECTILE/SRB2BACK static patch_t *interpic = NULL; // custom picture defined in map header static boolean usetile; boolean usebuffer = false; static boolean useinterpic; static INT32 timer; static INT32 intertic; static INT32 endtic = -1; intertype_t intertype = int_none; //static void Y_AwardCoopBonuses(void); //static void Y_AwardSpecialStageBonus(void); static void Y_CalculateTournamentPoints(void); // SRB2kart static void Y_CalculateCompetitionWinners(void); //static void Y_CalculateTimeRaceWinners(void); static void Y_CalculateMatchWinners(void); static void Y_FollowIntermission(void); static void Y_UnloadData(void); // SRB2Kart: voting stuff typedef struct { char str[40]; patch_t *pic; } y_votelvlinfo; typedef struct { SINT8 selection; UINT8 delay; UINT8 ranim; UINT8 rtics; UINT8 roffset; UINT8 rsynctime; UINT8 rendoff; } y_voteclient; static y_votelvlinfo levelinfo[4]; static y_voteclient voteclient; static INT32 votetic; static INT32 voteendtic = -1; static patch_t *cursor = NULL; static patch_t *randomlvl = NULL; static void Y_UnloadVoteData(void); // Stuff copy+pasted from st_stuff.c static INT32 SCX(INT32 x) { return FixedInt(FixedMul(x<actnum) V_DrawScaledPatch(244, 57, 0, data.coop.ttlnum); //if (gottimebonus && endtic != -1) // V_DrawCenteredString(BASEVIDWIDTH/2, 172, V_YELLOWMAP, "TIME BONUS UNLOCKED!"); } else if (intertype == int_race) { INT32 j = 0; INT32 x = 4; INT32 y = 48; char name[MAXPLAYERNAME+1]; boolean completed[MAXPLAYERS]; memset(completed, 0, sizeof (completed)); // draw the level name V_DrawCenteredString(BASEVIDWIDTH/2, 20, 0, data.match.levelstring); V_DrawFill(4, 42, 312, 1, 0); if (data.match.numplayers > 8) { V_DrawFill(160, 32, 1, 152, 0); V_DrawCenteredString(x+6+(BASEVIDWIDTH/2), 32, V_YELLOWMAP, "#"); V_DrawString(x+36+(BASEVIDWIDTH/2), 32, V_YELLOWMAP, "NAME"); V_DrawRightAlignedString(x+110, 32, V_YELLOWMAP, "TIME"); V_DrawRightAlignedString(x+152, 32, V_YELLOWMAP, "SCORE"); } V_DrawCenteredString(x+6, 32, V_YELLOWMAP, "#"); V_DrawString(x+36, 32, V_YELLOWMAP, "NAME"); if (data.match.numplayers > 8) { V_DrawRightAlignedString(x+(BASEVIDWIDTH/2)+110, 32, V_YELLOWMAP, "TIME"); } else { V_DrawRightAlignedString(x+(BASEVIDWIDTH/2)+62, 32, V_YELLOWMAP, "TIME"); } V_DrawRightAlignedString(x+(BASEVIDWIDTH/2)+152, 32, V_YELLOWMAP, "SCORE"); for (i = 0; i < data.match.numplayers; i++) { char strtime[10]; if (data.match.spectator[i]) continue; V_DrawCenteredString(x+6, y, 0, va("%d", j+1)); j++; //We skip spectators, but not their number. if (playeringame[data.match.num[i]]) { if (data.match.color[i] == 0) V_DrawSmallScaledPatch(x+16, y-4, 0,faceprefix[*data.match.character[i]]); else { UINT8 *colormap = R_GetTranslationColormap(*data.match.character[i], *data.match.color[i], GTC_CACHE); V_DrawSmallMappedPatch(x+16, y-4, 0,faceprefix[*data.match.character[i]], colormap); } if (data.match.numplayers > 8) { strlcpy(name, data.match.name[i], 6); } else STRBUFCPY(name, data.match.name[i]); V_DrawString(x+36, y, V_ALLOWLOWERCASE, name); snprintf(strtime, sizeof strtime, "%d", data.match.scores[i]-data.match.increase[i]); if (data.match.numplayers > 8) { V_DrawRightAlignedString(x+152, y, V_YELLOWMAP, strtime); } else { V_DrawRightAlignedString(x+152+BASEVIDWIDTH/2, y, V_YELLOWMAP, strtime); } if (data.match.increase[i] > 9) snprintf(strtime, sizeof strtime, "(+%02d)", data.match.increase[i]); else snprintf(strtime, sizeof strtime, "(+ %d)", data.match.increase[i]); if (data.match.numplayers <= 8) // Only draw this with less than 8 players, otherwise we won't be able to fit the times in { V_DrawString(x+84+BASEVIDWIDTH/2, y, 0, strtime); } snprintf(strtime, sizeof strtime, "%i:%02i.%02i", G_TicsToMinutes(data.match.time[i], true), G_TicsToSeconds(data.match.time[i]), G_TicsToCentiseconds(data.match.time[i])); strtime[sizeof strtime - 1] = '\0'; if (data.match.numplayers > 8) { V_DrawRightAlignedString(x+134, y, 0, strtime); } else { V_DrawRightAlignedString(x+80+BASEVIDWIDTH/2, y, 0, strtime); } completed[i] = true; } y += 16; if (y > 160) { y = 48; x += BASEVIDWIDTH/2; } } } else if (intertype == int_match) { INT32 j = 0; INT32 x = 4; INT32 y = 48; char name[MAXPLAYERNAME+1]; char strtime[10]; // draw the header V_DrawScaledPatch(112, 2, 0, data.match.result); // draw the level name V_DrawCenteredString(BASEVIDWIDTH/2, 20, 0, data.match.levelstring); V_DrawFill(4, 42, 312, 1, 0); if (data.match.numplayers > 9) { V_DrawFill(160, 32, 1, 152, 0); if (intertype == int_race) V_DrawRightAlignedString(x+152, 32, V_YELLOWMAP, "TIME"); else V_DrawRightAlignedString(x+152, 32, V_YELLOWMAP, "SCORE"); V_DrawCenteredString(x+(BASEVIDWIDTH/2)+6, 32, V_YELLOWMAP, "#"); V_DrawString(x+(BASEVIDWIDTH/2)+36, 32, V_YELLOWMAP, "NAME"); } V_DrawCenteredString(x+6, 32, V_YELLOWMAP, "#"); V_DrawString(x+36, 32, V_YELLOWMAP, "NAME"); if (intertype == int_race) V_DrawRightAlignedString(x+(BASEVIDWIDTH/2)+152, 32, V_YELLOWMAP, "TIME"); else V_DrawRightAlignedString(x+(BASEVIDWIDTH/2)+152, 32, V_YELLOWMAP, "SCORE"); for (i = 0; i < data.match.numplayers; i++) { if (data.match.spectator[i]) continue; //Ignore spectators. V_DrawCenteredString(x+6, y, 0, va("%d", j+1)); j++; //We skip spectators, but not their number. if (playeringame[data.match.num[i]]) { // Draw the back sprite, it looks ugly if we don't V_DrawSmallScaledPatch(x+16, y-4, 0, livesback); if (data.match.color[i] == 0) V_DrawSmallScaledPatch(x+16, y-4, 0,faceprefix[*data.match.character[i]]); else { UINT8 *colormap = R_GetTranslationColormap(*data.match.character[i], *data.match.color[i], GTC_CACHE); V_DrawSmallMappedPatch(x+16, y-4, 0,faceprefix[*data.match.character[i]], colormap); } if (data.match.numplayers > 9) { if (intertype == int_race) strlcpy(name, data.match.name[i], 8); else strlcpy(name, data.match.name[i], 9); } else STRBUFCPY(name, data.match.name[i]); V_DrawString(x+36, y, V_ALLOWLOWERCASE, name); if (data.match.numplayers > 9) { if (intertype == int_match) V_DrawRightAlignedString(x+152, y, 0, va("%i", data.match.scores[i])); else if (intertype == int_race) { if (players[data.match.num[i]].pflags & PF_TIMEOVER) snprintf(strtime, sizeof strtime, "DNF"); else snprintf(strtime, sizeof strtime, "%i:%02i.%02i", G_TicsToMinutes(data.match.scores[i], true), G_TicsToSeconds(data.match.scores[i]), G_TicsToCentiseconds(data.match.scores[i])); strtime[sizeof strtime - 1] = '\0'; V_DrawRightAlignedString(x+152, y, 0, strtime); } } else { if (intertype == int_match) V_DrawRightAlignedString(x+152+BASEVIDWIDTH/2, y, 0, va("%u", data.match.scores[i])); else if (intertype == int_race) { if (players[data.match.num[i]].pflags & PF_TIMEOVER) snprintf(strtime, sizeof strtime, "DNF"); else snprintf(strtime, sizeof strtime, "%i:%02i.%02i", G_TicsToMinutes(data.match.scores[i], true), G_TicsToSeconds(data.match.scores[i]), G_TicsToCentiseconds(data.match.scores[i])); strtime[sizeof strtime - 1] = '\0'; V_DrawRightAlignedString(x+152+BASEVIDWIDTH/2, y, 0, strtime); } } } y += 16; if (y > 176) { y = 48; x += BASEVIDWIDTH/2; } } } else if (intertype == int_ctf || intertype == int_teammatch) { INT32 x = 4, y = 0; INT32 redplayers = 0, blueplayers = 0; char name[MAXPLAYERNAME+1]; // Show the team flags and the team score at the top instead of "RESULTS" V_DrawSmallScaledPatch(128 - SHORT(data.match.blueflag->width)/4, 2, 0, data.match.blueflag); V_DrawCenteredString(128, 16, 0, va("%u", bluescore)); V_DrawSmallScaledPatch(192 - SHORT(data.match.redflag->width)/4, 2, 0, data.match.redflag); V_DrawCenteredString(192, 16, 0, va("%u", redscore)); // draw the level name V_DrawCenteredString(BASEVIDWIDTH/2, 24, 0, data.match.levelstring); V_DrawFill(4, 42, 312, 1, 0); //vert. line V_DrawFill(160, 32, 1, 152, 0); //strings at the top of the list V_DrawCenteredString(x+6, 32, V_YELLOWMAP, "#"); V_DrawCenteredString(x+(BASEVIDWIDTH/2)+6, 32, V_YELLOWMAP, "#"); V_DrawString(x+36, 32, V_YELLOWMAP, "NAME"); V_DrawString(x+(BASEVIDWIDTH/2)+36, 32, V_YELLOWMAP, "NAME"); V_DrawRightAlignedString(x+152, 32, V_YELLOWMAP, "SCORE"); V_DrawRightAlignedString(x+(BASEVIDWIDTH/2)+152, 32, V_YELLOWMAP, "SCORE"); for (i = 0; i < data.match.numplayers; i++) { if (playeringame[data.match.num[i]] && !(data.match.spectator[i])) { UINT8 *colormap = R_GetTranslationColormap(*data.match.character[i], *data.match.color[i], GTC_CACHE); if (*data.match.color[i] == SKINCOLOR_RED) //red { if (redplayers++ > 9) continue; x = 4 + (BASEVIDWIDTH/2); y = (redplayers * 16) + 32; V_DrawCenteredString(x+6, y, 0, va("%d", redplayers)); } else if (*data.match.color[i] == SKINCOLOR_BLUE) //blue { if (blueplayers++ > 9) continue; x = 4; y = (blueplayers * 16) + 32; V_DrawCenteredString(x+6, y, 0, va("%d", blueplayers)); } else continue; // Draw the back sprite, it looks ugly if we don't V_DrawSmallScaledPatch(x+16, y-4, 0, livesback); //color is ALWAYS going to be 6/7 here, no need to check if it's nonzero. V_DrawSmallMappedPatch(x+16, y-4, 0,faceprefix[*data.match.character[i]], colormap); strlcpy(name, data.match.name[i], 9); V_DrawString(x+36, y, V_ALLOWLOWERCASE, name); V_DrawRightAlignedString(x+152, y, 0, va("%u", data.match.scores[i])); } } } else if (intertype == int_classicrace) { INT32 x = 4; INT32 y = 48; UINT32 ptime, pring, pmaxring, pmonitor, pscore; char sstrtime[10]; // draw the level name V_DrawCenteredString(BASEVIDWIDTH/2, 8, 0, data.competition.levelstring); V_DrawFill(4, 42, 312, 1, 0); V_DrawCenteredString(x+6, 32, V_YELLOWMAP, "#"); V_DrawString(x+36, 32, V_YELLOWMAP, "NAME"); // Time V_DrawRightAlignedString(x+160, 32, V_YELLOWMAP, "TIME"); // Rings V_DrawThinString(x+168, 31, V_YELLOWMAP, "RING"); // Total rings V_DrawThinString(x+191, 22, V_YELLOWMAP, "TOTAL"); V_DrawThinString(x+196, 31, V_YELLOWMAP, "RING"); // Monitors V_DrawThinString(x+223, 22, V_YELLOWMAP, "ITEM"); V_DrawThinString(x+229, 31, V_YELLOWMAP, "BOX"); // Score V_DrawRightAlignedString(x+288, 32, V_YELLOWMAP, "SCORE"); // Points V_DrawRightAlignedString(x+312, 32, V_YELLOWMAP, "PT"); for (i = 0; i < data.competition.numplayers; i++) { ptime = (data.competition.times[i] & ~0x80000000); pring = (data.competition.rings[i] & ~0x80000000); pmaxring = (data.competition.maxrings[i] & ~0x80000000); pmonitor = (data.competition.monitors[i] & ~0x80000000); pscore = (data.competition.scores[i] & ~0x80000000); V_DrawCenteredString(x+6, y, 0, va("%d", i+1)); if (playeringame[data.competition.num[i]]) { // Draw the back sprite, it looks ugly if we don't V_DrawSmallScaledPatch(x+16, y-4, 0, livesback); if (data.competition.color[i] == 0) V_DrawSmallScaledPatch(x+16, y-4, 0,faceprefix[*data.competition.character[i]]); else { UINT8 *colormap = R_GetTranslationColormap(*data.competition.character[i], *data.competition.color[i], GTC_CACHE); V_DrawSmallMappedPatch(x+16, y-4, 0,faceprefix[*data.competition.character[i]], colormap); } // already constrained to 8 characters V_DrawString(x+36, y, V_ALLOWLOWERCASE, data.competition.name[i]); if (players[data.competition.num[i]].pflags & PF_TIMEOVER) snprintf(sstrtime, sizeof sstrtime, "Time Over"); else if (players[data.competition.num[i]].lives <= 0) snprintf(sstrtime, sizeof sstrtime, "Game Over"); else snprintf(sstrtime, sizeof sstrtime, "%i:%02i.%02i", G_TicsToMinutes(ptime, true), G_TicsToSeconds(ptime), G_TicsToCentiseconds(ptime)); sstrtime[sizeof sstrtime - 1] = '\0'; // Time V_DrawRightAlignedThinString(x+160, y-1, ((data.competition.times[i] & 0x80000000) ? V_YELLOWMAP : 0), sstrtime); // Rings V_DrawRightAlignedThinString(x+188, y-1, V_MONOSPACE|((data.competition.rings[i] & 0x80000000) ? V_YELLOWMAP : 0), va("%u", pring)); // Total rings V_DrawRightAlignedThinString(x+216, y-1, V_MONOSPACE|((data.competition.maxrings[i] & 0x80000000) ? V_YELLOWMAP : 0), va("%u", pmaxring)); // Monitors V_DrawRightAlignedThinString(x+244, y-1, V_MONOSPACE|((data.competition.monitors[i] & 0x80000000) ? V_YELLOWMAP : 0), va("%u", pmonitor)); // Score V_DrawRightAlignedThinString(x+288, y-1, V_MONOSPACE|((data.competition.scores[i] & 0x80000000) ? V_YELLOWMAP : 0), va("%u", pscore)); // Final Points V_DrawRightAlignedString(x+312, y, V_YELLOWMAP, va("%d", data.competition.points[i])); } y += 16; if (y > 176) break; } } if (timer) V_DrawCenteredString(BASEVIDWIDTH/2, 188, V_YELLOWMAP, va("start in %d seconds", timer/TICRATE)); // Make it obvious that scrambling is happening next round. if (cv_scrambleonchange.value && cv_teamscramble.value && (intertic/TICRATE % 2 == 0)) V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, V_YELLOWMAP, M_GetText("Teams will be scrambled next round!")); } // // Y_Ticker // // Manages fake score tally for single player end of act, and decides when intermission is over. // void Y_Ticker(void) { if (intertype == int_none) return; // Check for pause or menu up in single player if (paused || P_AutoPause()) return; intertic++; // Team scramble code for team match and CTF. // Don't do this if we're going to automatically scramble teams next round. if (G_GametypeHasTeams() && cv_teamscramble.value && !cv_scrambleonchange.value && server) { // If we run out of time in intermission, the beauty is that // the P_Ticker() team scramble code will pick it up. if ((intertic % (TICRATE/7)) == 0) P_DoTeamscrambling(); } // multiplayer uses timer (based on cv_inttime) if (timer) { if (!--timer) { Y_EndIntermission(); Y_FollowIntermission(); return; } } // single player is hardcoded to go away after awhile else if (intertic == endtic) { Y_EndIntermission(); Y_FollowIntermission(); return; } if (endtic != -1) return; // tally is done /* // SRB2kart if (intertype == int_coop) // coop or single player, normal level { INT32 i; UINT32 oldscore = data.coop.score; boolean skip = false; boolean anybonuses = false; if (!intertic) // first time only S_ChangeMusicInternal("lclear", false); // don't loop it if (intertic < TICRATE) // one second pause before tally begins return; for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i] && (players[i].cmd.buttons & BT_BRAKE || players[i].cmd.buttons & BT_ACCELERATE)) skip = true; // bonuses count down by 222 each tic for (i = 0; i < 4; ++i) { if (!data.coop.bonuses[i].points) continue; data.coop.bonuses[i].points -= 222; data.coop.total += 222; data.coop.score += 222; if (data.coop.bonuses[i].points < 0 || skip == true) // too far? { data.coop.score += data.coop.bonuses[i].points; data.coop.total += data.coop.bonuses[i].points; data.coop.bonuses[i].points = 0; } if (data.coop.bonuses[i].points > 0) anybonuses = true; } if (!anybonuses) { endtic = intertic + 3*TICRATE; // 3 second pause after end of tally S_StartSound(NULL, sfx_chchng); // cha-ching! // Update when done with tally if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback) { if (M_UpdateUnlockablesAndExtraEmblems()) S_StartSound(NULL, sfx_ncitem); G_SaveGameData(); } } else if (!(intertic & 1)) S_StartSound(NULL, sfx_ptally); // tally sound effect if (data.coop.gotlife > 0 && (skip == true || data.coop.score % 50000 < oldscore % 50000)) // just passed a 50000 point mark { // lives are already added since tally is fake, but play the music P_PlayLivesJingle(NULL); --data.coop.gotlife; } } else if (intertype == int_spec) // coop or single player, special stage { INT32 i; UINT32 oldscore = data.spec.score; boolean skip = false; static INT32 tallydonetic = 0; if (!intertic) // first time only { S_ChangeMusicInternal("lclear", false); // don't loop it tallydonetic = 0; } if (intertic < TICRATE) // one second pause before tally begins return; for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i] && (players[i].cmd.buttons & BT_BRAKE || players[i].cmd.buttons & BT_ACCELERATE)) skip = true; if (tallydonetic != 0) { if (intertic > tallydonetic) { endtic = intertic + 4*TICRATE; // 4 second pause after end of tally S_StartSound(NULL, sfx_flgcap); // cha-ching! } return; } // ring bonus counts down by 222 each tic data.spec.bonus.points -= 222; data.spec.score += 222; if (data.spec.bonus.points < 0 || skip == true) // went too far { data.spec.score += data.spec.bonus.points; data.spec.bonus.points = 0; } if (!data.spec.bonus.points) { if (data.spec.continues & 0x80) // don't set endtic yet! tallydonetic = intertic + (3*TICRATE)/2; else // okay we're good. endtic = intertic + 4*TICRATE; // 4 second pause after end of tally S_StartSound(NULL, sfx_chchng); // cha-ching! // Update when done with tally if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback) { if (M_UpdateUnlockablesAndExtraEmblems()) S_StartSound(NULL, sfx_ncitem); G_SaveGameData(); } } else if (!(intertic & 1)) S_StartSound(NULL, sfx_ptally); // tally sound effect if (data.spec.gotlife > 0 && (skip == true || data.spec.score % 50000 < oldscore % 50000)) // just passed a 50000 point mark { // lives are already added since tally is fake, but play the music P_PlayLivesJingle(NULL); --data.spec.gotlife; } } */ if (intertype == int_timeattack) { if (!intertic) endtic = intertic + 10*TICRATE; // 10 second pause after end of tally } else if (intertype == int_race) { INT32 q=0,r=0; /* // SRB2kart - removed temporarily. if (!intertic) { if (!((music_playing == "karwin") // Mario Kart Win || (music_playing == "karok") // Mario Kart Ok || (music_playing == "karlos"))) // Mario Kart Lose S_ChangeMusicInternal("racent", true); // Backup Plan }*/ if (intertic < TICRATE || intertic % 8) return; for (q = 0; q < data.match.numplayers; q++) { if (data.match.increase[q]) { data.match.increase[q]--; r++; } } if (r) S_StartSound(NULL, sfx_menu1); else if (modeattacking) endtic = intertic + 10*TICRATE; // 10 second pause after end of tally else endtic = intertic + 3*TICRATE; // 3 second pause after end of tally } else if (intertype == int_match || intertype == int_ctf || intertype == int_teammatch) // match { if (!intertic) // first time only S_ChangeMusicInternal("racent", true); // loop it // If a player has left or joined, recalculate scores. if (data.match.numplayers != D_NumPlayers()) Y_CalculateMatchWinners(); } else if (intertype == int_race || intertype == int_classicrace) // race { if (!intertic) // first time only S_ChangeMusicInternal("racent", true); // loop it // Don't bother recalcing for race. It doesn't make as much sense. } } // // Y_UpdateRecordReplays // // Update replay files/data, etc. for Record Attack // See G_SetNightsRecords for NiGHTS Attack. // static void Y_UpdateRecordReplays(void) { const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1; char *gpath; char lastdemo[256], bestdemo[256]; UINT8 earnedEmblems; // Record new best time if (!mainrecords[gamemap-1]) G_AllocMainRecordData(gamemap-1); if ((mainrecords[gamemap-1]->time == 0) || (players[consoleplayer].realtime < mainrecords[gamemap-1]->time)) mainrecords[gamemap-1]->time = players[consoleplayer].realtime; if ((mainrecords[gamemap-1]->lap == 0) || (bestlap < mainrecords[gamemap-1]->lap)) mainrecords[gamemap-1]->lap = bestlap; // Save demo! bestdemo[255] = '\0'; lastdemo[255] = '\0'; G_SetDemoTime(players[consoleplayer].realtime, bestlap); G_CheckDemoStatus(); I_mkdir(va("%s"PATHSEP"replay", srb2home), 0755); I_mkdir(va("%s"PATHSEP"replay"PATHSEP"%s", srb2home, timeattackfolder), 0755); if ((gpath = malloc(glen)) == NULL) I_Error("Out of memory for replay filepath\n"); sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap)); snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, cv_chooseskin.string); if (FIL_FileExists(lastdemo)) { UINT8 *buf; size_t len = FIL_ReadFile(lastdemo, &buf); snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, cv_chooseskin.string); if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1) { // Better time, save this demo. if (FIL_FileExists(bestdemo)) remove(bestdemo); FIL_WriteFile(bestdemo, buf, len); CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo); } snprintf(bestdemo, 255, "%s-%s-lap-best.lmp", gpath, cv_chooseskin.string); if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & (1<<1)) { // Better lap time, save this demo. if (FIL_FileExists(bestdemo)) remove(bestdemo); FIL_WriteFile(bestdemo, buf, len); CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD LAP!"), M_GetText("Saved replay as"), bestdemo); } //CONS_Printf("%s '%s'\n", M_GetText("Saved replay as"), lastdemo); Z_Free(buf); } free(gpath); // Check emblems when level data is updated if ((earnedEmblems = M_CheckLevelEmblems())) CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for Record Attack records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); // Update timeattack menu's replay availability. CV_AddValue(&cv_nextmap, 1); CV_AddValue(&cv_nextmap, -1); } // // Y_StartIntermission // // Called by G_DoCompleted. Sets up data for intermission drawer/ticker. // void Y_StartIntermission(void) { INT32 i; intertic = -1; #ifdef PARANOIA if (endtic != -1) I_Error("endtic is dirty"); #endif if (!multiplayer) { timer = 0; /* if (G_IsSpecialStage(gamemap)) intertype = (maptol & TOL_NIGHTS) ? int_nightsspec : int_spec; else intertype = (maptol & TOL_NIGHTS) ? int_nights : int_coop; */ /* // srb2kart: time attack tally is UGLY rn if (modeattacking) intertype = int_timeattack; else */ intertype = int_race; } else { if (cv_inttime.value == 0 && gametype == GT_COOP) timer = 0; else { timer = cv_inttime.value*TICRATE; if (!timer) timer = 1; } /* // SRB2kart if (gametype == GT_COOP) { // Nights intermission is single player only // Don't add it here if (G_IsSpecialStage(gamemap)) intertype = int_spec; else intertype = int_coop; } else */ if (gametype == GT_TEAMMATCH) intertype = int_teammatch; else if (gametype == GT_MATCH || gametype == GT_TAG || gametype == GT_HIDEANDSEEK) intertype = int_match; else if (gametype == GT_RACE) intertype = int_race; else if (gametype == GT_COMPETITION) intertype = int_classicrace; else if (gametype == GT_CTF) intertype = int_ctf; } // We couldn't display the intermission even if we wanted to. // But we still need to give the players their score bonuses, dummy. //if (dedicated) return; // This should always exist, but just in case... if(!mapheaderinfo[prevmap]) P_AllocMapHeader(prevmap); switch (intertype) { case int_nights: // Can't fail //G_SetNightsRecords(); // Check records { UINT8 earnedEmblems = M_CheckLevelEmblems(); if (earnedEmblems) CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for NiGHTS records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); } // fall back into the coop intermission for now intertype = int_timeattack; /* FALLTHRU */ case int_timeattack: // coop or single player, normal level // SRB2kart 230117 - replaced int_coop { // award time and ring bonuses // Y_AwardCoopBonuses(); // setup time data data.coop.tics = players[consoleplayer].realtime; if ((!modifiedgame || savemoddata) && !multiplayer && !demoplayback) { // Update visitation flags mapvisited[gamemap-1] |= MV_BEATEN; if (ALL7EMERALDS(emeralds)) mapvisited[gamemap-1] |= MV_ALLEMERALDS; /*if (ultimatemode) mapvisited[gamemap-1] |= MV_ULTIMATE; if (data.coop.gotperfbonus) mapvisited[gamemap-1] |= MV_PERFECT;*/ if (modeattacking == ATTACKING_RECORD) Y_UpdateRecordReplays(); } for (i = 0; i < 4; ++i) data.coop.bonuspatches[i] = W_CachePatchName(data.coop.bonuses[i].patch, PU_STATIC); data.coop.ptotal = W_CachePatchName("YB_TOTAL", PU_STATIC); // get act number if (mapheaderinfo[prevmap]->actnum) data.coop.ttlnum = W_CachePatchName(va("TTL%.2d", mapheaderinfo[prevmap]->actnum), PU_STATIC); else data.coop.ttlnum = W_CachePatchName("TTL01", PU_STATIC); // get background patches widebgpatch = W_CachePatchName("INTERSCW", PU_STATIC); bgpatch = W_CachePatchName("INTERSCR", PU_STATIC); // grab an interscreen if appropriate if (mapheaderinfo[gamemap-1]->interscreen[0] != '#') { interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_STATIC); useinterpic = true; usebuffer = false; } else { useinterpic = false; #ifdef HWRENDER if (rendermode == render_opengl) usebuffer = true; // This needs to be here for OpenGL, otherwise usebuffer is never set to true for it, and thus there's no screenshot in the intermission #endif } usetile = false; // set up the "got through act" message according to skin name // too long so just show "YOU GOT THROUGH THE ACT" if (strlen(skins[players[consoleplayer].skin].realname) > 13) { strcpy(data.coop.passed1, "YOU GOT"); strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "THROUGH ACT" : "THROUGH THE ACT"); } // long enough that "X GOT" won't fit so use "X PASSED THE ACT" else if (strlen(skins[players[consoleplayer].skin].realname) > 8) { strcpy(data.coop.passed1, skins[players[consoleplayer].skin].realname); strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "PASSED ACT" : "PASSED THE ACT"); } // length is okay for normal use else { snprintf(data.coop.passed1, sizeof data.coop.passed1, "%s GOT", skins[players[consoleplayer].skin].realname); strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "THROUGH ACT" : "THROUGH THE ACT"); } // set X positions if (mapheaderinfo[gamemap-1]->actnum) { data.coop.passedx1 = 62 + (176 - V_LevelNameWidth(data.coop.passed1))/2; data.coop.passedx2 = 62 + (176 - V_LevelNameWidth(data.coop.passed2))/2; } else { data.coop.passedx1 = (BASEVIDWIDTH - V_LevelNameWidth(data.coop.passed1))/2; data.coop.passedx2 = (BASEVIDWIDTH - V_LevelNameWidth(data.coop.passed2))/2; } // The above value is not precalculated because it needs only be computed once // at the start of intermission, and precalculating it would preclude mods // changing the font to one of a slightly different width. break; } /* // SRB2kart 230117 - removed case int_nightsspec: if (modeattacking && stagefailed) { // Nuh-uh. Get out of here. Y_EndIntermission(); Y_FollowIntermission(); break; } if (!stagefailed) G_SetNightsRecords(); // Check records { UINT8 earnedEmblems = M_CheckLevelEmblems(); if (earnedEmblems) CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for NiGHTS records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); } // fall back into the special stage intermission for now intertype = int_spec; // FALLTHRU case int_spec: // coop or single player, special stage { // Update visitation flags? if ((!modifiedgame || savemoddata) && !multiplayer && !demoplayback) { if (!stagefailed) mapvisited[gamemap-1] |= MV_BEATEN; } // give out ring bonuses Y_AwardSpecialStageBonus(); data.spec.bonuspatch = W_CachePatchName(data.spec.bonus.patch, PU_STATIC); data.spec.pscore = W_CachePatchName("YB_SCORE", PU_STATIC); data.spec.pcontinues = W_CachePatchName("YB_CONTI", PU_STATIC); // get background tile bgtile = W_CachePatchName("SPECTILE", PU_STATIC); // grab an interscreen if appropriate if (mapheaderinfo[gamemap-1]->interscreen[0] != '#') { interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_STATIC); useinterpic = true; } else useinterpic = false; // tile if using the default background usetile = !useinterpic; // get special stage specific patches if (!stagefailed && ALL7EMERALDS(emeralds)) { data.spec.cemerald = W_CachePatchName("GOTEMALL", PU_STATIC); data.spec.headx = 70; data.spec.nowsuper = players[consoleplayer].skin ? NULL : W_CachePatchName("NOWSUPER", PU_STATIC); } else { data.spec.cemerald = W_CachePatchName("CEMERALD", PU_STATIC); data.spec.headx = 48; data.spec.nowsuper = NULL; } // Super form stuff (normally blank) data.spec.passed3[0] = '\0'; data.spec.passed4[0] = '\0'; // Super form stuff (normally blank) data.spec.passed3[0] = '\0'; data.spec.passed4[0] = '\0'; // set up the "got through act" message according to skin name if (stagefailed) { strcpy(data.spec.passed2, "SPECIAL STAGE"); data.spec.passed1[0] = '\0'; } else if (ALL7EMERALDS(emeralds)) { snprintf(data.spec.passed1, sizeof data.spec.passed1, "%s", skins[players[consoleplayer].skin].realname); data.spec.passed1[sizeof data.spec.passed1 - 1] = '\0'; strcpy(data.spec.passed2, "GOT THEM ALL!"); if (skins[players[consoleplayer].skin].flags & SF_SUPER) { strcpy(data.spec.passed3, "CAN NOW BECOME"); snprintf(data.spec.passed4, sizeof data.spec.passed4, "SUPER %s", skins[players[consoleplayer].skin].realname); data.spec.passed4[sizeof data.spec.passed4 - 1] = '\0'; } } else { if (strlen(skins[players[consoleplayer].skin].realname) <= SKINNAMESIZE-5) { snprintf(data.spec.passed1, sizeof data.spec.passed1, "%s GOT", skins[players[consoleplayer].skin].realname); data.spec.passed1[sizeof data.spec.passed1 - 1] = '\0'; } else strcpy(data.spec.passed1, "YOU GOT"); strcpy(data.spec.passed2, "A CHAOS EMERALD"); } data.spec.passedx1 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed1))/2; data.spec.passedx2 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed2))/2; data.spec.passedx3 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed3))/2; data.spec.passedx4 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed4))/2; break; }*/ case int_match: { // Calculate who won Y_CalculateMatchWinners(); // set up the levelstring if (mapheaderinfo[prevmap]->zonttl) { if (mapheaderinfo[prevmap]->actnum) snprintf(data.match.levelstring, sizeof data.match.levelstring, "%.32s %.32s * %d *", mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->zonttl, mapheaderinfo[prevmap]->actnum); else snprintf(data.match.levelstring, sizeof data.match.levelstring, "* %.32s %.32s *", mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->zonttl); } else { if (mapheaderinfo[prevmap]->actnum) snprintf(data.match.levelstring, sizeof data.match.levelstring, "%.32s * %d *", mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum); else snprintf(data.match.levelstring, sizeof data.match.levelstring, "* %.32s *", mapheaderinfo[prevmap]->lvlttl); } data.match.levelstring[sizeof data.match.levelstring - 1] = '\0'; // get RESULT header data.match.result = W_CachePatchName("RESULT", PU_STATIC); bgtile = W_CachePatchName("SRB2BACK", PU_STATIC); usetile = true; useinterpic = false; break; } case int_race: // (time-only race) { if ((!modifiedgame || savemoddata) && !multiplayer && !demoplayback) // remove this once we have a proper time attack screen { // setup time data data.coop.tics = players[consoleplayer].realtime; // Update visitation flags mapvisited[gamemap-1] |= MV_BEATEN; if (ALL7EMERALDS(emeralds)) mapvisited[gamemap-1] |= MV_ALLEMERALDS; /*if (ultimatemode) mapvisited[gamemap-1] |= MV_ULTIMATE; if (data.coop.gotperfbonus) mapvisited[gamemap-1] |= MV_PERFECT;*/ if (modeattacking == ATTACKING_RECORD) Y_UpdateRecordReplays(); } // Calculate who won Y_CalculateTournamentPoints(); // set up the levelstring if (mapheaderinfo[prevmap]->zonttl) { if (mapheaderinfo[prevmap]->actnum) snprintf(data.match.levelstring, sizeof data.match.levelstring, "%.32s %.32s * %d *", mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->zonttl, mapheaderinfo[prevmap]->actnum); else snprintf(data.match.levelstring, sizeof data.match.levelstring, "* %.32s %.32s *", mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->zonttl); } else { if (mapheaderinfo[prevmap]->actnum) snprintf(data.match.levelstring, sizeof data.match.levelstring, "%.32s * %d *", mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum); else snprintf(data.match.levelstring, sizeof data.match.levelstring, "* %.32s *", mapheaderinfo[prevmap]->lvlttl); } data.match.levelstring[sizeof data.match.levelstring - 1] = '\0'; // get RESULT header //data.match.result = W_CachePatchName("RESULT", PU_STATIC); bgtile = W_CachePatchName("SRB2BACK", PU_STATIC); usetile = true; useinterpic = false; break; } case int_teammatch: case int_ctf: { // Calculate who won Y_CalculateMatchWinners(); // set up the levelstring if (mapheaderinfo[prevmap]->actnum) snprintf(data.match.levelstring, sizeof data.match.levelstring, "%.32s * %d *", mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum); else snprintf(data.match.levelstring, sizeof data.match.levelstring, "* %.32s *", mapheaderinfo[prevmap]->lvlttl); data.match.levelstring[sizeof data.match.levelstring - 1] = '\0'; if (intertype == int_ctf) { data.match.redflag = rflagico; data.match.blueflag = bflagico; } else // team match { data.match.redflag = rmatcico; data.match.blueflag = bmatcico; } bgtile = W_CachePatchName("SRB2BACK", PU_STATIC); usetile = true; useinterpic = false; break; } case int_classicrace: // classic (full race) { // find out who won Y_CalculateCompetitionWinners(); // set up the levelstring if (mapheaderinfo[prevmap]->actnum) snprintf(data.competition.levelstring, sizeof data.competition.levelstring, "%.32s * %d *", mapheaderinfo[prevmap]->lvlttl, mapheaderinfo[prevmap]->actnum); else snprintf(data.competition.levelstring, sizeof data.competition.levelstring, "* %.32s *", mapheaderinfo[prevmap]->lvlttl); data.competition.levelstring[sizeof data.competition.levelstring - 1] = '\0'; // get background tile bgtile = W_CachePatchName("SRB2BACK", PU_STATIC); usetile = true; useinterpic = false; break; } case int_none: default: break; } } // // Y_CalculateMatchWinners // static void Y_CalculateMatchWinners(void) { INT32 i, j; boolean completed[MAXPLAYERS]; // Initialize variables memset(data.match.scores, 0, sizeof (data.match.scores)); memset(data.match.color, 0, sizeof (data.match.color)); memset(data.match.character, 0, sizeof (data.match.character)); memset(data.match.spectator, 0, sizeof (data.match.spectator)); memset(completed, 0, sizeof (completed)); data.match.numplayers = 0; i = j = 0; for (j = 0; j < MAXPLAYERS; j++) { if (!playeringame[j]) continue; for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) continue; if (players[i].score >= data.match.scores[data.match.numplayers] && completed[i] == false) { data.match.scores[data.match.numplayers] = players[i].score; data.match.color[data.match.numplayers] = &players[i].skincolor; data.match.character[data.match.numplayers] = &players[i].skin; data.match.name[data.match.numplayers] = player_names[i]; data.match.spectator[data.match.numplayers] = players[i].spectator; data.match.num[data.match.numplayers] = i; } } completed[data.match.num[data.match.numplayers]] = true; data.match.numplayers++; } } /* // // Y_CalculateTimeRaceWinners // static void Y_CalculateTimeRaceWinners(void) { INT32 i, j; boolean completed[MAXPLAYERS]; // Initialize variables for (i = 0; i < MAXPLAYERS; i++) data.match.scores[i] = INT32_MAX; memset(data.match.color, 0, sizeof (data.match.color)); memset(data.match.character, 0, sizeof (data.match.character)); memset(data.match.spectator, 0, sizeof (data.match.spectator)); memset(completed, 0, sizeof (completed)); data.match.numplayers = 0; i = j = 0; for (j = 0; j < MAXPLAYERS; j++) { if (!playeringame[j]) continue; for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) continue; if (players[i].realtime <= data.match.scores[data.match.numplayers] && completed[i] == false) { data.match.scores[data.match.numplayers] = players[i].realtime; data.match.color[data.match.numplayers] = &players[i].skincolor; data.match.character[data.match.numplayers] = &players[i].skin; data.match.name[data.match.numplayers] = player_names[i]; data.match.num[data.match.numplayers] = i; } } completed[data.match.num[data.match.numplayers]] = true; data.match.numplayers++; } } */ // // Y_CalculateCompetitionWinners // static void Y_CalculateCompetitionWinners(void) { INT32 i, j; boolean bestat[5]; boolean completed[MAXPLAYERS]; INT32 winner; // shortcut UINT32 points[MAXPLAYERS]; UINT32 times[MAXPLAYERS]; UINT32 rings[MAXPLAYERS]; UINT32 maxrings[MAXPLAYERS]; UINT32 monitors[MAXPLAYERS]; UINT32 scores[MAXPLAYERS]; memset(data.competition.points, 0, sizeof (data.competition.points)); memset(points, 0, sizeof (points)); memset(completed, 0, sizeof (completed)); // Award points. for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) continue; for (j = 0; j < 5; j++) bestat[j] = true; times[i] = players[i].realtime; rings[i] = (UINT32)max(players[i].health-1, 0); maxrings[i] = (UINT32)players[i].totalring; monitors[i] = (UINT32)players[i].numboxes; scores[i] = (UINT32)min(players[i].score, 99999990); for (j = 0; j < MAXPLAYERS; j++) { if (!playeringame[j] || j == i) continue; if (players[i].realtime <= players[j].realtime) points[i]++; else bestat[0] = false; if (max(players[i].health-1, 0) >= max(players[j].health-1, 0)) points[i]++; else bestat[1] = false; if (players[i].totalring >= players[j].totalring) points[i]++; else bestat[2] = false; if (players[i].numboxes >= players[j].numboxes) points[i]++; else bestat[3] = false; if (players[i].score >= players[j].score) points[i]++; else bestat[4] = false; } // Highlight best scores if (bestat[0]) times[i] |= 0x80000000; if (bestat[1]) rings[i] |= 0x80000000; if (bestat[2]) maxrings[i] |= 0x80000000; if (bestat[3]) monitors[i] |= 0x80000000; if (bestat[4]) scores[i] |= 0x80000000; } // Now we go through and set the data.competition struct properly data.competition.numplayers = 0; for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) continue; winner = 0; for (j = 0; j < MAXPLAYERS; j++) { if (!playeringame[j]) continue; if (points[j] >= data.competition.points[data.competition.numplayers] && completed[j] == false) { data.competition.points[data.competition.numplayers] = points[j]; data.competition.num[data.competition.numplayers] = winner = j; } } // We know this person won this spot, now let's set everything appropriately data.competition.times[data.competition.numplayers] = times[winner]; data.competition.rings[data.competition.numplayers] = rings[winner]; data.competition.maxrings[data.competition.numplayers] = maxrings[winner]; data.competition.monitors[data.competition.numplayers] = monitors[winner]; data.competition.scores[data.competition.numplayers] = scores[winner]; snprintf(data.competition.name[data.competition.numplayers], 9, "%s", player_names[winner]); data.competition.name[data.competition.numplayers][8] = '\0'; data.competition.color[data.competition.numplayers] = &players[winner].skincolor; data.competition.character[data.competition.numplayers] = &players[winner].skin; completed[winner] = true; data.competition.numplayers++; } } // ============ // COOP BONUSES // ============ // // Y_SetNullBonus // No bonus in this slot, but we need to set some things anyway. // static void Y_SetNullBonus(player_t *player, y_bonus_t *bstruct) { (void)player; memset(bstruct, 0, sizeof(y_bonus_t)); strncpy(bstruct->patch, "MISSING", sizeof(bstruct->patch)); } // // Y_SetTimeBonus // static void Y_SetTimeBonus(player_t *player, y_bonus_t *bstruct) { INT32 secs, bonus; strncpy(bstruct->patch, "YB_TIME", sizeof(bstruct->patch)); bstruct->display = true; // calculate time bonus secs = player->realtime / TICRATE; if (secs < 30) /* :30 */ bonus = 100000; else if (secs < 45) /* :45 */ bonus = 50000; else if (secs < 60) /* 1:00 */ bonus = 10000; else if (secs < 90) /* 1:30 */ bonus = 5000; else if (secs < 120) /* 2:00 */ bonus = 4000; else if (secs < 180) /* 3:00 */ bonus = 3000; else if (secs < 240) /* 4:00 */ bonus = 2000; else if (secs < 300) /* 5:00 */ bonus = 1000; else if (secs < 360) /* 6:00 */ bonus = 500; else if (secs < 420) /* 7:00 */ bonus = 400; else if (secs < 480) /* 8:00 */ bonus = 300; else if (secs < 540) /* 9:00 */ bonus = 200; else if (secs < 600) /* 10:00 */ bonus = 100; else /* TIME TAKEN: TOO LONG */ bonus = 0; bstruct->points = bonus; } // // Y_SetRingBonus // static void Y_SetRingBonus(player_t *player, y_bonus_t *bstruct) { strncpy(bstruct->patch, "YB_RING", sizeof(bstruct->patch)); bstruct->display = true; bstruct->points = max(0, (player->health-1) * 100); } // // Y_SetLinkBonus // /* static void Y_SetLinkBonus(player_t *player, y_bonus_t *bstruct) // SRB2kart - unused. { strncpy(bstruct->patch, "YB_LINK", sizeof(bstruct->patch)); bstruct->display = true; bstruct->points = max(0, (player->maxlink - 1) * 100); } */ // // Y_SetGuardBonus // static void Y_SetGuardBonus(player_t *player, y_bonus_t *bstruct) { INT32 bonus; strncpy(bstruct->patch, "YB_GUARD", sizeof(bstruct->patch)); bstruct->display = true; if (player->timeshit == 0) bonus = 10000; else if (player->timeshit == 1) bonus = 5000; else if (player->timeshit == 2) bonus = 1000; else if (player->timeshit == 3) bonus = 500; else if (player->timeshit == 4) bonus = 100; else bonus = 0; bstruct->points = bonus; } // // Y_SetPerfectBonus // static void Y_SetPerfectBonus(player_t *player, y_bonus_t *bstruct) { INT32 i; (void)player; memset(bstruct, 0, sizeof(y_bonus_t)); strncpy(bstruct->patch, "YB_PERFE", sizeof(bstruct->patch)); if (data.coop.gotperfbonus == -1) { INT32 sharedringtotal = 0; for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) continue; sharedringtotal += players[i].health - 1; } if (!sharedringtotal || sharedringtotal < nummaprings) data.coop.gotperfbonus = 0; else data.coop.gotperfbonus = 1; } if (!data.coop.gotperfbonus) return; bstruct->display = true; bstruct->points = 50000; } // This list can be extended in the future with SOC/Lua, perhaps. typedef void (*bonus_f)(player_t *, y_bonus_t *); bonus_f bonuses_list[4][4] = { { Y_SetNullBonus, Y_SetNullBonus, Y_SetNullBonus, Y_SetNullBonus, }, { Y_SetNullBonus, Y_SetTimeBonus, Y_SetRingBonus, Y_SetPerfectBonus, }, { Y_SetNullBonus, Y_SetGuardBonus, Y_SetRingBonus, Y_SetNullBonus, }, { Y_SetNullBonus, Y_SetGuardBonus, Y_SetRingBonus, Y_SetPerfectBonus, }, }; /* // SRB2kart 230117 - Replaced with Y_CalculateTournamentPoints // // Y_AwardCoopBonuses // // Awards the time and ring bonuses. // static void Y_AwardCoopBonuses(void) { INT32 i, j, bonusnum, oldscore, ptlives; y_bonus_t localbonuses[4]; // set score/total first data.coop.total = 0; data.coop.score = players[consoleplayer].score; data.coop.gotperfbonus = -1; memset(data.coop.bonuses, 0, sizeof(data.coop.bonuses)); memset(data.coop.bonuspatches, 0, sizeof(data.coop.bonuspatches)); for (i = 0; i < MAXPLAYERS; ++i) { if (!playeringame[i] || players[i].lives < 1) // not active or game over bonusnum = 0; // all null else bonusnum = mapheaderinfo[prevmap]->bonustype + 1; // -1 is none oldscore = players[i].score; for (j = 0; j < 4; ++j) // Set bonuses { (bonuses_list[bonusnum][j])(&players[i], &localbonuses[j]); players[i].score += localbonuses[j].points; } ptlives = (!ultimatemode && !modeattacking) ? max((players[i].score/50000) - (oldscore/50000), 0) : 0; if (ptlives) P_GivePlayerLives(&players[i], ptlives); if (i == consoleplayer) { data.coop.gotlife = ptlives; M_Memcpy(&data.coop.bonuses, &localbonuses, sizeof(data.coop.bonuses)); } } // Just in case the perfect bonus wasn't checked. if (data.coop.gotperfbonus < 0) data.coop.gotperfbonus = 0; } // // Y_AwardSpecialStageBonus // // Gives a ring bonus only. static void Y_AwardSpecialStageBonus(void) { INT32 i, oldscore, ptlives; y_bonus_t localbonus; data.spec.score = players[consoleplayer].score; memset(&data.spec.bonus, 0, sizeof(data.spec.bonus)); data.spec.bonuspatch = NULL; for (i = 0; i < MAXPLAYERS; i++) { oldscore = players[i].score; if (!playeringame[i] || players[i].lives < 1) // not active or game over Y_SetNullBonus(&players[i], &localbonus); else if (useNightsSS) // Link instead of Score Y_SetLinkBonus(&players[i], &localbonus); else Y_SetRingBonus(&players[i], &localbonus); players[i].score += localbonus.points; // grant extra lives right away since tally is faked ptlives = (!ultimatemode && !modeattacking) ? max((players[i].score/50000) - (oldscore/50000), 0) : 0; if (ptlives) P_GivePlayerLives(&players[i], ptlives); if (i == consoleplayer) { M_Memcpy(&data.spec.bonus, &localbonus, sizeof(data.spec.bonus)); data.spec.gotlife = ptlives; // Continues related data.spec.continues = min(players[i].continues, 8); if (players[i].gotcontinue) data.spec.continues |= 0x80; data.spec.playercolor = &players[i].skincolor; data.spec.playerchar = &players[i].skin; } } } */ // // Y_CalculateTournamentPoints // static void Y_CalculateTournamentPoints(void) { INT32 i, j; boolean completed[MAXPLAYERS]; INT32 numplayersingame = 0; INT32 increase[MAXPLAYERS]; for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i]) numplayersingame++; } for (i = 0; i < numplayersingame; i++) { increase[i] = numplayersingame-i; if (increase[i] < 0) increase[i] = 0; } // Initialize variables for (j = 0; j < MAXPLAYERS; j++) data.match.scores[j] = INT32_MAX; memset(data.match.time, 0, sizeof (data.match.time)); memset(data.match.color, 0, sizeof (data.match.color)); memset(data.match.character, 0, sizeof (data.match.character)); memset(data.match.spectator, 0, sizeof (data.match.spectator)); memset(data.match.increase, 0, sizeof (data.match.increase)); memset(completed, 0, sizeof (completed)); data.match.numplayers = 0; i = j = 0; for (j = 0; j < MAXPLAYERS; j++) { if (!playeringame[j]) continue; for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) continue; if (players[i].realtime <= data.match.scores[data.match.numplayers] && completed[i] == false) { data.match.time[data.match.numplayers] = players[i].realtime; data.match.scores[data.match.numplayers] = players[i].realtime; data.match.color[data.match.numplayers] = &players[i].skincolor; data.match.character[data.match.numplayers] = &players[i].skin; data.match.name[data.match.numplayers] = player_names[i]; data.match.spectator[data.match.numplayers] = players[i].spectator; data.match.num[data.match.numplayers] = i; } } completed[data.match.num[data.match.numplayers]] = true; if (!(players[data.match.num[data.match.numplayers]].pflags & PF_TIMEOVER || players[data.match.num[data.match.numplayers]].spectator)) data.match.increase[data.match.numplayers] = increase[data.match.numplayers]; players[data.match.num[data.match.numplayers]].score += data.match.increase[data.match.numplayers]; data.match.scores[data.match.numplayers] = players[data.match.num[data.match.numplayers]].score; data.match.numplayers++; } } // ====== // // Y_EndIntermission // void Y_EndIntermission(void) { Y_UnloadData(); endtic = -1; intertype = int_none; usebuffer = false; } // // Y_EndGame // // Why end the game? // Because Y_FollowIntermission and F_EndCutscene would // both do this exact same thing *in different ways* otherwise, // which made it so that you could only unlock Ultimate mode // if you had a cutscene after the final level and crap like that. // This function simplifies it so only one place has to be updated // when something new is added. void Y_EndGame(void) { // Only do evaluation and credits in coop games. if (gametype == GT_COOP) { if (nextmap == 1102-1) // end game with credits { F_StartCredits(); return; } if (nextmap == 1101-1) // end game with evaluation { F_StartGameEvaluation(); return; } } // 1100 or competitive multiplayer, so go back to title screen. D_StartTitle(); } // // Y_FollowIntermission // static void Y_FollowIntermission(void) { if (modeattacking) { M_EndModeAttackRun(); return; } if (nextmap < 1100-1) { // normal level G_AfterIntermission(); return; } // Start a custom cutscene if there is one. if (mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking) { F_StartCustomCutscene(mapheaderinfo[gamemap-1]->cutscenenum-1, false, false); return; } Y_EndGame(); } #define UNLOAD(x) Z_ChangeTag(x, PU_CACHE); x = NULL // // Y_UnloadData // static void Y_UnloadData(void) { // In hardware mode, don't Z_ChangeTag a pointer returned by W_CachePatchName(). // It doesn't work and is unnecessary. if (rendermode != render_soft) return; // unload the background patches UNLOAD(bgpatch); UNLOAD(widebgpatch); UNLOAD(bgtile); UNLOAD(interpic); switch (intertype) { /* case int_coop: // unload the coop and single player patches UNLOAD(data.coop.ttlnum); UNLOAD(data.coop.bonuspatches[3]); UNLOAD(data.coop.bonuspatches[2]); UNLOAD(data.coop.bonuspatches[1]); UNLOAD(data.coop.bonuspatches[0]); UNLOAD(data.coop.ptotal); break; case int_spec: // unload the special stage patches //UNLOAD(data.spec.cemerald); //UNLOAD(data.spec.nowsuper); UNLOAD(data.spec.bonuspatch); UNLOAD(data.spec.pscore); UNLOAD(data.spec.pcontinues); break; */ case int_match: case int_race: UNLOAD(data.match.result); break; case int_ctf: UNLOAD(data.match.blueflag); UNLOAD(data.match.redflag); break; default: //without this default, //int_none, int_tag, int_chaos, and int_classicrace //are not handled break; } } // SRB2Kart: Voting! // // Y_VoteDrawer // // Draws the voting screen! // void Y_VoteDrawer(void) { INT32 i, x, y = 0; if (rendermode == render_none) return; if (votetic >= voteendtic && voteendtic != -1) return; V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); if (widebgpatch && rendermode == render_soft && vid.width / vid.dupx > 320) V_DrawScaledPatch(((vid.width/2) / vid.dupx) - (SHORT(widebgpatch->width)/2), (vid.height / vid.dupy) - SHORT(widebgpatch->height), V_SNAPTOTOP|V_SNAPTOLEFT, widebgpatch); else V_DrawScaledPatch(((vid.width/2) / vid.dupx) - (SHORT(bgpatch->width)/2), // Keep the width/height adjustments, for screens that are less wide than 320(?) (vid.height / vid.dupy) - SHORT(bgpatch->height), V_SNAPTOTOP|V_SNAPTOLEFT, bgpatch); y = 30; for (i = 0; i < 4; i++) { char str[40]; patch_t *pic; if (i == 3) { snprintf(str, sizeof str, "%.32s", "RANDOM"); str[sizeof str - 1] = '\0'; pic = randomlvl; } else { strcpy(str, levelinfo[i].str); pic = levelinfo[i].pic; } if (i == voteclient.selection) { if (votes[consoleplayer] == -1) { V_DrawScaledPatch(BASEVIDWIDTH-124, y+21, V_SNAPTORIGHT, cursor); if (votetic % 4 > 1) V_DrawFill(BASEVIDWIDTH-101, y-1, 82, 52, 120|V_SNAPTORIGHT); else V_DrawFill(BASEVIDWIDTH-101, y-1, 82, 52, 103|V_SNAPTORIGHT); } V_DrawSmallScaledPatch(BASEVIDWIDTH-100, y, V_SNAPTORIGHT, pic); V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 40+y, V_SNAPTORIGHT, str); y += 55; } else { V_DrawTinyScaledPatch(BASEVIDWIDTH-60, y, V_SNAPTORIGHT, pic); y += 30; } } x = 20; y = 10; for (i = 0; i < MAXPLAYERS; i++) { if ((playeringame[i] && !players[i].spectator) && votes[i] != -1) { patch_t *pic; if (votes[i] == 3 && (i != pickedvote || voteendtic == -1)) pic = randomlvl; else pic = levelinfo[votes[i]].pic; if (!timer && i == voteclient.ranim) { V_DrawScaledPatch(x-18, y+9, V_SNAPTOLEFT, cursor); if (votetic % 4 > 1) V_DrawFill(x-1, y-1, 42, 27, 120|V_SNAPTOLEFT); else V_DrawFill(x-1, y-1, 42, 27, 103|V_SNAPTOLEFT); } V_DrawTinyScaledPatch(x, y, V_SNAPTOLEFT, pic); if (players[i].skincolor == 0) V_DrawSmallScaledPatch(x+24, y+9, V_SNAPTOLEFT, faceprefix[players[i].skin]); else { UINT8 *colormap = R_GetTranslationColormap(players[i].skin, players[i].skincolor, GTC_CACHE); V_DrawSmallMappedPatch(x+24, y+9, V_SNAPTOLEFT, faceprefix[players[i].skin], colormap); } } if (splitscreen) // only 1p has a vote in splitscreen break; y += 30; if (y > BASEVIDHEIGHT-40) { x += 60; y = 10; } } if (timer) V_DrawCenteredString(BASEVIDWIDTH/2, 188, V_YELLOWMAP|V_SNAPTOBOTTOM, va("Vote ends in %d seconds", timer/TICRATE)); } // // Y_VoteTicker // // Vote screen thinking :eggthinking: // void Y_VoteTicker(void) { boolean pressed = false; INT32 i; if (paused || P_AutoPause()) return; votetic++; if (votetic == voteendtic) { Y_UnloadVoteData(); // Y_EndVote resets voteendtic too early apparently, causing the game to try to render patches that we just unloaded... Y_FollowIntermission(); return; } for (i = 0; i < MAXPLAYERS; i++) // Correct votes as early as possible, before they're processed by the game at all { if (!playeringame[i] || players[i].spectator) votes[i] = -1; // Spectators are the lower class, and have effectively no voice in the government. Democracy sucks. else if (pickedvote != -1 && votes[i] == -1 && !splitscreen) votes[i] = 3; // Slow people get random } if (server && pickedvote != -1 && votes[pickedvote] == -1) // Uh oh! The person who got picked left! Recalculate, quick! D_PickVote(); if (!votetic) S_ChangeMusicInternal("vote", true); if (timer) timer--; if (voteclient.delay) voteclient.delay--; if (pickedvote != -1) { timer = 0; voteclient.rsynctime++; if (voteendtic == -1) { UINT8 tempvotes[MAXPLAYERS]; UINT8 numvotes = 0; for (i = 0; i < MAXPLAYERS; i++) { if (votes[i] == -1) continue; tempvotes[numvotes] = i; numvotes++; } voteclient.rtics--; if (voteclient.rtics <= 0) { voteclient.roffset++; voteclient.rtics = min(20, (3*voteclient.roffset/4)+5); S_StartSound(NULL, sfx_kc39); } if (voteclient.rendoff == 0 || voteclient.roffset < voteclient.rendoff) voteclient.ranim = tempvotes[((pickedvote + voteclient.roffset) % numvotes)]; if (voteclient.roffset >= 20) { if (voteclient.rendoff == 0) { if (tempvotes[((pickedvote + voteclient.roffset + 4) % numvotes)] == pickedvote && voteclient.rsynctime % 50 == 0) // Song is 1.45 seconds long (sorry @ whoever wants to replace it in a music wad :V) { voteclient.rendoff = voteclient.roffset+4; S_ChangeMusicInternal("voteeb", false); } } else if (voteclient.roffset >= voteclient.rendoff) { voteendtic = votetic + (3*TICRATE); S_StartSound(NULL, sfx_kc48); } } } else voteclient.ranim = pickedvote; } else { if (votetic < 3*(NEWTICRATE/7)) // give it some time before letting you control it :V return; if ((playeringame[consoleplayer] && !players[consoleplayer].spectator) && !voteclient.delay && pickedvote == -1 && votes[consoleplayer] == -1) { if (InputDown(gc_aimforward, 1) || JoyAxis(AXISMOVE, 1) < 0) { voteclient.selection--; pressed = true; } if ((InputDown(gc_aimbackward, 1) || JoyAxis(AXISMOVE, 1) > 0) && !pressed) { voteclient.selection++; pressed = true; } if (voteclient.selection < 0) voteclient.selection = 3; if (voteclient.selection > 3) voteclient.selection = 0; if (InputDown(gc_accelerate, 1) && !pressed) { D_ModifyClientVote(voteclient.selection); pressed = true; } } if (pressed) { S_StartSound(NULL, sfx_kc4a); voteclient.delay = NEWTICRATE/7; } if (server) { if (timer == 0) { for (i = 0; i < MAXPLAYERS; i++) { if ((playeringame[i] && !players[i].spectator) && votes[i] == -1 && !splitscreen) votes[i] = 3; } } else { if (splitscreen) { if (votes[0] == -1) return; } else { for (i = 0; i < MAXPLAYERS; i++) { if ((playeringame[i] && !players[i].spectator) && votes[i] == -1) return; } } } timer = 0; if (voteendtic == -1) D_PickVote(); } } } // // Y_StartVote // // MK online style voting screen, appears after intermission // void Y_StartVote(void) { INT32 i = 0; votetic = -1; #ifdef PARANOIA if (voteendtic != -1) I_Error("voteendtic is dirty"); #endif widebgpatch = W_CachePatchName("INTERSCW", PU_STATIC); bgpatch = W_CachePatchName("INTERSCR", PU_STATIC); cursor = W_CachePatchName("M_CURSOR", PU_STATIC); randomlvl = W_CachePatchName("RANDOMLV", PU_STATIC); timer = cv_votetime.value*TICRATE; pickedvote = -1; voteclient.selection = 0; voteclient.delay = 0; voteclient.ranim = 0; voteclient.rtics = 1; voteclient.roffset = 0; voteclient.rsynctime = 0; voteclient.rendoff = 0; for (i = 0; i < MAXPLAYERS; i++) votes[i] = -1; for (i = 0; i < 4; i++) { lumpnum_t lumpnum; // set up the str if (mapheaderinfo[votelevels[i]]->zonttl) { if (mapheaderinfo[votelevels[i]]->actnum) snprintf(levelinfo[i].str, sizeof levelinfo[i].str, "%.32s %.32s %d", mapheaderinfo[votelevels[i]]->lvlttl, mapheaderinfo[votelevels[i]]->zonttl, mapheaderinfo[votelevels[i]]->actnum); else snprintf(levelinfo[i].str, sizeof levelinfo[i].str, "%.32s %.32s", mapheaderinfo[votelevels[i]]->lvlttl, mapheaderinfo[votelevels[i]]->zonttl); } else { if (mapheaderinfo[votelevels[i]]->actnum) snprintf(levelinfo[i].str, sizeof levelinfo[i].str, "%.32s %d", mapheaderinfo[votelevels[i]]->lvlttl, mapheaderinfo[votelevels[i]]->actnum); else snprintf(levelinfo[i].str, sizeof levelinfo[i].str, "%.32s", mapheaderinfo[votelevels[i]]->lvlttl); } levelinfo[i].str[sizeof levelinfo[i].str - 1] = '\0'; lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(votelevels[i]+1))); if (lumpnum != LUMPERROR) levelinfo[i].pic = W_CachePatchName(va("%sP", G_BuildMapName(votelevels[i]+1)), PU_STATIC); else levelinfo[i].pic = W_CachePatchName("BLANKLVL", PU_STATIC); } } // // Y_EndVote // void Y_EndVote(void) { Y_UnloadVoteData(); voteendtic = -1; } // // Y_UnloadVoteData // static void Y_UnloadVoteData(void) { if (rendermode != render_soft) return; UNLOAD(widebgpatch); UNLOAD(bgpatch); UNLOAD(cursor); UNLOAD(randomlvl); UNLOAD(levelinfo[3].pic); UNLOAD(levelinfo[2].pic); UNLOAD(levelinfo[1].pic); UNLOAD(levelinfo[0].pic); } // // Y_SetupVoteFinish // void Y_SetupVoteFinish(SINT8 pick, SINT8 level) { if (pickedvote == -1) { INT32 i; SINT8 votecompare = -1; boolean allsame = true; voteclient.rsynctime = 0; for (i = 0; i < MAXPLAYERS; i++) { if ((playeringame[i] && !players[i].spectator) && votes[i] == -1 && !splitscreen) votes[i] = 3; if (votes[i] == -1) continue; if (votecompare == -1) votecompare = votes[i]; else if (votes[i] != votecompare) allsame = false; } if (allsame) { voteendtic = votetic + (5*TICRATE); S_StartSound(NULL, sfx_kc48); S_ChangeMusicInternal("voteeb", false); } else S_ChangeMusicInternal("voteea", true); } pickedvote = pick; nextmap = votelevels[level]; timer = 0; }