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

This commit is contained in:
toaster 2022-02-24 21:19:03 +00:00
parent 82377fb001
commit c1f3237157
36 changed files with 1225 additions and 142 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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
{

View file

@ -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}
};

View file

@ -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)

View file

@ -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++)

View file

@ -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
},

View file

@ -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;

188
src/k_boss.c Normal file
View file

@ -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;
}
}

63
src/k_boss.h Normal file
View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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);

View file

@ -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!

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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}
};

View file

@ -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;

View file

@ -33,6 +33,10 @@
unit used as fixed_t
*/
#if (FRACBITS == 16)
#define M_TAU_FIXED 411769
#endif
typedef INT32 fixed_t;
/*!

View file

@ -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;
}

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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)

View file

@ -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)
{

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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)<<V_ALPHASHIFT) | (encorehack ? V_SUBTRACT : V_ADD);
for (i=0; i < 3; i++)
{
V_DrawFixedPatch((bannerx + bx)*FRACUNIT, 55*FRACUNIT, FRACUNIT, V_SNAPTOLEFT|transp, localwarn, NULL);
bx += localwarn->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)

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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;