From c1f3237157ce68840ab2fd83f8943941b04386d6 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 24 Feb 2022 21:19:03 +0000 Subject: [PATCH] Boss API + assorted relevant bugfixes, will go over the featureset of this branch with a fine toothed comb when it's time to write the merge request description so this is all you're getting right now --- src/Sourcefile | 1 + src/d_clisrv.c | 3 +- src/d_main.c | 10 ++ src/d_netcmd.c | 11 +- src/deh_tables.c | 11 +- src/doomstat.h | 7 +- src/g_game.c | 41 +++++- src/info.c | 2 +- src/k_battle.c | 21 ++- src/k_boss.c | 188 ++++++++++++++++++++++++++ src/k_boss.h | 63 +++++++++ src/k_bot.c | 9 +- src/k_collide.c | 57 ++++++-- src/k_collide.h | 1 + src/k_grandprix.c | 7 + src/k_hud.c | 328 +++++++++++++++++++++++++++++++++++++++++++--- src/k_kart.c | 25 +++- src/k_pwrlv.c | 3 +- src/lua_baselib.c | 75 ++++++++++- src/lua_script.c | 6 + src/m_fixed.h | 4 + src/m_menu.c | 94 ++++++++++++- src/p_enemy.c | 11 +- src/p_mobj.c | 39 +++++- src/p_setup.c | 32 ++++- src/p_tick.c | 42 ++++-- src/p_user.c | 11 +- src/r_things.c | 7 +- src/s_sound.c | 5 +- src/sounds.c | 2 + src/sounds.h | 2 + src/st_stuff.c | 138 ++++++++++++++++++- src/v_video.c | 26 +++- src/v_video.h | 2 +- src/y_inter.c | 79 ++++++----- src/y_inter.h | 4 +- 36 files changed, 1225 insertions(+), 142 deletions(-) create mode 100644 src/k_boss.c create mode 100644 src/k_boss.h diff --git a/src/Sourcefile b/src/Sourcefile index 6c8cabbcd..f67fa8d3f 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -110,6 +110,7 @@ k_bot.c k_botitem.c k_botsearch.c k_grandprix.c +k_boss.c k_hud.c k_terrain.c k_brightmap.c diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 31ecc92ca..946d296d1 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -53,6 +53,7 @@ #include "k_pwrlv.h" #include "k_bot.h" #include "k_grandprix.h" +#include "k_boss.h" #include "doomstat.h" #include "s_sound.h" // sfx_syfail @@ -3757,7 +3758,7 @@ void SV_StartSinglePlayerServer(void) netgame = false; multiplayer = false; - if (modeattacking == ATTACKING_CAPSULES) + if ((modeattacking == ATTACKING_CAPSULES) || (bossinfo.boss == true)) { G_SetGametype(GT_BATTLE); } diff --git a/src/d_main.c b/src/d_main.c index c95621a05..ebeced5d6 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -71,6 +71,7 @@ // SRB2Kart #include "k_grandprix.h" +#include "k_boss.h" #include "doomstat.h" #ifdef CMAKECONFIG @@ -928,6 +929,13 @@ void D_StartTitle(void) // Reset GP memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + // Reset boss info + if (bossinfo.enemyname) + Z_Free(bossinfo.enemyname); + if (bossinfo.subtitle) + Z_Free(bossinfo.subtitle); + memset(&bossinfo, 0, sizeof(struct bossinfo)); + // empty maptol so mario/etc sounds don't play in sound test when they shouldn't maptol = 0; @@ -1105,6 +1113,8 @@ static void IdentifyVersion(void) D_AddFile(startupiwads, va(pandf,srb2waddir,"followers.pk3")); #ifdef USE_PATCH_FILE D_AddFile(startupiwads, va(pandf,srb2waddir,PATCHNAME)); + // lat` had the right idea + D_AddFile(startupiwads, va(pandf,srb2waddir,"bosstest.pk3")); #endif //// #undef TEXTURESNAME diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 416aeb86a..cbb1a74cf 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -56,6 +56,7 @@ #include "k_color.h" #include "k_respawn.h" #include "k_grandprix.h" +#include "k_boss.h" #include "doomstat.h" #include "deh_tables.h" @@ -2376,6 +2377,10 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r // Too lazy to change the input value for every instance of this function....... pencoremode = grandprixinfo.encore; } + else if (bossinfo.boss == true) + { + pencoremode = bossinfo.encore; + } if (delay != 2) { @@ -2858,7 +2863,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) else if (gametype != lastgametype) D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype - if (!(gametyperules & GTR_CIRCUIT)) + if (!(gametyperules & GTR_CIRCUIT) && !bossinfo.boss) pencoremode = false; skipprecutscene = ((flags & (1<<2)) != 0); @@ -4991,9 +4996,9 @@ void Command_Retry_f(void) { CONS_Printf(M_GetText("You must be in a level to use this.\n")); } - else if (grandprixinfo.gp == false) + else if (grandprixinfo.gp == false && bossinfo.boss == false) { - CONS_Printf(M_GetText("This only works in Grand Prix.\n")); + CONS_Printf(M_GetText("This only works in Grand Prix or Mission Mode.\n")); } else { diff --git a/src/deh_tables.c b/src/deh_tables.c index e0f60c3b1..7856d2c1e 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -23,6 +23,7 @@ #include "i_sound.h" // musictype_t (for lua) #include "g_state.h" // gamestate_t (for lua) #include "r_data.h" // patchalphastyle_t +#include "k_boss.h" // spottype_t (for lua) #include "deh_tables.h" @@ -5862,6 +5863,8 @@ const char *const MOBJEFLAG_LIST[] = { "APPLYPMOMZ", // Platform movement "TRACERANGLE", // Compute and trigger on mobj angle relative to tracer "JUSTBOUNCEDWALL", + "DAMAGEHITLAG", + "SLOPELAUNCHED", NULL }; @@ -6316,6 +6319,7 @@ struct int_const_s const INT_CONST[] = { {"FRACUNIT",FRACUNIT}, {"FU" ,FRACUNIT}, {"FRACBITS",FRACBITS}, + {"M_TAU_FIXED",M_TAU_FIXED}, // doomdef.h constants {"TICRATE",TICRATE}, @@ -6568,7 +6572,7 @@ struct int_const_s const INT_CONST[] = { {"int_none",int_none}, {"int_race",int_race}, {"int_battle",int_battle}, - {"int_timeattack",int_timeattack}, + {"int_battletime", int_battletime}, // Jingles (jingletype_t) {"JT_NONE",JT_NONE}, @@ -6940,6 +6944,11 @@ struct int_const_s const INT_CONST[] = { {"KSPIN_STUNG",KSPIN_STUNG}, {"KSPIN_EXPLOSION",KSPIN_EXPLOSION}, + // spottype_t + {"SPOT_NONE",SPOT_NONE}, + {"SPOT_WEAK",SPOT_WEAK}, + {"SPOT_BUMP",SPOT_BUMP}, + {NULL,0} }; diff --git a/src/doomstat.h b/src/doomstat.h index 19359df3b..59c59d7ba 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -500,11 +500,12 @@ extern INT32 timelimits[NUMGAMETYPES]; enum TypeOfLevel { // Gametypes - TOL_RACE = 0x0001, ///< Race - TOL_BATTLE = 0x0002, ///< Battle + TOL_RACE = 0x0001, ///< Race + TOL_BATTLE = 0x0002, ///< Battle + TOL_BOSS = 0x0004, ///< Boss (variant of battle, but forbidden) // Modifiers - TOL_TV = 0x0100, ///< Midnight Channel specific: draw TV like overlay on HUD + TOL_TV = 0x0100 ///< Midnight Channel specific: draw TV like overlay on HUD }; #define MAXTOL (1<<31) diff --git a/src/g_game.c b/src/g_game.c index 9b10e1b03..0841a48b5 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -56,6 +56,7 @@ #include "k_color.h" #include "k_respawn.h" #include "k_grandprix.h" +#include "k_boss.h" #include "k_bot.h" #include "doomstat.h" @@ -2668,7 +2669,7 @@ mapthing_t *G_FindMapStart(INT32 playernum) { // In platform gametypes, spawn in Co-op starts first // Overriden by GTR_BATTLESTARTS. - if (gametyperules & GTR_BATTLESTARTS) + if (gametyperules & GTR_BATTLESTARTS && bossinfo.boss == false) spawnpoint = G_FindBattleStartOrFallback(playernum); else spawnpoint = G_FindRaceStartOrFallback(playernum); @@ -2775,10 +2776,30 @@ void G_ExitLevel(void) { if (gamestate == GS_LEVEL) { - if (grandprixinfo.gp == true && grandprixinfo.wonround != true) + UINT8 i; + boolean youlost = false; + if (bossinfo.boss == true) { - UINT8 i; + youlost = true; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator && !players[i].bot) + { + if (players[i].bumpers > 0) + { + youlost = false; + break; + } + } + } + } + else if (grandprixinfo.gp == true) + { + youlost = (grandprixinfo.wonround != true); + } + if (youlost) + { // You didn't win... for (i = 0; i < MAXPLAYERS; i++) @@ -3007,6 +3028,7 @@ UINT32 gametypetol[NUMGAMETYPES] = tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = { {"RACE",TOL_RACE}, {"BATTLE",TOL_BATTLE}, + {"BOSS",TOL_BOSS}, {"TV",TOL_TV}, {NULL, 0} }; @@ -3084,10 +3106,17 @@ boolean G_IsSpecialStage(INT32 mapnum) // boolean G_GametypeUsesLives(void) { + if (modeattacking || metalrecording) // NOT in Record Attack + return false; + + if (bossinfo.boss == true) // Fighting a boss? + { + return true; + } + if ((grandprixinfo.gp == true) // In Grand Prix && (gametype == GT_RACE) // NOT in bonus round - && !G_IsSpecialStage(gamemap) // NOT in special stage - && !(modeattacking || metalrecording)) // NOT in Record Attack + && !G_IsSpecialStage(gamemap)) // NOT in special stage { return true; } @@ -3709,7 +3738,7 @@ void G_NextLevel(void) { if (gamestate != GS_VOTING) { - if ((cv_advancemap.value == 3) && grandprixinfo.gp == false && !modeattacking && !skipstats && (multiplayer || netgame)) + if ((cv_advancemap.value == 3) && grandprixinfo.gp == false && bossinfo.boss == false && !modeattacking && !skipstats && (multiplayer || netgame)) { UINT8 i; for (i = 0; i < MAXPLAYERS; i++) diff --git a/src/info.c b/src/info.c index 2cd257839..bbe58d13f 100644 --- a/src/info.c +++ b/src/info.c @@ -6784,7 +6784,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 4, // mass 0, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags S_NULL // raisestate }, diff --git a/src/k_battle.c b/src/k_battle.c index 4b8bf219a..b5c9baf04 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -2,6 +2,7 @@ /// \brief SRB2Kart Battle Mode specific code #include "k_battle.h" +#include "k_boss.h" #include "k_kart.h" #include "doomtype.h" #include "doomdata.h" @@ -68,6 +69,9 @@ void K_SpawnBattlePoints(player_t *source, player_t *victim, UINT8 amount) pt->color = victim->skincolor; else pt->color = source->skincolor; + + if (encoremode) + pt->renderflags ^= RF_HORIZONTALFLIP; } void K_CheckBumpers(void) @@ -107,7 +111,19 @@ void K_CheckBumpers(void) winnerscoreadd -= players[i].roundscore; } - if (numingame <= 1) + if (bossinfo.boss) + { + if (nobumpers) + { + for (i = 0; i < MAXPLAYERS; i++) + { + players[i].pflags |= PF_NOCONTEST; + P_DoPlayerExit(&players[i]); + } + } + return; + } + else if (numingame <= 1) { if (!battlecapsules) { @@ -697,6 +713,9 @@ void K_SpawnBattleCapsules(void) if (battlecapsules) return; + if (bossinfo.boss) + return; + if (!(gametyperules & GTR_CAPSULES)) return; diff --git a/src/k_boss.c b/src/k_boss.c new file mode 100644 index 000000000..345e09389 --- /dev/null +++ b/src/k_boss.c @@ -0,0 +1,188 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2022 by Viv "toaster" Grannell +// Copyright (C) 2018-2022 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_boss.c +/// \brief Boss battle game logic + +#include "k_boss.h" +#include "doomdef.h" +#include "d_player.h" +#include "g_game.h" +#include "p_local.h" +#include "k_kart.h" +#include "s_sound.h" +#include "st_stuff.h" +#include "z_zone.h" + +struct bossinfo bossinfo; + +/*-------------------------------------------------- + void K_BossInfoTicker(void) + + See header file for description. +--------------------------------------------------*/ +void K_BossInfoTicker(void) +{ + UINT8 i; + + if (bossinfo.boss == false) + return; + + // Update healthbar data. (only if the hud is visible) + if (leveltime > (TICRATE/2)+3) + { + // Greater than the actual health? + if (bossinfo.visualbar > bossinfo.healthbar) + { + bossinfo.visualbar--; + // If the boss is dying, start shrinking the healthbar. + if (bossinfo.visualbar == 0) + bossinfo.barlen-= 2; + } + // Less than the actual health? + else if (bossinfo.visualbar < bossinfo.healthbar) + bossinfo.visualbar++; + // Continue to shrink the healthbar. + else if (bossinfo.barlen > 1 && bossinfo.barlen < BOSSHEALTHBARLEN) + bossinfo.barlen -= 2; + + // Jitter timer. + if (bossinfo.visualbarimpact) + bossinfo.visualbarimpact--; + } + + if (bossinfo.doweakspotsound != SPOT_NONE) + { + S_StartSound(NULL, sfx_mbs55); // may change for bump option + bossinfo.doweakspotsound = SPOT_NONE; + } + + // Update weakspot data. + for (i = 0; i < NUMWEAKSPOTS; i++) + { + // Clean out invalid references. + if ((bossinfo.weakspots[i].spot && P_MobjWasRemoved(bossinfo.weakspots[i].spot))) + P_SetTarget(&bossinfo.weakspots[i].spot, NULL); + + if (bossinfo.weakspots[i].spot == NULL) + continue; + + // Damaged quickly? Make it disappear immediately (making sure to match the flashing). + if ((bossinfo.weakspots[i].spot->hitlag > 0) && (bossinfo.weakspots[i].time > TICRATE/2)) + bossinfo.weakspots[i].time = (TICRATE/2) & ~(bossinfo.weakspots[i].time & 1); + + // Handle counter. + if (!bossinfo.weakspots[i].time) + continue; + bossinfo.weakspots[i].time--; + + // Get rid of concluded spots. + if (!bossinfo.weakspots[i].time && !bossinfo.weakspots[i].minimap) + P_SetTarget(&bossinfo.weakspots[i].spot, NULL); + } +} + +/*-------------------------------------------------- + void K_InitBossHealthBar(const char *enemyname, const char *subtitle, sfxenum_t titlesound, fixed_t pinchmagnitude, UINT8 divisions) + + See header file for description. +--------------------------------------------------*/ + +void K_InitBossHealthBar(const char *enemyname, const char *subtitle, sfxenum_t titlesound, fixed_t pinchmagnitude, UINT8 divisions) +{ + if (enemyname && enemyname[0]) + { + if (bossinfo.enemyname) + Z_Free(bossinfo.enemyname); + bossinfo.enemyname = Z_StrDup(enemyname); + } + + if (subtitle && subtitle[0]) + { + if (bossinfo.subtitle) + Z_Free(bossinfo.subtitle); + bossinfo.subtitle = Z_StrDup(subtitle); + } + + if (titlesound) + { + bossinfo.titlesound = titlesound; + } + + bossinfo.barlen = BOSSHEALTHBARLEN; + K_UpdateBossHealthBar(FRACUNIT, 0); + + bossinfo.healthbarpinch = FixedMul(pinchmagnitude, BOSSHEALTHBARLEN*FRACUNIT)>>FRACBITS; + + // we do this here so we can fudge our working a bit + if (divisions > 0) + { + if (divisions > (BOSSHEALTHBARLEN/2)) // megamanification + { + divisions = (BOSSHEALTHBARLEN/2); + } + bossinfo.visualdiv = FixedDiv(BOSSHEALTHBARLEN*FRACUNIT, divisions*FRACUNIT); + } + else + { + bossinfo.visualdiv = 0; + } +} + +/*-------------------------------------------------- + void K_UpdateBossHealthBar(fixed_t magnitude, tic_t jitterlen) + + See header file for description. +--------------------------------------------------*/ + +void K_UpdateBossHealthBar(fixed_t magnitude, tic_t jitterlen) +{ + if (jitterlen > bossinfo.visualbarimpact) + bossinfo.visualbarimpact = jitterlen; + bossinfo.healthbar = FixedMul(magnitude, BOSSHEALTHBARLEN); +} + +/*-------------------------------------------------- + void K_DeclareWeakspot(mobj_t *weakspot, spottype_t spottype, boolean minimap) + + See header file for description. +--------------------------------------------------*/ +void K_DeclareWeakspot(mobj_t *spot, spottype_t spottype, UINT16 color, boolean minimap) +{ + UINT8 i; + + // First check whether the spot is already in the list and simply redeclaring weakness (for example, a vulnerable moment in the pattern). + for (i = 0; i < NUMWEAKSPOTS; i++) + if (bossinfo.weakspots[i].spot == spot) + break; + + // If it's not redeclaring, check for an empty spot. + if (i == NUMWEAKSPOTS) + { + for (i = 0; i < NUMWEAKSPOTS; i++) + if (!bossinfo.weakspots[i].spot || P_MobjWasRemoved(bossinfo.weakspots[i].spot)) + break; + } + + // If you've crammed up the list with minimap entries (the only ones that will persist), give up. + if (i == NUMWEAKSPOTS) + return; + + // OK, now we can slot in the weakspot data... + P_SetTarget(&bossinfo.weakspots[i].spot, spot); + bossinfo.weakspots[i].type = spottype; + bossinfo.weakspots[i].time = WEAKSPOTANIMTIME; + bossinfo.weakspots[i].color = color; + bossinfo.weakspots[i].minimap = minimap; + + if (spottype && bossinfo.doweakspotsound != SPOT_WEAK) + { + bossinfo.doweakspotsound = spottype; + } +} diff --git a/src/k_boss.h b/src/k_boss.h new file mode 100644 index 000000000..838154817 --- /dev/null +++ b/src/k_boss.h @@ -0,0 +1,63 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2022 by Viv "toaster" Grannell +// Copyright (C) 2018-2022 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_boss.h +/// \brief Boss battle game logic + +#ifndef __K_BOSS__ +#define __K_BOSS__ + +#include "doomdef.h" +#include "doomstat.h" + +typedef enum +{ + SPOT_NONE = 0, + SPOT_WEAK, + SPOT_BUMP, +} spottype_t; + +#define NUMWEAKSPOTS 8 +#define WEAKSPOTANIMTIME (3*TICRATE) + +typedef struct weakspot_t +{ + mobj_t *spot; + spottype_t type; + tic_t time; + UINT16 color; + boolean minimap; +} weakspot_t; + +#define BOSSHEALTHBARLEN 110 + +extern struct bossinfo +{ + boolean boss; ///< If true, then we are fighting a boss + UINT8 healthbar; ///< Actual health bar fill amount + UINT8 visualbar; ///< Tracks above, but with delay + fixed_t visualdiv; ///< How far apart health bar divisions should appear + tic_t visualbarimpact; ///< Bar jitter (on damage) + UINT8 healthbarpinch; ///< If actual health is lower than this, make it orange + UINT8 barlen; ///< The length of the bar (only reduced when a boss is deceased) + char *enemyname; ///< The name next to the bar + weakspot_t weakspots[NUMWEAKSPOTS]; ///< Array of weak spots (for minimap/object tracking) + boolean encore; ///< Copy of encore, just to make sure you can't cheat it with cvars + spottype_t doweakspotsound; ///< If nonzero, at least one weakspot was declared this tic + tic_t titleshow; ///< Show this many letters on the titlecard + sfxenum_t titlesound; ///< Sound to play when title typing + char *subtitle; ///< The subtitle under the titlecard +} bossinfo; + +void K_BossInfoTicker(void); +void K_InitBossHealthBar(const char *enemyname, const char *subtitle, sfxenum_t titlesound, fixed_t pinchmagnitude, UINT8 divisions); +void K_UpdateBossHealthBar(fixed_t magnitude, tic_t jitterlen); +void K_DeclareWeakspot(mobj_t *spot, spottype_t spottype, UINT16 color, boolean minimap); + +#endif diff --git a/src/k_bot.c b/src/k_bot.c index 44f9c27c2..70afca57b 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -27,6 +27,7 @@ #include "m_random.h" #include "r_things.h" // numskins #include "k_race.h" // finishBeamLine +#include "k_boss.h" /*-------------------------------------------------- @@ -166,7 +167,7 @@ void K_UpdateMatchRaceBots(void) } } - if (difficulty == 0) + if (difficulty == 0 || bossinfo.boss == true) { wantedbots = 0; } @@ -260,7 +261,10 @@ void K_UpdateMatchRaceBots(void) --------------------------------------------------*/ boolean K_PlayerUsesBotMovement(player_t *player) { - if (player->bot || player->exiting || player->quittime) + if (player->exiting || player->quittime) + return true; + + if (player->bot) return true; return false; @@ -1040,6 +1044,7 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) || player->mo->scale <= 1 || player->playerstate == PST_DEAD || leveltime <= introtime + || (player->exiting && !(gametyperules & GTR_CIRCUIT)) ) { // No need to do anything else. diff --git a/src/k_collide.c b/src/k_collide.c index 2a4fc73f4..21f028465 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -12,6 +12,30 @@ #include "doomdef.h" // Sink snipe print #include "g_game.h" // Sink snipe print +angle_t K_GetCollideAngle(mobj_t *t1, mobj_t *t2) +{ + fixed_t momux, momuy; + angle_t test; + + if (!(t1->flags & MF_PAPERCOLLISION)) + { + return R_PointToAngle2(t1->x, t1->y, t2->x, t2->y)+ANGLE_90; + } + + test = R_PointToAngle2(0, 0, t2->momx, t2->momy) + ANGLE_90 - t1->angle; + if (test > ANGLE_180) + test = t1->angle + ANGLE_180; + else + test = t1->angle; + + // intentional way around - sine... + momuy = P_AproxDistance(t2->momx, t2->momy); + momux = t2->momx - P_ReturnThrustY(t2, test, 2*momuy); + momuy = t2->momy - P_ReturnThrustX(t2, test, 2*momuy); + + return R_PointToAngle2(0, 0, momux, momuy); +} + boolean K_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) { boolean damageitem = false; @@ -20,7 +44,7 @@ boolean K_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) return true; - if (((t1->target == t2) || (t1->target == t2->target)) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) + if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; if (t1->health <= 0 || t2->health <= 0) @@ -61,11 +85,13 @@ boolean K_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) || t2->type == MT_BALLHOG) { // Other Item Damage + angle_t bounceangle = K_GetCollideAngle(t1, t2); + S_StartSound(t2, t2->info->deathsound); P_KillMobj(t2, t1, t1, DMG_NORMAL); P_SetObjectMomZ(t2, 8*FRACUNIT, false); - P_InstaThrust(t2, R_PointToAngle2(t1->x, t1->y, t2->x, t2->y)+ANGLE_90, 16*FRACUNIT); + P_InstaThrust(t2, bounceangle, 16*FRACUNIT); P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH); @@ -92,11 +118,12 @@ boolean K_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) if (damageitem) { // This Item Damage + angle_t bounceangle = K_GetCollideAngle(t2, t1); S_StartSound(t1, t1->info->deathsound); P_KillMobj(t1, t2, t2, DMG_NORMAL); P_SetObjectMomZ(t1, 8*FRACUNIT, false); - P_InstaThrust(t1, R_PointToAngle2(t2->x, t2->y, t1->x, t1->y)+ANGLE_90, 16*FRACUNIT); + P_InstaThrust(t1, bounceangle, 16*FRACUNIT); } if (sprung) @@ -114,7 +141,7 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2) if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) return true; - if (((t1->target == t2) || (t1->target == t2->target)) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) + if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; if (t1->health <= 0 || t2->health <= 0) @@ -154,11 +181,13 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2) || t2->type == MT_BALLHOG) { // Other Item Damage + angle_t bounceangle = K_GetCollideAngle(t1, t2); + S_StartSound(t2, t2->info->deathsound); P_KillMobj(t2, t1, t1, DMG_NORMAL); P_SetObjectMomZ(t2, 8*FRACUNIT, false); - P_InstaThrust(t2, R_PointToAngle2(t1->x, t1->y, t2->x, t2->y)+ANGLE_90, 16*FRACUNIT); + P_InstaThrust(t2, bounceangle, 16*FRACUNIT); P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH); @@ -180,11 +209,13 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2) if (damageitem) { // This Item Damage + angle_t bounceangle = K_GetCollideAngle(t2, t1); + S_StartSound(t1, t1->info->deathsound); P_KillMobj(t1, t2, t2, DMG_NORMAL); P_SetObjectMomZ(t1, 8*FRACUNIT, false); - P_InstaThrust(t1, R_PointToAngle2(t2->x, t2->y, t1->x, t1->y)+ANGLE_90, 16*FRACUNIT); + P_InstaThrust(t1, bounceangle, 16*FRACUNIT); } return true; @@ -270,7 +301,7 @@ boolean K_MineCollide(mobj_t *t1, mobj_t *t2) if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) return true; - if (((t1->target == t2) || (t1->target == t2->target)) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) + if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; if (t1->health <= 0 || t2->health <= 0) @@ -296,6 +327,8 @@ boolean K_MineCollide(mobj_t *t1, mobj_t *t2) || t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ_SHIELD) { // Bomb death + angle_t bounceangle = K_GetCollideAngle(t1, t2); + P_KillMobj(t1, t2, t2, DMG_NORMAL); // Other Item Damage @@ -303,7 +336,7 @@ boolean K_MineCollide(mobj_t *t1, mobj_t *t2) P_KillMobj(t2, t1, t1, DMG_NORMAL); P_SetObjectMomZ(t2, 8*FRACUNIT, false); - P_InstaThrust(t2, R_PointToAngle2(t1->x, t1->y, t2->x, t2->y)+ANGLE_90, 16*FRACUNIT); + P_InstaThrust(t2, bounceangle, 16*FRACUNIT); } else if (t2->flags & MF_SHOOTABLE) { @@ -346,7 +379,7 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) return true; - if (((t1->target == t2) || (t1->target == t2->target)) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) + if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; if (t1->health <= 0 || t2->health <= 0) @@ -380,6 +413,8 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) || t2->type == MT_BALLHOG) { // Other Item Damage + angle_t bounceangle = K_GetCollideAngle(t1, t2); + if (t2->eflags & MFE_VERTICALFLIP) t2->z -= t2->height; else @@ -389,7 +424,7 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) P_KillMobj(t2, t1, t1, DMG_NORMAL); P_SetObjectMomZ(t2, 8*FRACUNIT, false); - P_InstaThrust(t2, R_PointToAngle2(t1->x, t1->y, t2->x, t2->y)+ANGLE_90, 16*FRACUNIT); + P_InstaThrust(t2, bounceangle, 16*FRACUNIT); P_SpawnMobj(t2->x/2 + t1->x/2, t2->y/2 + t1->y/2, t2->z/2 + t1->z/2, MT_ITEMCLASH); @@ -416,7 +451,7 @@ boolean K_KitchenSinkCollide(mobj_t *t1, mobj_t *t2) if ((t1->threshold > 0 && t2->hitlag > 0) || (t2->threshold > 0 && t1->hitlag > 0)) return true; - if (((t1->target == t2) || (t1->target == t2->target)) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) + if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; if (t2->player) diff --git a/src/k_collide.h b/src/k_collide.h index e005bc497..3be27c803 100644 --- a/src/k_collide.h +++ b/src/k_collide.h @@ -4,6 +4,7 @@ #include "doomtype.h" #include "p_mobj.h" +angle_t K_GetCollideAngle(mobj_t *t1, mobj_t *t2); boolean K_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2); boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2); boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2); diff --git a/src/k_grandprix.c b/src/k_grandprix.c index c9e6b391b..c07bf2a81 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -11,6 +11,7 @@ /// \brief Grand Prix mode game logic & bot behaviors #include "k_grandprix.h" +#include "k_boss.h" #include "doomdef.h" #include "d_player.h" #include "g_game.h" @@ -595,6 +596,12 @@ boolean K_CanChangeRules(void) return false; } + if (bossinfo.boss == true) + { + // Don't cheat the boss! + return false; + } + if (modeattacking == true) { // Don't cheat the rules of Time Trials! diff --git a/src/k_hud.c b/src/k_hud.c index a15f24409..989f319e8 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -12,6 +12,7 @@ #include "k_hud.h" #include "k_kart.h" #include "k_battle.h" +#include "k_boss.h" #include "k_color.h" #include "k_director.h" #include "screen.h" @@ -33,6 +34,7 @@ #include "s_sound.h" #include "r_things.h" #include "r_fps.h" +#include "m_random.h" #define NUMPOSNUMS 10 #define NUMPOSFRAMES 7 // White, three blues, three reds @@ -164,6 +166,9 @@ static patch_t *kp_cpu; static patch_t *kp_nametagstem; +static patch_t *kp_bossbar[8]; +static patch_t *kp_bossret[4]; + static patch_t *kp_trickcool[2]; void K_LoadKartHUDGraphics(void) @@ -606,6 +611,20 @@ void K_LoadKartHUDGraphics(void) kp_nametagstem = (patch_t *) W_CachePatchName("K_NAMEST", PU_HUDGFX); + sprintf(buffer, "K_BOSB0x"); + for (i = 0; i < 8; i++) + { + buffer[7] = '0'+((i+1)%10); + kp_bossbar[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + sprintf(buffer, "K_BOSR0x"); + for (i = 0; i < 4; i++) + { + buffer[7] = '0'+((i+1)%10); + kp_bossret[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + kp_trickcool[0] = W_CachePatchName("K_COOL1", PU_HUDGFX); kp_trickcool[1] = W_CachePatchName("K_COOL2", PU_HUDGFX); } @@ -759,14 +778,22 @@ void K_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 du if (options & V_SLIDEIN) { - const tic_t length = TICRATE/2; + const tic_t length = TICRATE/4; + tic_t timer = lt_exitticker; + if (bossinfo.boss == true) + { + if (leveltime <= 3) + timer = 0; + else + timer = leveltime-3; + } - if (lt_exitticker < length) + if (timer < length) { boolean slidefromright = false; - const INT32 offsetAmount = (screenwidth * FRACUNIT) / length; - fixed_t offset = (screenwidth * FRACUNIT) - (lt_exitticker * offsetAmount); + const INT32 offsetAmount = (screenwidth * FRACUNIT/2) / length; + fixed_t offset = (screenwidth * FRACUNIT/2) - (timer * offsetAmount); offset += FixedMul(offsetAmount, renderdeltatics); offset /= FRACUNIT; @@ -1434,7 +1461,7 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI if (!mode) { splitflags = V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP|V_SNAPTORIGHT|V_SPLITSCREEN; - if (cv_timelimit.value && timelimitintics > 0) + if (cv_timelimit.value && timelimitintics > 0 && !bossinfo.boss) // TODO { if (drawtime >= timelimitintics) drawtime = 0; @@ -1867,6 +1894,115 @@ static boolean K_drawKartPositionFaces(void) return false; } +static void K_drawBossHealthBar(void) +{ + UINT8 i = 0, barstatus = 1, randlen = 0, darken = 0; + const INT32 startx = BASEVIDWIDTH - 23; + INT32 starty = BASEVIDHEIGHT - 25; + INT32 rolrand = 0; + boolean randsign = false; + + if (bossinfo.barlen <= 1) + return; + + // Entire bar juddering! + if (lt_exitticker < (TICRATE/2)) + ; + else if (bossinfo.visualbarimpact) + { + INT32 mag = min((bossinfo.visualbarimpact/4) + 1, 8); + if (bossinfo.visualbarimpact & 1) + starty -= mag; + else + starty += mag; + } + + if (bossinfo.enemyname) + V_DrawRightAlignedThinString(startx, starty-10, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_6WIDTHSPACE, bossinfo.enemyname); + + // Used for colour and randomisation. + if (bossinfo.healthbar <= (bossinfo.visualdiv/FRACUNIT)) + { + barstatus = 3; + } + else if (bossinfo.healthbar <= bossinfo.healthbarpinch) + { + barstatus = 2; + } + + randlen = M_RandomKey(bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)))+1; + randsign = M_RandomChance(FRACUNIT/2); + + // Right wing. + V_DrawScaledPatch(startx-1, starty, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_FLIP, kp_bossbar[0]); + + // Draw the bar itself... + while (i < bossinfo.barlen) + { + V_DrawScaledPatch(startx-i, starty, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT, kp_bossbar[1]); + if (i < bossinfo.visualbar) + { + randlen--; + if (!randlen) + { + randlen = M_RandomKey(bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)))+1; + if (barstatus > 1) + { + rolrand = M_RandomKey(barstatus)+1; + } + else + { + rolrand = 1; + } + if (randsign) + { + rolrand = -rolrand; + } + randsign = !randsign; + } + else + { + rolrand = 0; + } + if (lt_exitticker < (TICRATE/2)) + ; + else if ((bossinfo.visualbar - i) < (INT32)(bossinfo.visualbarimpact/8)) + { + if (bossinfo.visualbarimpact & 1) + rolrand += (bossinfo.visualbar - i); + else + rolrand -= (bossinfo.visualbar - i); + } + if (bossinfo.visualdiv) + { + fixed_t work = 0; + if ((i+1) == bossinfo.visualbar) + darken = 1; + else + { + darken = 0; + // a hybrid fixed-int modulo... + while ((work/FRACUNIT) < bossinfo.visualbar) + { + if (work/FRACUNIT != i) + { + work += bossinfo.visualdiv; + continue; + } + darken = 1; + break; + } + } + } + V_DrawScaledPatch(startx-i, starty+rolrand, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT, kp_bossbar[(2*barstatus)+darken]); + } + i++; + } + + // Left wing. + V_DrawScaledPatch(startx-i, starty, V_HUDTRANS|V_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT, kp_bossbar[0]); +} + static void K_drawKartEmeralds(void) { static const INT32 emeraldOffsets[7][2] = { @@ -2233,7 +2369,9 @@ static void K_drawKartAccessibilityIcons(INT32 fx) if (r_splitscreen < 2) // adjust to speedometer height { - if (gametype == GT_BATTLE) + if (bossinfo.boss) + fy += 8; + else if (gametype == GT_BATTLE) fy -= 4; } else @@ -2247,7 +2385,7 @@ static void K_drawKartAccessibilityIcons(INT32 fx) } } - if (stplyr->pflags & PF_KICKSTARTACCEL) // just KICKSTARTACCEL right now, maybe more later + if ((stplyr->pflags & PF_KICKSTARTACCEL) && gametype != GT_BATTLE) // just KICKSTARTACCEL right now, maybe more later { fil = 7-(stplyr->kickstartaccel*7)/ACCEL_KICKSTART; i = 7; @@ -2320,7 +2458,9 @@ static void K_drawKartSpeedometer(void) numbers[1] = ((convSpeed / 10) % 10); numbers[2] = (convSpeed % 10); - if (gametype == GT_BATTLE) + if (bossinfo.boss) + battleoffset = 8; + else if (gametype == GT_BATTLE) battleoffset = -4; V_DrawScaledPatch(LAPS_X, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometersticker); @@ -2470,7 +2610,10 @@ static void K_drawKartBumpersOrKarma(void) V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_bumpersticker, colormap); // TODO BETTER HUD - V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d %d", stplyr->bumpers, maxbumper, stplyr->overtimekarma / TICRATE)); + if (bossinfo.boss) + V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", stplyr->bumpers, maxbumper)); + else + V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d %d", stplyr->bumpers, maxbumper, stplyr->overtimekarma / TICRATE)); } } } @@ -2770,6 +2913,43 @@ static void K_DrawNameTagForPlayer(fixed_t x, fixed_t y, player_t *p, UINT8 cnum V_DrawThinStringAtFixed(x + (5*FRACUNIT), y - (26*FRACUNIT), V_6WIDTHSPACE|V_ALLOWLOWERCASE|clr, player_names[p - players]); } +typedef struct weakspotdraw_t +{ + UINT8 i; + INT32 x; + INT32 y; + boolean candrawtag; +} weakspotdraw_t; + +static void K_DrawWeakSpot(weakspotdraw_t *ws) +{ + UINT8 *colormap; + UINT8 j = (bossinfo.weakspots[ws->i].type == SPOT_BUMP) ? 1 : 0; + tic_t flashtime = ~1; // arbitrary high even number + + if (bossinfo.weakspots[ws->i].time < TICRATE) + { + if (bossinfo.weakspots[ws->i].time & 1) + return; + + flashtime = bossinfo.weakspots[ws->i].time; + } + else if (bossinfo.weakspots[ws->i].time > (WEAKSPOTANIMTIME - TICRATE)) + flashtime = WEAKSPOTANIMTIME - bossinfo.weakspots[ws->i].time; + + if (flashtime & 1) + colormap = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE); + else + colormap = R_GetTranslationColormap(TC_RAINBOW, bossinfo.weakspots[ws->i].color, GTC_CACHE); + + V_DrawFixedPatch(ws->x, ws->y, FRACUNIT, 0, kp_bossret[j], colormap); + + if (!ws->candrawtag || flashtime & 1 || flashtime < TICRATE/2) + return; + + V_DrawFixedPatch(ws->x, ws->y, FRACUNIT, 0, kp_bossret[j+1], colormap); +} + static void K_drawKartNameTags(void) { const fixed_t maxdistance = 8192*mapobjectscale; @@ -2818,6 +2998,81 @@ static void K_drawKartNameTags(void) c.z = R_InterpolateFixed(stplyr->mo->old_z, stplyr->mo->z); } + // Maybe shouldn't be handling this here... but the camera info is too good. + if (bossinfo.boss) + { + weakspotdraw_t weakspotdraw[NUMWEAKSPOTS]; + UINT8 numdraw = 0; + boolean onleft = false; + + for (i = 0; i < NUMWEAKSPOTS; i++) + { + trackingResult_t result; + vector3_t v; + + if (bossinfo.weakspots[i].spot == NULL || P_MobjWasRemoved(bossinfo.weakspots[i].spot)) + { + // No object + continue; + } + + if (bossinfo.weakspots[i].time == 0 || bossinfo.weakspots[i].type == SPOT_NONE) + { + // not visible + continue; + } + + v.x = R_InterpolateFixed(bossinfo.weakspots[i].spot->old_x, bossinfo.weakspots[i].spot->x); + v.y = R_InterpolateFixed(bossinfo.weakspots[i].spot->old_y, bossinfo.weakspots[i].spot->y); + v.z = R_InterpolateFixed(bossinfo.weakspots[i].spot->old_z, bossinfo.weakspots[i].spot->z); + + v.z += (bossinfo.weakspots[i].spot->height / 2); + + K_ObjectTracking(&result, &v, cnum, 0); + if (result.onScreen == false) + { + continue; + } + + weakspotdraw[numdraw].i = i; + weakspotdraw[numdraw].x = result.x; + weakspotdraw[numdraw].y = result.y; + weakspotdraw[numdraw].candrawtag = true; + + for (j = 0; j < numdraw; j++) + { + if (abs(weakspotdraw[j].x - weakspotdraw[numdraw].x) > 50*FRACUNIT) + { + continue; + } + + onleft = (weakspotdraw[j].x < weakspotdraw[numdraw].x); + + if (abs((onleft ? -5 : 5) + + weakspotdraw[j].y - weakspotdraw[numdraw].y) > 18*FRACUNIT) + { + continue; + } + + if (weakspotdraw[j].x < weakspotdraw[numdraw].x) + { + weakspotdraw[j].candrawtag = false; + break; + } + + weakspotdraw[numdraw].candrawtag = false; + break; + } + + numdraw++; + } + + for (i = 0; i < numdraw; i++) + { + K_DrawWeakSpot(&weakspotdraw[i]); + } + } + for (i = 0; i < MAXPLAYERS; i++) { player_t *ntplayer = &players[i]; @@ -3252,6 +3507,34 @@ static void K_drawKartMinimap(void) splitflags &= ~V_HUDTRANSHALF; splitflags |= V_HUDTRANS; + // ...but first, any boss targets. + if (bossinfo.boss) + { + for (i = 0; i < NUMWEAKSPOTS; i++) + { + // exists at all? + if (bossinfo.weakspots[i].spot == NULL || P_MobjWasRemoved(bossinfo.weakspots[i].spot)) + continue; + // shows on the minimap? + if (bossinfo.weakspots[i].minimap == false) + continue; + // in the flashing period? + if ((bossinfo.weakspots[i].time > (WEAKSPOTANIMTIME-(TICRATE/2))) && (bossinfo.weakspots[i].time & 1)) + continue; + + colormap = NULL; + + if (bossinfo.weakspots[i].color) + colormap = R_GetTranslationColormap(TC_RAINBOW, bossinfo.weakspots[i].color, GTC_CACHE); + + interpx = R_InterpolateFixed(bossinfo.weakspots[i].spot->old_x, bossinfo.weakspots[i].spot->x); + interpy = R_InterpolateFixed(bossinfo.weakspots[i].spot->old_y, bossinfo.weakspots[i].spot->y); + + // temporary graphic? + K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, colormap, AutomapPic); + } + } + for (i = 0; i < numlocalplayers; i++) { if (i == -1) @@ -3482,7 +3765,8 @@ static void K_drawKartStartCountdown(void) } else if (leveltime >= introtime && leveltime < starttime-(3*TICRATE)) { - K_drawKartStartBulbs(); + if (bossinfo.boss == false) + K_drawKartStartBulbs(); } else { @@ -4412,14 +4696,15 @@ void K_drawKartHUD(void) K_drawKartAccessibilityIcons((r_splitscreen > 1) ? 0 : 8); } - if (gametyperules & GTR_SPHERES) + if (!bossinfo.boss && gametyperules & GTR_SPHERES) { K_drawBlueSphereMeter(); } } // Draw the countdowns after everything else. - if (leveltime >= introtime + if (starttime != introtime + && leveltime >= introtime && leveltime < starttime+TICRATE) { K_drawKartStartCountdown(); @@ -4450,6 +4735,18 @@ void K_drawKartHUD(void) if (stplyr->karthud[khud_trickcool]) K_drawTrickCool(); + if (gametype == GT_BATTLE) + { + if (bossinfo.boss) + { + K_drawBossHealthBar(); + } + else if (!freecam) + { + K_drawKartEmeralds(); + } + } + if (modeattacking || freecam) // everything after here is MP and debug only return; @@ -4457,7 +4754,7 @@ void K_drawKartHUD(void) V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem); // Draw FREE PLAY. - if (isfreeplay && !stplyr->spectator) + if (isfreeplay && !bossinfo.boss && !stplyr->spectator) { if (LUA_HudEnabled(hud_freeplay)) K_drawKartFreePlay(); @@ -4510,9 +4807,4 @@ void K_drawKartHUD(void) K_DrawWaypointDebugger(); K_DrawDirectorDebugger(); - - if (gametype == GT_BATTLE) - { - K_drawKartEmeralds(); - } } diff --git a/src/k_kart.c b/src/k_kart.c index bf63ad04e..6539b8860 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6,6 +6,7 @@ #include "k_kart.h" #include "k_battle.h" +#include "k_boss.h" #include "k_pwrlv.h" #include "k_color.h" #include "k_respawn.h" @@ -56,6 +57,10 @@ void K_TimerInit(void) UINT8 i; UINT8 numPlayers = 0;//, numspec = 0; + // Bosses handle it elsewhere! + if (bossinfo.boss == true) + return; + for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) @@ -271,6 +276,9 @@ boolean K_IsPlayerLosing(player_t *player) if (battlecapsules && player->bumpers <= 0) return true; // DNF in break the capsules + if (bossinfo.boss) + return (player->bumpers <= 0); // anything short of DNF is COOL + if (player->position == 1) return false; @@ -1043,13 +1051,20 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) } else if (gametype == GT_BATTLE) { - if (mashed && (modeattacking || cv_banana.value)) // ANY mashed value? You get a banana. + if (mashed && (modeattacking || bossinfo.boss || cv_banana.value)) // ANY mashed value? You get a banana. { K_KartGetItemResult(player, KITEM_BANANA); player->karthud[khud_itemblinkmode] = 1; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolm); } + else if (bossinfo.boss) + { + K_KartGetItemResult(player, KITEM_ORBINAUT); + player->karthud[khud_itemblinkmode] = 0; + if (P_IsDisplayPlayer(player)) + S_StartSound(NULL, sfx_itrolf); + } else { if (modeattacking || cv_tripleorbinaut.value) // Waited patiently? You get Orbinaut x3! @@ -3545,7 +3560,11 @@ void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers) player->karmadelay = comebacktime; - if (netgame) + if (bossinfo.boss) + { + P_DoTimeOver(player); + } + else if (netgame) { CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]); } @@ -5578,7 +5597,7 @@ void K_DropHnextList(player_t *player, boolean keepshields) dropwork->angle = work->angle; P_SetScale(dropwork, work->scale); - dropwork->destscale = work->destscale; + dropwork->destscale = K_ItemScaleForPlayer(player); //work->destscale; dropwork->scalespeed = work->scalespeed; dropwork->flags |= MF_NOCLIPTHING; diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 860727184..22c386191 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -9,6 +9,7 @@ #include "m_cond.h" // M_UpdateUnlockablesAndExtraEmblems #include "p_tick.h" // leveltime #include "k_grandprix.h" +#include "k_boss.h" // Online rankings for the main gametypes. // This array is saved to the gamedata. @@ -30,7 +31,7 @@ SINT8 K_UsingPowerLevels(void) { SINT8 pt = PWRLV_DISABLED; - if (!cv_kartusepwrlv.value || !(netgame || (demo.playback && demo.netgame)) || grandprixinfo.gp == true) + if (!cv_kartusepwrlv.value || !(netgame || (demo.playback && demo.netgame)) || grandprixinfo.gp == true || bossinfo.boss == true) { return PWRLV_DISABLED; } diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 596f8b158..a0dc63781 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -28,6 +28,8 @@ #include "console.h" #include "k_kart.h" // SRB2Kart #include "k_battle.h" +#include "k_boss.h" +#include "k_collide.h" #include "k_color.h" #include "k_hud.h" #include "d_netcmd.h" // IsPlayerAdmin @@ -2921,8 +2923,9 @@ static int lib_sSoundPlaying(lua_State *L) INLEVEL if (id >= NUMSFX) return luaL_error(L, "sfx %d out of range (0 - %d)", id, NUMSFX-1); - if (!GetValidSoundOrigin(L, &origin)) - return LUA_ErrInvalid(L, "mobj_t/sector_t"); + if (!lua_isnil(L, 1)) + if (!GetValidSoundOrigin(L, &origin)) + return LUA_ErrInvalid(L, "mobj_t/sector_t"); lua_pushboolean(L, S_SoundPlaying(origin, id)); return 1; } @@ -3807,6 +3810,66 @@ static int lib_kGetItemPatch(lua_State *L) return 1; } +static int lib_kGetCollideAngle(lua_State *L) +{ + mobj_t *t1 = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); + mobj_t *t2 = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ)); + //HUDSAFE + if (!t1) + return LUA_ErrInvalid(L, "mobj_t"); + if (!t2) + return LUA_ErrInvalid(L, "mobj_t"); + lua_pushinteger(L, K_GetCollideAngle(t1, t2)); + return 1; +} + +static int lib_kAddHitLag(lua_State *L) +{ + mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); + tic_t tics = (tic_t)luaL_checkinteger(L, 2); + boolean fromdamage = lua_opttrueboolean(L, 3); + NOHUD + if (!mo) + return LUA_ErrInvalid(L, "mobj_t"); + K_AddHitLag(mo, tics, fromdamage); + return 0; +} + + +static int lib_kInitBossHealthBar(lua_State *L) +{ + const char *enemyname = luaL_checkstring(L, 1); + const char *subtitle = luaL_checkstring(L, 2); + sfxenum_t titlesound = luaL_checkinteger(L, 3); + fixed_t pinchmagnitude = luaL_checkfixed(L, 4); + UINT8 divisions = (UINT8)luaL_checkinteger(L, 5); + NOHUD + K_InitBossHealthBar(enemyname, subtitle, titlesound, pinchmagnitude, divisions); + return 0; +} + +static int lib_kUpdateBossHealthBar(lua_State *L) +{ + fixed_t magnitude = luaL_checkfixed(L, 1); + tic_t jitterlen = (tic_t)luaL_checkinteger(L, 2); + NOHUD + K_UpdateBossHealthBar(magnitude, jitterlen); + return 0; +} + +static int lib_kDeclareWeakspot(lua_State *L) +{ + mobj_t *spot = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); + spottype_t spottype = luaL_checkinteger(L, 2); + UINT16 color = luaL_checkinteger(L, 3); + boolean minimap = lua_optboolean(L, 4); + NOHUD + if (!spot) + return LUA_ErrInvalid(L, "mobj_t"); + K_DeclareWeakspot(spot, spottype, color, minimap); + return 0; +} + static luaL_Reg lib[] = { {"print", lib_print}, {"chatprint", lib_chatprint}, @@ -4077,6 +4140,14 @@ static luaL_Reg lib[] = { {"K_GetKartFlashing",lib_kGetKartFlashing}, {"K_GetItemPatch",lib_kGetItemPatch}, + {"K_GetCollideAngle",lib_kGetCollideAngle}, + {"K_AddHitLag",lib_kAddHitLag}, + + // k_boss + {"K_InitBossHealthBar", lib_kInitBossHealthBar}, + {"K_UpdateBossHealthBar", lib_kUpdateBossHealthBar}, + {"K_DeclareWeakspot", lib_kDeclareWeakspot}, + {NULL, NULL} }; diff --git a/src/lua_script.c b/src/lua_script.c index 94ba64767..254c510e3 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -300,6 +300,12 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"leveltime")) { lua_pushinteger(L, leveltime); return 1; + } else if (fastcmp(word,"introtime")) { + lua_pushinteger(L, introtime); + return 1; + } else if (fastcmp(word,"starttime")) { + lua_pushinteger(L, starttime); + return 1; } else if (fastcmp(word,"defrosting")) { lua_pushinteger(L, hook_defrosting); return 1; diff --git a/src/m_fixed.h b/src/m_fixed.h index 289ca442a..49108ab93 100644 --- a/src/m_fixed.h +++ b/src/m_fixed.h @@ -33,6 +33,10 @@ unit used as fixed_t */ +#if (FRACBITS == 16) +#define M_TAU_FIXED 411769 +#endif + typedef INT32 fixed_t; /*! diff --git a/src/m_menu.c b/src/m_menu.c index 3a5be46a9..c81958e2f 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -63,6 +63,7 @@ #include "d_player.h" // KITEM_ constants #include "k_color.h" #include "k_grandprix.h" +#include "k_boss.h" #include "i_joy.h" // for joystick menu controls @@ -154,7 +155,8 @@ typedef enum LLM_CREATESERVER, LLM_LEVELSELECT, LLM_TIMEATTACK, - LLM_BREAKTHECAPSULES + LLM_BREAKTHECAPSULES, + LLM_BOSS } levellist_mode_t; levellist_mode_t levellistmode = LLM_CREATESERVER; @@ -253,6 +255,8 @@ menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef, MISC_ChangeSpectateDef; // Single Player static void M_GrandPrixTemp(INT32 choice); static void M_StartGrandPrix(INT32 choice); +static void M_BossTemp(INT32 choice); +static void M_StartBoss(INT32 choice); static void M_TimeAttack(INT32 choice); static boolean M_QuitTimeAttackMenu(void); static void M_BreakTheCapsules(INT32 choice); @@ -849,13 +853,15 @@ static menuitem_t SP_MainMenu[] = {IT_STRING|IT_CALL, NULL, "Grand Prix", M_GrandPrixTemp, 92}, {IT_SECRET, NULL, "Time Attack", M_TimeAttack, 100}, {IT_SECRET, NULL, "Break the Capsules", M_BreakTheCapsules, 108}, + {IT_STRING|IT_CALL, NULL, "Boss Missions", M_BossTemp, 116}, }; enum { spgrandprix, sptimeattack, - spbreakthecapsules + spbreakthecapsules, + spboss }; // Single Player Load Game @@ -871,6 +877,17 @@ static menuitem_t SP_GrandPrixPlaceholderMenu[] = {IT_STRING|IT_CALL, NULL, "Start", M_StartGrandPrix, 80}, }; +static menuitem_t SP_BossPlaceholderMenu[] = +{ + {IT_STRING|IT_CVAR, NULL, "Character", &cv_chooseskin, 50}, + {IT_STRING|IT_CVAR, NULL, "Color", &cv_playercolor[0], 58}, + + {IT_STRING|IT_CVAR, NULL, "Encore Rematch", &cv_dummygpencore, 68}, + + {IT_STRING|IT_CVAR, NULL, "Boss", &cv_nextmap, 78}, + {IT_STRING|IT_CALL, NULL, "Start", M_StartBoss, 130}, +}; + // Single Player Time Attack static menuitem_t SP_TimeAttackMenu[] = { @@ -1747,7 +1764,7 @@ inline static void M_GetGametypeColor(void) else gt = gametype; - if (gt == GT_BATTLE) + if (gt == GT_BATTLE || levellistmode == LLM_BOSS) { highlightflags = V_REDMAP; warningflags = V_ORANGEMAP; @@ -1842,6 +1859,8 @@ menu_t SP_LevelStatsDef = static menu_t SP_GrandPrixTempDef = DEFAULTMENUSTYLE(MN_NONE, NULL, SP_GrandPrixPlaceholderMenu, &MainDef, 60, 30); +static menu_t SP_BossTempDef = MAPICONMENUSTYLE(NULL, SP_BossPlaceholderMenu, &MainDef); + static menu_t SP_TimeAttackDef = { MN_NONE, @@ -4404,7 +4423,7 @@ boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt) { // Random map! if (mapnum == -1) - return (gamestate != GS_TIMEATTACK && !modeattacking); + return (levellistmode == LLM_CREATESERVER); // Does the map exist? if (!mapheaderinfo[mapnum]) @@ -4459,6 +4478,13 @@ boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt) return false; return true; + + case LLM_BOSS: + if (!(mapheaderinfo[mapnum]->typeoflevel & TOL_BOSS)) + return false; + + return true; + default: return false; } @@ -4935,8 +4961,12 @@ static boolean M_AddonsRefresh(void) return true; } +#ifdef DEVELOP + prevmajormods = majormods; +#else if (!majormods && prevmajormods) prevmajormods = false; +#endif if ((refreshdirmenu & REFRESHDIR_ADDFILE) || (majormods && !prevmajormods)) { @@ -6339,7 +6369,7 @@ static void M_RetryResponse(INT32 ch) static void M_Retry(INT32 choice) { (void)choice; - M_StartMessage(M_GetText("Start this race over?\n\n(Press 'Y' to confirm)\n"),M_RetryResponse,MM_YESNO); + M_StartMessage(va("Start this %s over?\n\n(Press 'Y' to confirm)\n", (bossinfo.boss == true) ? "boss" : "race"),M_RetryResponse,MM_YESNO); } static void M_SelectableClearMenus(INT32 choice) @@ -6826,6 +6856,7 @@ static void M_SinglePlayerMenu(INT32 choice) (M_SecretUnlocked(SECRET_TIMEATTACK)) ? IT_CALL|IT_STRING : IT_SECRET; SP_MainMenu[spbreakthecapsules].status = (M_SecretUnlocked(SECRET_BREAKTHECAPSULES)) ? IT_CALL|IT_STRING : IT_SECRET; + SP_MainMenu[spboss].status = IT_CALL|IT_STRING; M_SetupNextMenu(&SP_MainDef); } @@ -7714,6 +7745,29 @@ static void M_GrandPrixTemp(INT32 choice) M_SetupNextMenu(&SP_GrandPrixTempDef); } +static void M_BossTemp(INT32 choice) +{ + (void)choice; + + levellistmode = LLM_BOSS; // Don't be dependent on cv_newgametype + + if (M_CountLevelsToShowInList() == 0) + { + M_StartMessage(M_GetText("No bosses found.\n"),NULL,MM_NOTHING); + return; + } + + M_PatchSkinNameTable(); + + M_PrepareLevelSelect(); + + if (cv_nextmap.value) + Nextmap_OnChange(); + else + CV_AddValue(&cv_nextmap, 1); + M_SetupNextMenu(&SP_BossTempDef); +} + // Start Grand Prix! static void M_StartGrandPrix(INT32 choice) { @@ -7778,6 +7832,32 @@ static void M_StartGrandPrix(INT32 choice) ); } +// Start Boss! +static void M_StartBoss(INT32 choice) +{ + (void)choice; + + M_ClearMenus(true); + + // Reset boss info + if (bossinfo.enemyname) + Z_Free(bossinfo.enemyname); + if (bossinfo.subtitle) + Z_Free(bossinfo.subtitle); + memset(&bossinfo, 0, sizeof(struct bossinfo)); + + bossinfo.boss = true; + bossinfo.encore = (boolean)(cv_dummygpencore.value); + + G_DeferedInitNew( + false, + G_BuildMapName(cv_nextmap.value), + (UINT8)(cv_chooseskin.value - 1), + (UINT8)(cv_splitplayers.value - 1), + false + ); +} + // =========== // MODE ATTACK // =========== @@ -8807,14 +8887,14 @@ static INT32 M_FindFirstMap(INT32 gtype) { INT32 i; - if (mapheaderinfo[gamemap] && (mapheaderinfo[gamemap]->typeoflevel & gtype)) + if (mapheaderinfo[gamemap] && (mapheaderinfo[gamemap]->typeoflevel & gametypetol[gtype])) return gamemap; for (i = 0; i < NUMMAPS; i++) { if (!mapheaderinfo[i]) continue; - if (!(mapheaderinfo[i]->typeoflevel & gtype)) + if (!(mapheaderinfo[i]->typeoflevel & gametypetol[gtype])) continue; return i + 1; } diff --git a/src/p_enemy.c b/src/p_enemy.c index 04e77bd8f..5e2f6b1b5 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -9432,7 +9432,7 @@ void A_ForceWin(mobj_t *actor) for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && ((players[i].mo && players[i].mo->health) - || ((netgame || multiplayer) && players[i].lives))) + && !(players[i].pflags & PF_NOCONTEST))) break; } @@ -11089,14 +11089,10 @@ void A_Boss5Jump(mobj_t *actor) if (!actor->tracer) return; // Don't even bother if we've got nothing to aim at. - // Look up actor's current gravity situation - if (actor->subsector->sector->gravity) - g = FixedMul(gravity,(FixedDiv(*actor->subsector->sector->gravity>>FRACBITS, 1000))); - else - g = gravity; + g = FixedMul(gravity, mapobjectscale); // Look up distance between actor and its tracer - x = P_AproxDistance(actor->tracer->x - actor->x, actor->tracer->y - actor->y); + x = FixedHypot(actor->tracer->x - actor->x, actor->tracer->y - actor->y); // Look up height difference between actor and its tracer y = actor->tracer->z - actor->z; @@ -13302,6 +13298,7 @@ void A_ItemPop(mobj_t *actor) remains->flags = actor->flags; // Transfer flags remains->flags2 = actor->flags2; // Transfer flags2 remains->fuse = actor->fuse; // Transfer respawn timer + remains->cvmem = leveltime; remains->threshold = (actor->threshold == 70 ? 70 : (actor->threshold == 69 ? 69 : 68)); remains->skin = NULL; remains->spawnpoint = actor->spawnpoint; diff --git a/src/p_mobj.c b/src/p_mobj.c index 2164038be..53ca8740d 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -37,6 +37,7 @@ // SRB2kart #include "k_kart.h" #include "k_battle.h" +#include "k_boss.h" #include "k_color.h" #include "k_respawn.h" #include "k_bot.h" @@ -4250,6 +4251,9 @@ boolean P_SupermanLook4Players(mobj_t *actor) if (players[c].mo->health <= 0) continue; // dead + if ((gametyperules & GTR_BUMPERS) && players[c].bumpers <= 0) + continue; // other dead + playersinthegame[stop] = &players[c]; stop++; } @@ -4970,6 +4974,9 @@ void P_RunOverlays(void) mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP) | (mo->target->eflags & MFE_VERTICALFLIP); mo->scale = mo->destscale = mo->target->scale; mo->angle = mo->target->angle + mo->movedir; + mo->rollangle = mo->target->rollangle; + mo->pitch = mo->target->pitch; + mo->roll = mo->target->roll; if ((mo->flags & MF_DONTENCOREMAP) != (mo->target->flags & MF_DONTENCOREMAP)) mo->flags ^= MF_DONTENCOREMAP; @@ -5360,6 +5367,8 @@ static void P_MobjSceneryThink(mobj_t *mobj) } else P_AddOverlay(mobj); + if (mobj->target->hitlag) // move to the correct position, update to the correct properties, but DON'T STATE-ANIMATE + return; break; case MT_WATERDROP: P_SceneryCheckWater(mobj); @@ -7850,7 +7859,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->extravalue1 = 1; } - if (!S_SoundPlaying(mobj, mobj->info->attacksound)) + if (mobj->extravalue1 != 2 && !S_SoundPlaying(mobj, mobj->info->attacksound)) S_StartSound(mobj, mobj->info->attacksound); if (mobj->extravalue2 <= 8) // Short delay @@ -8917,6 +8926,8 @@ static boolean P_FuseThink(mobj_t *mobj) { P_SpawnMapThing(mobj->spawnpoint); newmobj = mobj->spawnpoint->mobj; // this is set to the new mobj in P_SpawnMapThing + if (nummapboxes > 0) + nummapboxes--; // just to avoid doubling up } else newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->type); @@ -10633,12 +10644,17 @@ void P_RespawnBattleBoxes(void) if (box->type != MT_RANDOMITEM || box->threshold != 68 || box->fuse) // only popped items continue; + if ((tic_t)box->cvmem+1 >= leveltime) // This one was just popped, don't respawn! + continue; + // Respawn from mapthing if you have one! if (box->spawnpoint) { P_SpawnMapThing(box->spawnpoint); newmobj = box->spawnpoint->mobj; // this is set to the new mobj in P_SpawnMapThing P_SpawnMobj(box->spawnpoint->mobj->x, box->spawnpoint->mobj->y, box->spawnpoint->mobj->z, MT_EXPLODE); // poof into existance + if (nummapboxes > 0) + nummapboxes--; // just to avoid doubling up } else { @@ -10650,9 +10666,8 @@ void P_RespawnBattleBoxes(void) newmobj->flags2 = box->flags2; P_RemoveMobj(box); // make sure they disappear - numgotboxes--; // you've restored a box, remove it from the count - if (numgotboxes < 0) - numgotboxes = 0; + if (numgotboxes > 0) + numgotboxes--; // you've restored a box, remove it from the count } } @@ -10678,8 +10693,8 @@ void P_RespawnSpecials(void) INT32 time = 30*TICRATE; // Respawn things in empty dedicated servers mapthing_t *mthing = NULL; - //if (!(gametyperules & GTR_CIRCUIT) && numgotboxes >= (4*nummapboxes/5)) // Battle Mode respawns all boxes in a different way - //P_RespawnBattleBoxes(); + if (!(gametyperules & GTR_CIRCUIT) && numgotboxes >= (4*nummapboxes/5)) // Battle Mode respawns all boxes in a different way + P_RespawnBattleBoxes(); // wait time depends on player count for (p = 0; p < MAXPLAYERS; p++) @@ -11271,6 +11286,11 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i) break; } + // No bosses outside of a combat situation. + // (just in case we want boss arenas to do double duty as battle maps) + if (!bossinfo.boss && (mobjinfo[i].flags & MF_BOSS)) + return false; + if (metalrecording) // Metal Sonic can't use these things. { if (mobjinfo[i].flags & (MF_ENEMY|MF_BOSS)) @@ -11287,7 +11307,7 @@ static mobjtype_t P_GetMobjtypeSubstitute(mapthing_t *mthing, mobjtype_t i) if ((gametyperules & GTR_SPHERES) && (i == MT_RING)) return MT_BLUESPHERE; - if ((gametyperules & GTR_PAPERITEMS) && (i == MT_RANDOMITEM)) + if ((gametyperules & GTR_PAPERITEMS) && !bossinfo.boss && (i == MT_RANDOMITEM)) return MT_PAPERITEMSPOT; return i; @@ -12215,6 +12235,11 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean } break; } + case MT_RANDOMITEM: + { + nummapboxes++; + break; + } case MT_ITEMCAPSULE: { // we have to adjust for reverse gravity early so that the below grounded checks work diff --git a/src/p_setup.c b/src/p_setup.c index 1a1583a0a..390708e30 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -91,6 +91,7 @@ #include "k_waypoint.h" #include "k_bot.h" #include "k_grandprix.h" +#include "k_boss.h" #include "k_terrain.h" // TRF_TRIPWIRE #include "k_brightmap.h" #include "k_director.h" // K_InitDirector @@ -3524,7 +3525,7 @@ static void P_InitLevelSettings(void) if (playeringame[i] && !players[i].spectator) p++; - if (grandprixinfo.gp == false) + if (grandprixinfo.gp == false && bossinfo.boss == false) players[i].lives = 3; G_PlayerReborn(i, true); @@ -3549,6 +3550,12 @@ static void P_InitLevelSettings(void) franticitems = false; comeback = true; } + else if (bossinfo.boss) + { + gamespeed = KARTSPEED_EASY; + franticitems = false; + comeback = true; + } else if (modeattacking) { // Just play it safe and set everything @@ -4073,6 +4080,15 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } F_RunWipe(wipedefs[wipe_level_toblack], false, ((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), false, false); + + { + sfxenum_t kstart = sfx_kstart; + if (bossinfo.boss) + kstart = sfx_ssa021; + else if (encoremode) + kstart = sfx_ruby2; + S_StartSound(NULL, kstart); + } } /*if (!titlemapinaction) wipegamestate = GS_LEVEL;*/ @@ -4279,6 +4295,20 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) K_UpdateMatchRaceBots(); } + if (bossinfo.boss) + { + // Reset some pesky boss state that can't be handled elsewhere. + bossinfo.barlen = BOSSHEALTHBARLEN; + if (bossinfo.enemyname) + Z_Free(bossinfo.enemyname); + if (bossinfo.subtitle) + Z_Free(bossinfo.subtitle); + bossinfo.enemyname = bossinfo.subtitle = NULL; + bossinfo.titleshow = 0; + bossinfo.titlesound = sfx_typri1; + memset(&(bossinfo.weakspots), 0, sizeof(weakspot_t)*NUMWEAKSPOTS); + } + if (!fromnetsave) // uglier hack { // to make a newly loaded level start on the second frame. INT32 buf = gametic % TICQUEUE; diff --git a/src/p_tick.c b/src/p_tick.c index 22a090fba..20182264e 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -33,6 +33,7 @@ #include "k_kart.h" #include "k_race.h" #include "k_battle.h" +#include "k_boss.h" #include "k_waypoint.h" #include "k_director.h" @@ -327,6 +328,7 @@ if ((*mop = targ) != NULL) // Set new target and if non-NULL, increase its count static inline void P_RunThinkers(void) { size_t i; + for (i = 0; i < NUM_THINKERLISTS; i++) { ps_thlist_times[i] = I_GetPreciseTime(); @@ -610,24 +612,36 @@ void P_Ticker(boolean run) if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) P_PlayerAfterThink(&players[i]); - // Plays the music after the starting countdown. - if (leveltime == (starttime + (TICRATE/2))) + // Bosses have a punchy start, so no position. + if (bossinfo.boss == true) { - S_ChangeMusic(mapmusname, mapmusflags, true); - S_ShowMusicCredit(); - } - - if (encoremode) - { - // Encore humming starts immediately. if (leveltime == 3) - S_ChangeMusicInternal("encore", true); + { + S_ChangeMusic(mapmusname, mapmusflags, true); + S_ShowMusicCredit(); + } } + // Plays the music after the starting countdown. else { - // Plays the POSITION music after the camera spin - if (leveltime == introtime) - S_ChangeMusicInternal("postn", true); + if (leveltime == (starttime + (TICRATE/2))) + { + S_ChangeMusic(mapmusname, mapmusflags, true); + S_ShowMusicCredit(); + } + + if (encoremode) + { + // Encore humming starts immediately. + if (leveltime == 3) + S_ChangeMusicInternal("encore", true); + } + else + { + // Plays the POSITION music after the camera spin + if (leveltime == introtime) + S_ChangeMusicInternal("postn", true); + } } ps_lua_thinkframe_time = I_GetPreciseTime(); @@ -665,6 +679,8 @@ void P_Ticker(boolean run) if (hyubgone > 0) hyubgone--; + K_BossInfoTicker(); + if ((gametyperules & GTR_BUMPERS)) { if (wantedcalcdelay && --wantedcalcdelay <= 0) diff --git a/src/p_user.c b/src/p_user.c index b8837144e..7fba7cfef 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -52,6 +52,7 @@ #include "k_respawn.h" #include "k_bot.h" #include "k_grandprix.h" +#include "k_boss.h" #include "k_terrain.h" // K_SpawnSplashForMobj #include "k_color.h" @@ -805,7 +806,9 @@ void P_RestoreMusic(player_t *player) return; // Event - Level Start - if (leveltime >= (starttime + (TICRATE/2))) // see also where time overs are handled - search for "lives = 2" in this file + if (bossinfo.boss == false && (leveltime < (starttime + (TICRATE/2)))) // see also where time overs are handled + return; + { INT32 wantedmus = 0; // 0 is level music, 1 is invincibility, 2 is grow @@ -3002,7 +3005,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall else timeover = 0; - if (!(player->playerstate == PST_DEAD || player->exiting)) + if (!(player->playerstate == PST_DEAD || player->exiting || leveltime < introtime)) { if (player->spectator) // force cam off for spectators return true; @@ -3757,7 +3760,7 @@ void P_DoTimeOver(player_t *player) legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p } - if (netgame && !player->bot) + if (netgame && !player->bot && !bossinfo.boss) { CON_LogMessage(va(M_GetText("%s ran out of time.\n"), player_names[player-players])); } @@ -4322,7 +4325,7 @@ void P_PlayerThink(player_t *player) } // Accessibility - kickstart your acceleration - if (!(player->pflags & PF_KICKSTARTACCEL)) + if (gametype == GT_BATTLE || !(player->pflags & PF_KICKSTARTACCEL)) player->kickstartaccel = 0; else if (cmd->buttons & BT_ACCELERATE) { diff --git a/src/r_things.c b/src/r_things.c index 95482bc38..2678b35b6 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -762,16 +762,16 @@ boolean R_SpriteIsFlashing(vissprite_t *vis) return (!(vis->cut & SC_PRECIP) && (vis->mobj->flags & (MF_ENEMY|MF_BOSS)) && (vis->mobj->flags2 & MF2_FRET) - && !(vis->mobj->flags & MF_GRENADEBOUNCE) - && (leveltime & 1)); + && !(vis->mobj->flags & MF_GRENADEBOUNCE)); } UINT8 *R_GetSpriteTranslation(vissprite_t *vis) { - if (vis->mobj->hitlag > 0 && (vis->mobj->eflags & MFE_DAMAGEHITLAG)) + if ((vis->mobj->hitlag > 0 && (vis->mobj->eflags & MFE_DAMAGEHITLAG)) || R_SpriteIsFlashing(vis)) { return R_GetTranslationColormap(TC_HITLAG, 0, GTC_CACHE); } + /* else if (R_SpriteIsFlashing(vis)) // Bosses "flash" { if (vis->mobj->type == MT_CYBRAKDEMON || vis->mobj->colorized) @@ -781,6 +781,7 @@ UINT8 *R_GetSpriteTranslation(vissprite_t *vis) else return R_GetTranslationColormap(TC_BOSS, 0, GTC_CACHE); } + */ else if (vis->mobj->color) { // New colormap stuff for skins Tails 06-07-2002 diff --git a/src/s_sound.c b/src/s_sound.c index 4a224ec6f..2cc2d7467 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -30,6 +30,7 @@ #include "m_misc.h" // for tunes command #include "m_cond.h" // for conditionsets #include "lua_hook.h" // MusicChange hook +#include "k_boss.h" // bossinfo #ifdef HW3SOUND // 3D Sound Interface @@ -2351,7 +2352,9 @@ void S_StartEx(boolean reset) S_StopMusic(); // Starting ambience should always be restarted, if playing. if (leveltime < (starttime + (TICRATE/2))) // SRB2Kart - S_StartSound(NULL, encoremode ? sfx_ruby2 : sfx_kstart); + { + ; + } else S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0); diff --git a/src/sounds.c b/src/sounds.c index 58e8f2afe..cae8496f1 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1100,6 +1100,8 @@ sfxinfo_t S_sfx[NUMSFX] = {"cock", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Hammer cocks, bang bang {"itcaps", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, "Item capsule"}, {"kstart", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Sonic Adventure shwing! + {"typri1", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // SA2 boss typewriting 1 + {"typri2", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // SA2 final boss-type typewriting // SRB2Kart - Engine sounds // Engine class A diff --git a/src/sounds.h b/src/sounds.h index 4093fec7b..63e115566 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1164,6 +1164,8 @@ typedef enum sfx_cock, sfx_itcaps, sfx_kstart, + sfx_typri1, + sfx_typri2, // Next up: UNIQUE ENGINE SOUNDS! Hoooooo boy... // Engine class A - Low Speed, Low Weight diff --git a/src/st_stuff.c b/src/st_stuff.c index 3e0788f4d..41c4382a7 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -32,6 +32,7 @@ #include "m_anigif.h" // cv_gif_downscale #include "p_setup.h" // NiGHTS grading #include "k_grandprix.h" // we need to know grandprix status for titlecards +#include "k_boss.h" //random index #include "m_random.h" @@ -647,6 +648,9 @@ static patch_t *tcroundnum[10]; static patch_t *tcactnum[10]; static patch_t *tcact; +static patch_t *twarn; +static patch_t *twarn2; + // some coordinates define to make my life easier.... #define FINAL_ROUNDX (24) #define FINAL_EGGY (160) @@ -700,6 +704,9 @@ static void ST_cacheLevelTitle(void) tcact = (patch_t *)W_CachePatchName("TT_ACT", PU_HUDGFX); + twarn = (patch_t *)W_CachePatchName("K_BOSW01", PU_HUDGFX); + twarn2 = (patch_t *)W_CachePatchName("K_BOSW02", PU_HUDGFX); + // Cache round # for (i=1; i < 11; i++) { @@ -786,7 +793,36 @@ void ST_runTitleCard(void) // SRB2KART // side Zig-Zag positions... - + if (bossinfo.boss == true) + { + // Handle name info... + if (bossinfo.enemyname) + { + UINT32 len = strlen(bossinfo.enemyname)+1; + if (len > 1 && bossinfo.titleshow < len) + { + len = (lt_endtime-(TICRATE/2))/len; + if (lt_ticker % len == 0) + { + char c = toupper(bossinfo.enemyname[bossinfo.titleshow]); + bossinfo.titleshow++; + c -= LT_FONTSTART; + if (c < 0 || c >= LT_FONTSIZE || !tc_font[1][(INT32)c] || !bossinfo.titlesound) + { + ; + } + else + { + S_StartSound(NULL, bossinfo.titlesound); + } + } + } + } + // No matter the circumstances, scroll the WARN... + bannerx = -(lt_ticker%((encoremode ? twarn2 : twarn)->width)); + } + else + { // TITLECARD START if (lt_ticker < TTANIMSTART) { @@ -909,6 +945,7 @@ void ST_runTitleCard(void) // No matter the circumstances, scroll the banner... bannerx = -(lt_ticker%(tcbanner->width)); + } // used for hud slidein @@ -952,6 +989,101 @@ void ST_drawTitleCard(void) if (lt_ticker < TTANIMSTART) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol); + if (bossinfo.boss == true) + { + // WARNING! + // https://twitter.com/matthewseiji/status/1485003284196716544 + // the above tweet is directly responsible for the existence of bosses in this game at all + { +#define LOTIME 5 +#define HITIME 15 + patch_t *localwarn = (encoremode ? twarn2 : twarn); + INT32 transp = (lt_ticker+HITIME) % (LOTIME+HITIME); + boolean encorehack = (encoremode && lt_ticker <= PRELEVELTIME+4); + + if (lt_ticker + (HITIME-transp) <= lt_endtime) + { + if (transp > HITIME-1) + { + transp = HITIME-1; + } + + transp = (((10*transp)/HITIME)<width; + } + } +#undef LOTIME +#undef HITIME + } + + // Everything else... + if (bossinfo.enemyname) + { + bx = V_TitleCardStringWidth(bossinfo.enemyname); + + // Name. + V_DrawTitleCardString((BASEVIDWIDTH - bx)/2, 75, bossinfo.enemyname, 0, true, bossinfo.titleshow, lt_exitticker); + + // Under-bar. + { + angle_t fakeang = 0; + fixed_t scalex = FRACUNIT; + + // Handle scaling. + if (lt_ticker <= 3) + { + fakeang = (lt_ticker*ANGLE_45)/2; + scalex = FINESINE(fakeang>>ANGLETOFINESHIFT); + } + else if (lt_exitticker > 1) + { + if (lt_exitticker <= 4) + { + fakeang = ((lt_exitticker-1)*ANGLE_45)/2; + scalex = FINECOSINE(fakeang>>ANGLETOFINESHIFT); + } + else + { + scalex = 0; + } + } + // Handle subtitle. + else if (bossinfo.subtitle && lt_ticker >= TICRATE/2) + { + INT32 by = 75+32; + if (lt_ticker == TICRATE/2 || lt_exitticker == 1) + { + ; + } + else if (lt_ticker == (TICRATE/2)+1 || lt_ticker == lt_endtime) + { + by += 3; + } + else + { + by += 5; + } + + V_DrawRightAlignedThinString((BASEVIDWIDTH+bx)/2, by, V_6WIDTHSPACE, bossinfo.subtitle); + } + + // Now draw the under-bar itself. + if (scalex > 0) + { + bx = FixedMul(bx, scalex); + V_DrawFill((BASEVIDWIDTH-(bx+2))/2, 75+32, bx+2, 3, 31); + V_DrawFill((BASEVIDWIDTH-(bx))/2, 75+32+1, bx, 1, 0); + } + } + } + lt_lasttic = lt_ticker; + goto luahook; + } + // Background zig-zags V_DrawFixedPatch((chev1x)*FRACUNIT, (chev1y)*FRACUNIT, FRACUNIT, chevtflag, tcchev1, NULL); V_DrawFixedPatch((chev2x)*FRACUNIT, (chev2y)*FRACUNIT, FRACUNIT, chevtflag, tcchev2, NULL); @@ -982,10 +1114,10 @@ void ST_drawTitleCard(void) V_DrawFixedPatch(eggx2*FRACUNIT, eggy2*FRACUNIT, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, tccirclebottom, NULL); // Now the level name. - V_DrawTitleCardString((actnum) ? 265 : 280, 60, lvlttl, V_SNAPTORIGHT, true, lt_ticker, TTANIMENDTHRESHOLD); + V_DrawTitleCardString((actnum) ? 265 : 280, 60, lvlttl, V_SNAPTORIGHT, false, lt_ticker, TTANIMENDTHRESHOLD); if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE)) - V_DrawTitleCardString((actnum) ? 265 : 280, 60+32, strlen(zonttl) ? zonttl : "ZONE", V_SNAPTORIGHT, true, lt_ticker - strlen(lvlttl), TTANIMENDTHRESHOLD); + V_DrawTitleCardString((actnum) ? 265 : 280, 60+32, strlen(zonttl) ? zonttl : "ZONE", false, false, lt_ticker - strlen(lvlttl), TTANIMENDTHRESHOLD); // the act has a similar graphic animation, but we'll handle it here since it's only like 2 graphics lmfao. if (actnum && actnum < 10) diff --git a/src/v_video.c b/src/v_video.c index 60764ccbd..d58104eef 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -1719,7 +1719,7 @@ INT32 V_TitleCardStringWidth(const char *str) // V_DrawTitleCardScreen. // see v_video.h's prototype for more information. // -void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean alignright, INT32 timer, INT32 threshold) +void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold) { INT32 xoffs = 0; @@ -1740,7 +1740,7 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole x -= 2; // Account for patch width... - if (alignright) + if (flags & V_SNAPTORIGHT) x -= V_TitleCardStringWidth(str); @@ -1778,11 +1778,25 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole ol = tc_font[0][(INT32)c]; pp = tc_font[1][(INT32)c]; - if (timer) + if (bossmode) + { + if (let_time <= 0) + return; + if (threshold > 0) + { + if (threshold > 3) + return; + fakeang = (threshold*ANGLE_45)/2; + scalex = FINECOSINE(fakeang>>ANGLETOFINESHIFT); + } + offs = ((FRACUNIT-scalex)*pp->width)/2; + } + else if (timer) { - // make letters appear - if (!threshold || let_time < threshold) + if (!threshold) + ; + else if (let_time < threshold) { if (let_time <= 0) return; // No reason to continue drawing, none of the next letters will be drawn either. @@ -1792,7 +1806,7 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole fakeang = min(360 + 90, let_time*41) * ANG1; scalex = FINESINE(fakeang>>ANGLETOFINESHIFT); } - else if (let_time > threshold) + else if (!bossmode && let_time > threshold) { // Make letters disappear... let_time -= threshold; diff --git a/src/v_video.h b/src/v_video.h index 402f485a4..fc9c38696 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -269,7 +269,7 @@ 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_ALLOWLOWERCASE ...) // NOTE: This font only works with uppercase letters. -void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean alignright, INT32 timer, INT32 threshold); +void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold); // returns thr width of a string drawn using the above function. INT32 V_TitleCardStringWidth(const char *str); diff --git a/src/y_inter.c b/src/y_inter.c index 415672713..f8343532f 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -42,6 +42,7 @@ #include "m_random.h" // M_RandomKey #include "g_input.h" // PlayerInputDown #include "k_battle.h" +#include "k_boss.h" #include "k_pwrlv.h" #include "console.h" // cons_menuhighlight #include "k_grandprix.h" @@ -222,7 +223,14 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) else { // set up the levelstring - if (mapheaderinfo[prevmap]->levelflags & LF_NOZONE) + if (bossinfo.boss == true && bossinfo.enemyname) + { + snprintf(data.levelstring, + sizeof data.levelstring, + "* %s *", + bossinfo.enemyname); + } + else if (mapheaderinfo[prevmap]->levelflags & LF_NOZONE) { if (mapheaderinfo[prevmap]->actnum > 0) snprintf(data.levelstring, @@ -493,7 +501,7 @@ void Y_IntermissionDrawer(void) x += (((16 - count) * vid.width) / (8 * vid.dupx)); } - if (intertype == int_race || intertype == int_battle) + if (intertype == int_race || intertype == int_battle || intertype == int_battletime) { #define NUMFORNEWCOLUMN 8 INT32 y = 41, gutter = ((data.numplayers > NUMFORNEWCOLUMN) ? 0 : (BASEVIDWIDTH/2)); @@ -516,19 +524,11 @@ void Y_IntermissionDrawer(void) { switch (intertype) { - default: - case int_race: - timeheader = "TIME"; - break; case int_battle: - if (battlecapsules) - { - timeheader = "TIME"; - } - else - { - timeheader = "SCORE"; - } + timeheader = "SCORE"; + break; + default: + timeheader = "TIME"; break; } } @@ -653,7 +653,7 @@ void Y_IntermissionDrawer(void) V_DrawRightAlignedThinString(x+152+gutter, y-1, (data.numplayers > NUMFORNEWCOLUMN ? V_6WIDTHSPACE : 0), "NO CONTEST."); else { - if (intertype == int_race || (intertype == int_battle && battlecapsules)) + if (intertype == int_race || intertype == int_battletime) { snprintf(strtime, sizeof strtime, "%i'%02i\"%02i", G_TicsToMinutes(data.val[i], true), G_TicsToSeconds(data.val[i]), G_TicsToCentiseconds(data.val[i])); @@ -695,7 +695,7 @@ skiptallydrawer: if (!LUA_HudEnabled(hud_intermissionmessages)) return; - if (timer && grandprixinfo.gp == false) + if (timer && grandprixinfo.gp == false && bossinfo.boss == false) { char *string; INT32 tickdown = (timer+1)/TICRATE; @@ -797,11 +797,11 @@ void Y_Ticker(void) if (intertic < TICRATE || intertic & 1 || endtic != -1) return; - if (intertype == int_race || intertype == int_battle) + if (intertype == int_race || intertype == int_battle || intertype == int_battletime) { //if (!(multiplayer && demo.playback)) // Don't advance to rankings in replays { - if (!data.rankingsmode && (intertic >= sorttic + 8)) + if (!data.rankingsmode && sorttic != -1 && (intertic >= sorttic + 8)) { Y_CalculateMatchData(1, Y_CompareRank); } @@ -1034,12 +1034,19 @@ void Y_DetermineIntermissionType(void) if (intermissiontypes[gametype] != int_none) intertype = intermissiontypes[gametype]; - else if (modeattacking) - intertype = int_timeattack; else if (gametype == GT_RACE) intertype = int_race; else if (gametype == GT_BATTLE) - intertype = int_battle; + { + UINT8 i = 0, nump = 0; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + nump++; + } + intertype = (nump < 2 ? int_battletime : int_battle); + } } // @@ -1081,7 +1088,14 @@ void Y_StartIntermission(void) if (intermissiontypes[gametype] != int_none) intertype = intermissiontypes[gametype]; - sorttic = max((timer/2) - 2*TICRATE, 2*TICRATE); // 8 second pause after match results + if (multiplayer) + { + sorttic = max((timer/2) - 2*TICRATE, 2*TICRATE); // 8 second pause after match results + } + else + { + sorttic = -1; + } // We couldn't display the intermission even if we wanted to. // But we still need to give the players their score bonuses, dummy. @@ -1094,23 +1108,20 @@ void Y_StartIntermission(void) switch (intertype) { case int_battle: + case int_battletime: { - // Calculate who won - if (battlecapsules) - { - Y_CalculateMatchData(0, Y_CompareTime); - } - else - { - Y_CalculateMatchData(0, Y_CompareScore); - } - if (cv_inttime.value > 0) S_ChangeMusicInternal("racent", true); // loop it - break; + // Calculate who won + if (intertype == int_battle) + { + Y_CalculateMatchData(0, Y_CompareScore); + break; + } } - case int_race: // (time-only race) + // FALLTHRU + case int_race: { // Calculate who won Y_CalculateMatchData(0, Y_CompareTime); diff --git a/src/y_inter.h b/src/y_inter.h index 74a622963..296ffed31 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -32,8 +32,8 @@ typedef enum { int_none, int_race, // Race - int_battle, // Battle - int_timeattack, // Time Attack + int_battle, // Battle (score-based) + int_battletime, // Battle (time-based) } intertype_t; extern intertype_t intertype;