Merge remote-tracking branch 'origin/master' into bot-grip

This commit is contained in:
AJ Martinez 2025-06-03 13:02:10 -04:00
commit 8c463d47b9
76 changed files with 2820 additions and 1154 deletions

View file

@ -3589,13 +3589,15 @@ bool CallFunc_SetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, A
INT32 value = 0;
tag = argV[0];
mobj = P_FindMobjFromTID(tag, mobj, info->mo);
mobj_t *next = P_FindMobjFromTID(tag, mobj, info->mo);
property = argV[1];
value = argV[2];
while (mobj != NULL)
while ((mobj = next) != NULL)
{
// First in case of deletion. (Can't check for value == S_NULL because of A_ calls, etc)
next = P_FindMobjFromTID(tag, mobj, info->mo);
#define PROP_READONLY(x, y) \
case x: \
@ -3830,8 +3832,6 @@ bool CallFunc_SetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, A
}
}
mobj = P_FindMobjFromTID(tag, mobj, info->mo);
#undef PROP_FLAGS
#undef PROP_SCALE
#undef PROP_MOBJ

View file

@ -149,6 +149,7 @@ public:
Vector& operator=(const Vector& rhs)
{
clear();
for (auto itr = rhs.begin(); itr != rhs.end(); itr++)
{
push_back(*itr);

View file

@ -728,7 +728,7 @@ consvar_t cv_kartspeed = UnsavedNetVar("gamespeed", "Auto Gear").values(kartspee
consvar_t cv_teamplay = UnsavedNetVar("teamplay", "Off").on_off();
consvar_t cv_kartusepwrlv = UnsavedNetVar("usepwrlv", "Yes").yes_no();
consvar_t cv_kartusepwrlv = UnsavedNetVar("mobiums", "Yes").yes_no();
void LiveStudioAudience_OnChange(void);
#ifdef DEVELOP
@ -783,6 +783,8 @@ consvar_t cv_timelimit = UnsavedNetVar("timelimit", "Default").min_max(1, 30*60,
consvar_t cv_votetime = UnsavedNetVar("votetime", "20").min_max(10, 3600);
consvar_t cv_dueltimelimit = UnsavedNetVar("dueltimelimit", "180").min_max(0, 3600);
consvar_t cv_duelscorelimit = UnsavedNetVar("duelscorelimit", "4").min_max(1, 9);
//
// Online cheats - synced in netgames.

View file

@ -281,7 +281,7 @@ shouldsign_t ShouldSignChallenge(uint8_t *message)
if ((max(now, then) - min(now, then)) > 60*15)
return SIGN_BADTIME;
// ____ _____ ___ ____ _
// ____ _____ ___ ____ _
// / ___|_ _/ _ \| _ \| |
// \___ \ | || | | | |_) | |
// ___) || || |_| | __/|_|
@ -2436,6 +2436,11 @@ static void CL_ConnectToServer(void)
joinedIP[0] = '\0'; // And empty this for good measure regardless of whether or not we actually used it.
// Enable sound input/microphone in netgames, activating the microphone device.
if (netgame)
{
S_SoundInputSetEnabled(true);
}
}
static void Command_connect(void)
@ -2502,6 +2507,8 @@ static void Command_connect(void)
{
CONS_Alert(CONS_ERROR, M_GetText("There is no server identification with this network driver\n"));
D_CloseConnection();
S_SoundInputSetEnabled(false);
return;
}
}
@ -3659,6 +3666,7 @@ void D_QuitNetGame(void)
K_ClearClientPowerLevels();
G_ObliterateParties();
K_ResetMidVote();
S_SoundInputSetEnabled(false);
DEBFILE("===========================================================================\n"
" Log finish\n"
@ -7376,9 +7384,6 @@ void NetVoiceUpdate(void)
return;
}
// This necessarily runs every frame, not every tic
S_SoundInputSetEnabled(true);
UINT32 bytes_dequed = 0;
do
{
@ -7555,66 +7560,6 @@ tic_t GetLag(INT32 node)
return gametic - nettics[node];
}
#define REWIND_POINT_INTERVAL 4*TICRATE + 16
rewind_t *rewindhead;
void CL_ClearRewinds(void)
{
rewind_t *head;
while ((head = rewindhead))
{
rewindhead = rewindhead->next;
free(head);
}
}
rewind_t *CL_SaveRewindPoint(size_t demopos)
{
savebuffer_t save = {0};
rewind_t *rewind;
if (rewindhead && rewindhead->leveltime + REWIND_POINT_INTERVAL > leveltime)
return NULL;
rewind = (rewind_t *)malloc(sizeof (rewind_t));
if (!rewind)
return NULL;
P_SaveBufferFromExisting(&save, rewind->savebuffer, NETSAVEGAMESIZE);
P_SaveNetGame(&save, false);
rewind->leveltime = leveltime;
rewind->next = rewindhead;
rewind->demopos = demopos;
rewindhead = rewind;
return rewind;
}
rewind_t *CL_RewindToTime(tic_t time)
{
savebuffer_t save = {0};
rewind_t *rewind;
while (rewindhead && rewindhead->leveltime > time)
{
rewind = rewindhead->next;
free(rewindhead);
rewindhead = rewind;
}
if (!rewindhead)
return NULL;
P_SaveBufferFromExisting(&save, rewindhead->savebuffer, NETSAVEGAMESIZE);
P_LoadNetGame(&save, false);
wipegamestate = gamestate; // No fading back in!
timeinmap = leveltime;
return rewindhead;
}
void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest)
{
#ifdef NOMD5

View file

@ -712,21 +712,6 @@ extern boolean hu_stopped;
// SRB2Kart
//
struct rewind_t {
UINT8 savebuffer[NETSAVEGAMESIZE];
tic_t leveltime;
size_t demopos;
ticcmd_t oldcmd[MAXPLAYERS];
mobj_t oldghost[MAXPLAYERS];
rewind_t *next;
};
void CL_ClearRewinds(void);
rewind_t *CL_SaveRewindPoint(size_t demopos);
rewind_t *CL_RewindToTime(tic_t time);
void HandleSigfail(const char *string);
void DoSayPacket(SINT8 target, UINT8 flags, UINT8 source, char *message);

View file

@ -742,9 +742,6 @@ static bool D_Display(bool world)
if (forcerefresh && G_GamestateUsesLevel() == false)
V_SetPalette(0);
if (demo.rewinding)
V_DrawFadeScreen(TC_RAINBOW, (leveltime & 0x20) ? SKINCOLOR_PASTEL : SKINCOLOR_MOONSET);
// vid size change is now finished if it was on...
vid.recalc = 0;
@ -936,7 +933,7 @@ void D_SRB2Loop(void)
realtics = entertic - oldentertics;
oldentertics = entertic;
if (demo.playback && gamestate == GS_LEVEL)
if (demo.playback && gamestate == GS_LEVEL && demo.simplerewind == DEMO_REWIND_OFF)
{
// Nicer place to put this.
realtics = realtics * cv_playbackspeed.value;
@ -1216,6 +1213,7 @@ void D_ClearState(void)
// Reset GP and roundqueue
memset(&grandprixinfo, 0, sizeof(struct grandprixinfo));
memset(&roundqueue, 0, sizeof(struct roundqueue));
memset(&menuqueue, 0, sizeof(struct menuqueue));
// empty some other semi-important state
maptol = 0;
@ -1248,7 +1246,8 @@ void D_ClearState(void)
if (gamedata && gamedata->deferredsave)
G_SaveGameData();
K_UnsetDialogue();
P_FreeLevelState();
P_InvalidateThinkersWithoutInit();
G_SetGamestate(GS_NULL);
wipegamestate = GS_NULL;

View file

@ -2291,11 +2291,11 @@ void D_SetupVote(INT16 newgametype)
void D_ModifyClientVote(UINT8 player, SINT8 voted)
{
char buf[2];
char buf[3];
char *p = buf;
UINT8 sendPlayer = consoleplayer;
UINT8 sendPlayer = 0;
if (player == UINT8_MAX)
if (player >= MAXPLAYERS)
{
// Special game vote (map anger, duel)
if (!server)
@ -2304,16 +2304,16 @@ void D_ModifyClientVote(UINT8 player, SINT8 voted)
}
}
if (player == UINT8_MAX)
{
// special vote
WRITEUINT8(p, UINT8_MAX);
}
else
{
INT32 i = 0;
WRITEUINT8(p, player);
// Context value -- if context has changed, then discard vote update.
// This is to prevent votes being registered from different vote types.
// Currently used for Duel vs Normal votes.
WRITEUINT8(p, Y_VoteContext());
WRITEUINT8(p, player);
if (player <= MAXPLAYERS)
{
INT32 i;
for (i = 0; i <= splitscreen; i++)
{
if (g_localplayers[i] == player)
@ -2529,7 +2529,7 @@ static void Command_Map_f(void)
newforcespecialstage = COM_CheckParm("-forcespecialstage");
usingcheats = CV_CheatsEnabled();
ischeating = (!(netgame || multiplayer)) || (!newresetplayers);
ischeating = (!(netgame || multiplayer)) || (!newresetplayers) || (!K_CanChangeRules(false));
if (!( first_option = COM_FirstOption() ))
first_option = COM_Argc();
@ -2984,7 +2984,7 @@ static void Command_RestartLevel(void)
D_MapChange(gamemap, g_lastgametype, newencore, false, 0, false, false);
}
static void Handle_MapQueueSend(UINT16 newmapnum, UINT16 newgametype, boolean newencoremode)
void Handle_MapQueueSend(UINT16 newmapnum, UINT16 newgametype, boolean newencoremode)
{
UINT8 flags = 0;
@ -5636,30 +5636,35 @@ static void Got_SetupVotecmd(const UINT8 **cp, INT32 playernum)
static void Got_ModifyVotecmd(const UINT8 **cp, INT32 playernum)
{
UINT8 context = READUINT8(*cp);
UINT8 targetID = READUINT8(*cp);
SINT8 vote = READSINT8(*cp);
if (targetID == UINT8_MAX)
if (context != Y_VoteContext())
{
if (playernum != serverplayer) // server-only special vote
// Silently discard. Server changed the
// vote type as we were sending our vote.
return;
}
if (targetID >= MAXPLAYERS)
{
// only the server is allowed to send these
if (playernum != serverplayer)
{
goto fail;
}
targetID = VOTE_SPECIAL;
}
else if (playeringame[targetID] == true && players[targetID].bot == true)
{
if (targetID >= MAXPLAYERS
|| playernum != serverplayer)
if (playernum != serverplayer)
{
goto fail;
}
}
else
{
if (targetID >= MAXPLAYERS
|| playernode[targetID] != playernode[playernum])
if (playernode[targetID] != playernode[playernum])
{
goto fail;
}

View file

@ -52,6 +52,7 @@ extern consvar_t cv_joyscale[MAXSPLITSCREENPLAYERS];
extern consvar_t cv_pointlimit;
extern consvar_t cv_timelimit;
extern consvar_t cv_dueltimelimit, cv_duelscorelimit;
extern consvar_t cv_numlaps;
extern UINT32 timelimitintics, extratimeintics, secretextratime;
extern UINT32 g_pointlimit;
@ -205,6 +206,7 @@ size_t WeaponPref_Parse(const UINT8 *p, INT32 playernum);
void D_SendPlayerConfig(UINT8 n);
void Command_ExitGame_f(void);
void Command_Retry_f(void);
void Handle_MapQueueSend(UINT16 newmapnum, UINT16 newgametype, boolean newencoremode);
boolean G_GamestateUsesExitLevel(void);
void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore
void D_MapChange(UINT16 pmapnum, INT32 pgametype, boolean pencoremode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pforcespecialstage);

View file

@ -830,6 +830,8 @@ struct player_t
UINT8 numsneakers; // Number of stacked sneaker effects
UINT16 panelsneakertimer;
UINT8 numpanelsneakers;
UINT16 weaksneakertimer;
UINT8 numweaksneakers;
UINT8 floorboost; // (0 to 3) - Prevents Sneaker sounds for a brief duration when triggered by a floor panel
INT16 growshrinktimer; // > 0 = Big, < 0 = small
@ -959,6 +961,8 @@ struct player_t
INT32 cheatchecknum; // The number of the last cheatcheck you hit
INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp
INT16 duelscore;
UINT8 team; // 0 == Spectator, 1 == Red, 2 == Blue
UINT8 checkskip; // Skipping checkpoints? Oh no no no

View file

@ -297,6 +297,7 @@ actionpointer_t actionpointers[] =
{{A_MakeSSCandle}, "A_MAKESSCANDLE"},
{{A_HologramRandomTranslucency}, "A_HOLOGRAMRANDOMTRANSLUCENCY"},
{{A_SSChainShatter}, "A_SSCHAINSHATTER"},
{{A_GenericBumper}, "A_GENERICBUMPER"},
{{NULL}, "NONE"},

View file

@ -745,7 +745,7 @@ extern int
#define MAXAMPSCALINGDIST 18000
// Exp
#define MINEXP 50 // The min value target
#define MINEXP 25 // The min value target
#define TARGETEXP 100 // The target value needed for A rank
#define MAXEXP 125 // The max value displayed by the hud and in the tally screen and GP results screen

View file

@ -882,16 +882,24 @@ extern SINT8 spbplace;
extern boolean rainbowstartavailable;
extern tic_t linecrossed;
extern boolean inDuel;
extern UINT8 overtimecheckpoints;
extern tic_t bombflashtimer; // Used to avoid causing seizures if multiple mines explode close to you :)
extern boolean legitimateexit;
extern boolean comebackshowninfo;
#define VOTE_NUM_LEVELS (4)
#define VOTE_NOT_PICKED (-1)
#define VOTE_SPECIAL (MAXPLAYERS)
#define VOTE_TOTAL (MAXPLAYERS+1)
extern UINT16 g_voteLevels[4][2];
#define VOTE_TIMEOUT_LOSER (MAXPLAYERS+1) // not a real vote ID
#define VOTE_TIMEOUT_WINNER (MAXPLAYERS+2) // ditto
extern UINT16 g_voteLevels[VOTE_NUM_LEVELS][2];
extern SINT8 g_votes[VOTE_TOTAL];
extern SINT8 g_pickedVote;
extern boolean g_votes_striked[VOTE_NUM_LEVELS];
// ===========================
// Internal parameters, fixed.

View file

@ -404,7 +404,10 @@ class TiccmdBuilder
map(gc_item, BT_ATTACK); // fire
map(gc_lookback, BT_LOOKBACK); // rear view
map(gc_respawn, BT_RESPAWN | (freecam() ? 0 : BT_EBRAKEMASK)); // respawn
if (!modeattacking)
{
map(gc_respawn, BT_RESPAWN | (freecam() ? 0 : BT_EBRAKEMASK)); // respawn
}
map(gc_vote, BT_VOTE); // mp general function button
// lua buttons a thru c

View file

@ -270,22 +270,31 @@ static ticcmd_t oldcmd[MAXPLAYERS];
static mobj_t oldghost[MAXPLAYERS];
boolean G_ConsiderEndingDemoWrite(void)
{
// chill, we reserved extra memory so it's
// "safe" to have written a bit past the end
if (demobuf.p < demobuf.end)
return false;
G_CheckDemoStatus();
return true;
}
boolean G_ConsiderEndingDemoRead(void)
{
if (*demobuf.p != DEMOMARKER)
return false;
G_CheckDemoStatus();
return true;
}
void G_ReadDemoExtraData(void)
{
INT32 p, extradata, i;
char name[64];
static_assert(sizeof name >= std::max({MAXPLAYERNAME+1u, SKINNAMESIZE+1u, MAXCOLORNAME+1u}));
if (leveltime > starttime)
{
rewind_t *rewind = CL_SaveRewindPoint(demobuf.p - demobuf.buffer);
if (rewind)
{
memcpy(rewind->oldcmd, oldcmd, sizeof (oldcmd));
memcpy(rewind->oldghost, oldghost, sizeof (oldghost));
}
}
memset(name, '\0', sizeof name);
p = READUINT8(demobuf.p);
@ -460,13 +469,6 @@ void G_ReadDemoExtraData(void)
p = READUINT8(demobuf.p);
}
if (!(demoflags & DF_GHOST) && *demobuf.p == DEMOMARKER)
{
// end of demo data stream
G_CheckDemoStatus();
return;
}
}
void G_WriteDemoExtraData(void)
@ -623,13 +625,6 @@ void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum)
}
G_CopyTiccmd(cmd, &oldcmd[playernum], 1);
if (!(demoflags & DF_GHOST) && *demobuf.p == DEMOMARKER)
{
// end of demo data stream
G_CheckDemoStatus();
return;
}
}
void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum)
@ -739,14 +734,6 @@ void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum)
WRITEUINT16(botziptic_p, botziptic);
}
// attention here for the ticcmd size!
// latest demos with mouse aiming byte in ticcmd
if (!(demoflags & DF_GHOST) && ziptic_p > demobuf.end - 9)
{
G_CheckDemoStatus(); // no more space
return;
}
}
void G_GhostAddFlip(INT32 playernum)
@ -794,8 +781,11 @@ void G_GhostAddHit(INT32 playernum, mobj_t *victim)
void G_WriteAllGhostTics(void)
{
boolean toobig = false;
INT32 i, counter = leveltime;
if (!demobuf.p || !(demoflags & DF_GHOST))
return; // No ghost data to write.
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
@ -811,22 +801,9 @@ void G_WriteAllGhostTics(void)
WRITEUINT8(demobuf.p, i);
G_WriteGhostTic(players[i].mo, i);
// attention here for the ticcmd size!
// latest demos with mouse aiming byte in ticcmd
if (demobuf.p >= demobuf.end - (13 + 9 + 9))
{
toobig = true;
break;
}
}
WRITEUINT8(demobuf.p, 0xFF);
if (toobig)
{
G_CheckDemoStatus(); // no more space
return;
}
}
void G_WriteGhostTic(mobj_t *ghost, INT32 playernum)
@ -835,11 +812,6 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum)
UINT8 *ziptic_p;
UINT32 i;
if (!demobuf.p)
return;
if (!(demoflags & DF_GHOST))
return; // No ghost data to write.
ziptic_p = demobuf.p++; // the ziptic, written at the end of this function
#define MAXMOM (0xFFFF<<8)
@ -1061,7 +1033,7 @@ void G_ConsAllGhostTics(void)
{
UINT8 p;
if (!demobuf.p || !demo.deferstart)
if (!demobuf.p || !(demoflags & DF_GHOST) || !demo.deferstart)
return;
p = READUINT8(demobuf.p);
@ -1071,13 +1043,6 @@ void G_ConsAllGhostTics(void)
G_ConsGhostTic(p);
p = READUINT8(demobuf.p);
}
if (*demobuf.p == DEMOMARKER)
{
// end of demo data stream
G_CheckDemoStatus();
return;
}
}
// Uses ghost data to do consistency checks on your position.
@ -1089,9 +1054,6 @@ void G_ConsGhostTic(INT32 playernum)
mobj_t *testmo;
UINT32 syncleeway;
if (!(demoflags & DF_GHOST))
return; // No ghost data to use.
testmo = players[playernum].mo;
// Grab ghost data.
@ -1282,13 +1244,6 @@ void G_ConsGhostTic(INT32 playernum)
}
}
}
if (*demobuf.p == DEMOMARKER)
{
// end of demo data stream
G_CheckDemoStatus();
return;
}
}
void G_GhostTicker(void)
@ -1309,11 +1264,31 @@ void G_GhostTicker(void)
continue;
readghosttic:
#define follow g->mo->tracer
// Skip normal demo data.
ziptic = READUINT8(g->p);
xziptic = 0;
// Demo ends after ghost data.
if (ziptic == DEMOMARKER)
{
fadeghost:
g->mo->momx = g->mo->momy = g->mo->momz = 0;
g->mo->fuse = TICRATE;
if (follow)
{
follow->fuse = TICRATE;
}
g->done = true;
if (p)
{
p->next = g->next;
}
continue;
}
while (ziptic != DW_END) // Get rid of extradata stuff
{
if (ziptic < MAXPLAYERS)
@ -1414,9 +1389,11 @@ readghosttic:
// Grab ghost data.
ziptic = READUINT8(g->p);
if (ziptic == DEMOMARKER) // Had to end early for some reason
goto fadeghost;
if (ziptic == 0xFF)
goto skippedghosttic; // Didn't write ghost info this frame
else if (ziptic != 0)
if (ziptic != 0)
I_Error("Ghost is not a record attack ghost ZIPTIC"); //@TODO lmao don't blow up like this
ziptic = READUINT8(g->p);
@ -1530,7 +1507,6 @@ readghosttic:
g->mo->renderflags &= ~RF_DONTDRAW;
}
#define follow g->mo->tracer
if (ziptic & GZT_FOLLOW)
{ // Even more...
UINT8 followtic = READUINT8(g->p);
@ -1620,28 +1596,6 @@ skippedghosttic:
if (READUINT8(g->p) != 0xFF) // Make sure there isn't other ghost data here.
I_Error("Ghost is not a record attack ghost GHOSTEND"); //@TODO lmao don't blow up like this
// Demo ends after ghost data.
if (*g->p == DEMOMARKER)
{
g->mo->momx = g->mo->momy = g->mo->momz = 0;
#if 0 // freeze frame (maybe more useful for time attackers) (2024-03-11: you leave it behind anyway!)
g->mo->colorized = true;
g->mo->fuse = 10*TICRATE;
if (follow)
follow->colorized = true;
#else // dissapearing act
g->mo->fuse = TICRATE;
if (follow)
follow->fuse = TICRATE;
#endif
g->done = true;
if (p)
{
p->next = g->next;
}
continue;
}
// If the timer started, skip ahead until the ghost starts too.
if (starttime <= leveltime && !g->linecrossed && G_TimeAttackStart())
goto readghosttic;
@ -1651,203 +1605,6 @@ skippedghosttic:
}
}
// Demo rewinding functions
typedef struct rewindinfo_s {
tic_t leveltime;
struct {
boolean ingame;
player_t player;
mobj_t mobj;
} playerinfo[MAXPLAYERS];
struct rewindinfo_s *prev;
} rewindinfo_t;
static tic_t currentrewindnum;
static rewindinfo_t *rewindhead = NULL; // Reverse chronological order
void G_InitDemoRewind(void)
{
CL_ClearRewinds();
while (rewindhead)
{
rewindinfo_t *p = rewindhead->prev;
Z_Free(rewindhead);
rewindhead = p;
}
currentrewindnum = 0;
}
void G_StoreRewindInfo(void)
{
static UINT8 timetolog = 8;
rewindinfo_t *info;
size_t i;
if (timetolog-- > 0)
return;
timetolog = 8;
info = static_cast<rewindinfo_t*>(Z_Calloc(sizeof(rewindinfo_t), PU_STATIC, NULL));
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
{
info->playerinfo[i].ingame = false;
continue;
}
info->playerinfo[i].ingame = true;
memcpy(&info->playerinfo[i].player, &players[i], sizeof(player_t));
if (players[i].mo)
memcpy(&info->playerinfo[i].mobj, players[i].mo, sizeof(mobj_t));
}
info->leveltime = leveltime;
info->prev = rewindhead;
rewindhead = info;
}
void G_PreviewRewind(tic_t previewtime)
{
SINT8 i;
//size_t j;
fixed_t tweenvalue = 0;
rewindinfo_t *info = rewindhead, *next_info = rewindhead;
if (!info)
return;
while (info->leveltime > previewtime && info->prev)
{
next_info = info;
info = info->prev;
}
if (info != next_info)
tweenvalue = FixedDiv(previewtime - info->leveltime, next_info->leveltime - info->leveltime);
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
{
if (info->playerinfo[i].player.mo)
{
//@TODO spawn temp object to act as a player display
}
continue;
}
if (!info->playerinfo[i].ingame || !info->playerinfo[i].player.mo)
{
if (players[i].mo)
players[i].mo->renderflags |= RF_DONTDRAW;
continue;
}
if (!players[i].mo)
continue; //@TODO spawn temp object to act as a player display
players[i].mo->renderflags &= ~RF_DONTDRAW;
P_UnsetThingPosition(players[i].mo);
#define TWEEN(pr) info->playerinfo[i].mobj.pr + FixedMul((INT32) (next_info->playerinfo[i].mobj.pr - info->playerinfo[i].mobj.pr), tweenvalue)
players[i].mo->x = TWEEN(x);
players[i].mo->y = TWEEN(y);
players[i].mo->z = TWEEN(z);
players[i].mo->angle = TWEEN(angle);
#undef TWEEN
P_SetThingPosition(players[i].mo);
players[i].drawangle = info->playerinfo[i].player.drawangle + FixedMul((INT32) (next_info->playerinfo[i].player.drawangle - info->playerinfo[i].player.drawangle), tweenvalue);
players[i].mo->sprite = info->playerinfo[i].mobj.sprite;
players[i].mo->sprite2 = info->playerinfo[i].mobj.sprite2;
players[i].mo->frame = info->playerinfo[i].mobj.frame;
players[i].mo->hitlag = info->playerinfo[i].mobj.hitlag;
players[i].realtime = info->playerinfo[i].player.realtime;
// Genuinely CANNOT be fucked. I can redo lua and I can redo netsaves but I draw the line at this abysmal hack.
/*for (j = 0; j < NUMKARTSTUFF; j++)
players[i].kartstuff[j] = info->playerinfo[i].player.kartstuff[j];*/
}
for (i = splitscreen; i >= 0; i--)
P_ResetCamera(&players[displayplayers[i]], &camera[i]);
}
void G_ConfirmRewind(tic_t rewindtime)
{
SINT8 i;
tic_t j;
boolean oldmenuactive = menuactive, oldsounddisabled = sound_disabled;
INT32 olddp1 = displayplayers[0], olddp2 = displayplayers[1], olddp3 = displayplayers[2], olddp4 = displayplayers[3];
UINT8 oldss = splitscreen;
menuactive = false; // Prevent loops
CV_StealthSetValue(&cv_renderview, 0);
if (rewindtime <= starttime)
{
demo.rewinding = true; // this doesn't APPEAR to cause any misery, and it allows us to prevent running all the wipes again
G_DoPlayDemo(NULL); // Restart the current demo
}
else
{
rewind_t *rewind;
sound_disabled = true; // Prevent sound spam
demo.rewinding = true;
rewind = CL_RewindToTime(rewindtime);
if (rewind)
{
demobuf.p = demobuf.buffer + rewind->demopos;
memcpy(oldcmd, rewind->oldcmd, sizeof (oldcmd));
memcpy(oldghost, rewind->oldghost, sizeof (oldghost));
paused = false;
}
else
{
demo.rewinding = true;
G_DoPlayDemo(NULL); // Restart the current demo
}
}
for (j = 0; j < rewindtime && leveltime < rewindtime; j++)
{
G_Ticker((j % NEWTICRATERATIO) == 0);
}
demo.rewinding = false;
menuactive = oldmenuactive; // Bring the menu back up
sound_disabled = oldsounddisabled; // Re-enable SFX
wipegamestate = gamestate; // No fading back in!
COM_BufInsertText("renderview on\n");
splitscreen = oldss;
displayplayers[0] = olddp1;
displayplayers[1] = olddp2;
displayplayers[2] = olddp3;
displayplayers[3] = olddp4;
R_ExecuteSetViewSize();
G_ResetViews();
for (i = splitscreen; i >= 0; i--)
P_ResetCamera(&players[displayplayers[i]], &camera[i]);
}
//
// G_RecordDemo
//
@ -1885,7 +1642,7 @@ void G_RecordDemo(const char *name)
functions will check if they overran the buffer, but it
should be safe enough because they'll think there's less
memory than there actually is and stop early. */
const size_t deadspace = 1024;
const size_t deadspace = 2048;
I_Assert(demobuf.size > deadspace);
demobuf.size -= deadspace;
demobuf.end -= deadspace;
@ -2368,6 +2125,8 @@ void G_BeginRecording(void)
WRITEUINT8(demobuf.p, player->skin);
WRITEUINT8(demobuf.p, player->lastfakeskin);
WRITEUINT8(demobuf.p, player->team);
// Color
demobuf.p += copy_fixed_buf(demobuf.p, skincolors[player->skincolor].name, g_buffer_sizes.color_name);
@ -2997,8 +2756,6 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum)
boolean skiperrors = true;
#endif
G_InitDemoRewind();
gtname[MAXGAMETYPELENGTH-1] = '\0';
if (deflumpnum != LUMPERROR)
@ -3361,22 +3118,6 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum)
// Load "mapmusrng" used for altmusic selection
mapmusrng = READUINT8(demobuf.p);
// Sigh ... it's an empty demo.
if (*demobuf.p == DEMOMARKER)
{
snprintf(msg, 1024, M_GetText("%s contains no data to be played.\n"), pdemoname);
CONS_Alert(CONS_ERROR, "%s", msg);
M_StartMessage("Demo Playback", msg, NULL, MM_NOTHING, NULL, "Return to Menu");
Z_Free(demo.skinlist);
demo.skinlist = NULL;
Z_Free(pdemoname);
Z_Free(demobuf.buffer);
demo.playback = false;
return;
}
Z_Free(pdemoname);
memset(&oldcmd,0,sizeof(oldcmd));
memset(&oldghost,0,sizeof(oldghost));
memset(&ghostext,0,sizeof(ghostext));
@ -3500,6 +3241,8 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum)
demo.currentskinid[p] = 0;
lastfakeskin[p] = READUINT8(demobuf.p);
players[p].team = READUINT8(demobuf.p);
// Color
demobuf.p += copy_fixed_buf(color, demobuf.p, g_buffer_sizes.color_name);
for (i = 0; i < numskincolors; i++)
@ -3603,9 +3346,26 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum)
players[p].lastfakeskin = lastfakeskin[p];
}
// Sigh ... it's an empty demo.
if (*demobuf.p == DEMOMARKER)
{
snprintf(msg, 1024, M_GetText("%s contains no data to be played.\n"), pdemoname);
CONS_Alert(CONS_ERROR, "%s", msg);
M_StartMessage("Demo Playback", msg, NULL, MM_NOTHING, NULL, "Return to Menu");
Z_Free(demo.skinlist);
demo.skinlist = NULL;
Z_Free(pdemoname);
Z_Free(demobuf.buffer);
demo.playback = false;
return;
}
Z_Free(pdemoname);
demo.deferstart = true;
CV_StealthSetValue(&cv_playbackspeed, 1);
if (demo.simplerewind == DEMO_REWIND_OFF)
CV_StealthSetValue(&cv_playbackspeed, 1);
}
void G_AddGhost(savebuffer_t *buffer, const char *defdemoname)
@ -3752,14 +3512,6 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname)
p++; // mapmusrng
if (*p == DEMOMARKER)
{
CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Replay is empty.\n"), defdemoname);
Z_Free(skinlist);
P_SaveBufferFree(buffer);
return;
}
p++; // player number - doesn't really need to be checked, TODO maybe support adding multiple players' ghosts at once
// any invalidating flags?
@ -3783,6 +3535,8 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname)
ghskin = &skins[skinlist[i].mapping];
p++; // lastfakeskin
p++; // team
// Color
p += copy_fixed_buf(color, p, ghostsizes.color_name);
@ -3805,6 +3559,14 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname)
return;
}
if (*p == DEMOMARKER)
{
CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Replay is empty.\n"), defdemoname);
Z_Free(skinlist);
P_SaveBufferFree(buffer);
return;
}
gh = static_cast<demoghost*>(Z_Calloc(sizeof(demoghost), PU_LEVEL, NULL));
gh->sizes = ghostsizes;
@ -4167,7 +3929,7 @@ boolean G_CheckDemoStatus(void)
// Keep the demo open and don't boot to intermission
// YET, pause demo playback.
if (!demo.waitingfortally && modeattacking && exitcountdown)
demo.waitingfortally = true;
;
else if (!demo.attract)
G_FinishExitLevel();
else
@ -4185,6 +3947,8 @@ boolean G_CheckDemoStatus(void)
D_SetDeferredStartTitle(true);
}
demo.waitingfortally = true; // if we've returned early for some reason...
return true;
}
@ -4229,6 +3993,13 @@ void G_SaveDemo(void)
if (currentMenu == &TitleEntryDef)
M_ClearMenus(true);
if (!leveltime)
{
// Why would you save if nothing has been recorded
G_ResetDemoRecording();
return;
}
// Ensure extrainfo pointer is always available, even if no info is present.
if (demoinfo_p && *(UINT32 *)demoinfo_p == 0)
{

View file

@ -84,7 +84,7 @@ struct demovars_s {
boolean recording, playback, timing;
UINT16 version; // Current file format of the demo being played
UINT8 attract; // Attract demo can be cancelled by any key
boolean rewinding; // Rewind in progress
UINT8 simplerewind;
boolean loadfiles, ignorefiles; // Demo file loading options
boolean quitafterplaying; // quit after playing a demo from cmdline
@ -172,6 +172,8 @@ extern UINT8 demo_writerng;
boolean G_CompatLevel(UINT16 level);
// Record/playback tics
boolean G_ConsiderEndingDemoRead(void);
boolean G_ConsiderEndingDemoWrite(void);
void G_ReadDemoExtraData(void);
void G_WriteDemoExtraData(void);
void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum);
@ -186,11 +188,6 @@ void G_ConsAllGhostTics(void);
void G_ConsGhostTic(INT32 playernum);
void G_GhostTicker(void);
void G_InitDemoRewind(void);
void G_StoreRewindInfo(void);
void G_PreviewRewind(tic_t previewtime);
void G_ConfirmRewind(tic_t rewindtime);
struct DemoBufferSizes
{
size_t player_name;
@ -250,6 +247,13 @@ typedef enum
DEMO_ATTRACT_CREDITS
} demoAttractMode_t;
typedef enum
{
DEMO_REWIND_OFF = 0,
DEMO_REWIND_RESUME,
DEMO_REWIND_PAUSE
} demoRewindMode_t;
void G_SyncDemoParty(INT32 rem, INT32 newsplitscreen);
#ifdef __cplusplus

View file

@ -297,9 +297,10 @@ boolean franticitems; // Frantic items currently enabled?
boolean g_teamplay;
// Voting system
UINT16 g_voteLevels[4][2]; // Levels that were rolled by the host
UINT16 g_voteLevels[VOTE_NUM_LEVELS][2]; // Levels that were rolled by the host
SINT8 g_votes[VOTE_TOTAL]; // Each player's vote
SINT8 g_pickedVote; // What vote the host rolls
boolean g_votes_striked[VOTE_NUM_LEVELS]; // Which levels were striked from votes?
// Server-sided, synched variables
tic_t wantedcalcdelay; // Time before it recalculates WANTED
@ -311,6 +312,7 @@ SINT8 spbplace; // SPB exists, give the person behind better items
boolean rainbowstartavailable; // Boolean, keeps track of if the rainbow start was gotten
tic_t linecrossed; // For Time Attack
boolean inDuel; // Boolean, keeps track of if it is a 1v1
UINT8 overtimecheckpoints; // Duel overtime speedups!
// Client-sided, unsynched variables (NEVER use in anything that needs to be synced with other players)
tic_t bombflashtimer = 0; // Cooldown before another FlashPal can be intialized by a bomb exploding near a displayplayer. Avoids seizures.
@ -1243,26 +1245,36 @@ void G_StartTitleCard(void)
// prepare status bar
ST_startTitleCard(); // <-- always must be called to init some variables
// The title card has been disabled for this map.
// Oh well.
if (demo.rewinding || !G_IsTitleCardAvailable())
{
WipeStageTitle = false;
if (demo.simplerewind)
return;
sfxenum_t kstart = 0;
if (K_CheckBossIntro() == true)
{
kstart = sfx_ssa021;
}
else if (encoremode)
{
kstart = sfx_ruby2;
}
if (kstart)
{
// Play the guaranteed alt sounds
S_StartSound(NULL, kstart);
}
if (!G_IsTitleCardAvailable())
return;
// start the title card
WipeStageTitle = (gamestate == GS_LEVEL);
// play the sound
if (WipeStageTitle)
if (WipeStageTitle && !kstart)
{
sfxenum_t kstart = sfx_kstart;
if (K_CheckBossIntro() == true)
kstart = sfx_ssa021;
else if (encoremode == true)
kstart = sfx_ruby2;
S_StartSound(NULL, kstart);
// Play the standard titlecard sound
S_StartSound(NULL, sfx_kstart);
}
}
@ -1437,13 +1449,7 @@ boolean G_Responder(event_t *ev)
{
paused = !paused;
if (demo.rewinding)
{
G_ConfirmRewind(leveltime);
paused = true;
S_PauseAudio();
}
else if (paused)
if (paused)
S_PauseAudio();
else
S_ResumeAudio();
@ -1554,7 +1560,7 @@ boolean G_CouldView(INT32 playernum)
return false;
// SRB2Kart: we have no team-based modes, YET...
if (G_GametypeHasTeams())
if (G_GametypeHasTeams() && !demo.playback)
{
if (players[consoleplayer].spectator == false && player->team != players[consoleplayer].team)
return false;
@ -2148,6 +2154,14 @@ void G_Ticker(boolean run)
if (g_fast_forward == 0)
{
// Not "rewinding" anymore.
if (demo.simplerewind == DEMO_REWIND_PAUSE)
{
paused = true;
S_PauseAudio();
}
demo.simplerewind = DEMO_REWIND_OFF;
// Next fast-forward is unlimited.
g_fast_forward_clock_stop = INFTICS;
}
@ -2262,6 +2276,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
INT32 kickstartaccel;
INT32 checkpointId;
boolean enteredGame;
tic_t spectatewait;
UINT8 lastsafelap;
UINT8 lastsafecheatcheck;
UINT16 bigwaypointgap;
@ -2349,8 +2364,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
bot = players[player].bot;
botdifficulty = players[player].botvars.difficulty;
cangrabitems = players[player].cangrabitems;
botdiffincrease = players[player].botvars.diffincrease;
botrival = players[player].botvars.rival;
@ -2436,13 +2449,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
tallyactive = false;
cangrabitems = 0;
if (gametyperules & GTR_SPHERES
|| gametyperules & GTR_CATCHER
|| G_TimeAttackStart()
|| gametype == GT_TUTORIAL
|| !M_NotFreePlay()
|| K_GetNumWaypoints() == 0)
cangrabitems = EARLY_ITEM_FLICKER;
}
else
{
@ -2498,6 +2504,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
{
tally = players[player].tally;
}
cangrabitems = players[player].cangrabitems;
}
spectatorReentry = (betweenmaps ? 0 : players[player].spectatorReentry);
@ -2551,6 +2559,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
checkpointId = players[player].checkpointId;
enteredGame = players[player].enteredGame;
spectatewait = players[player].spectatewait;
p = &players[player];
memset(p, 0, sizeof (*p));
@ -2624,6 +2633,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
p->karthud[khud_fault] = khudfault;
p->kickstartaccel = kickstartaccel;
p->checkpointId = checkpointId;
p->spectatewait = spectatewait;
p->ringvolume = 255;
p->ringtransparency = 255;
@ -3868,6 +3878,12 @@ tryAgain:
continue;
}
if (numPlayers == 2 && gametype == GT_RACE && ((mapheaderinfo[i]->levelflags & LF_SECTIONRACE) == LF_SECTIONRACE))
{
// Duel doesn't support sprints.
continue;
}
// Only care about restrictions if the host is a listen server.
if (!dedicated)
{
@ -4090,6 +4106,7 @@ doremove:
// Next map apparatus
struct roundqueue roundqueue;
struct menuqueue menuqueue;
void G_MapSlipIntoRoundQueue(UINT8 position, UINT16 map, UINT8 setgametype, boolean setencore, boolean rankrestricted)
{
@ -5373,7 +5390,7 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr
S_ResumeAudio();
}
prevencoremode = ((!Playing()) ? false : encoremode);
prevencoremode = encoremode;
encoremode = pencoremode;
legitimateexit = false; // SRB2Kart
@ -5415,6 +5432,8 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr
players[i].xtralife = 0;
players[i].totalring = 0;
players[i].score = 0;
if (roundqueue.position == 0) // Don't unassign teams in tournament play
players[i].team = TEAM_UNASSIGNED;
}
if (resetplayer || !(gametyperules & GTR_CHECKPOINTS && map == gamemap))
@ -5823,7 +5842,7 @@ boolean G_GetExitGameFlag(void)
// Same deal with retrying.
void G_SetRetryFlag(void)
{
if (retrying == false)
if (retrying == false && grandprixinfo.gp)
{
grandprixinfo.rank.continuesUsed++;
}

View file

@ -75,6 +75,15 @@ extern struct roundqueue
roundentry_t entries[ROUNDQUEUE_MAX]; // Entries in the round queue
} roundqueue;
extern struct menuqueue
{
// Degenerate version of roundqueue exclusively for menu use.
UINT8 size;
UINT8 sending;
UINT8 anchor;
roundentry_t entries[ROUNDQUEUE_MAX];
} menuqueue;
void G_MapSlipIntoRoundQueue(UINT8 position, UINT16 map, UINT8 setgametype, boolean setencore, boolean rankrestricted);
void G_MapIntoRoundQueue(UINT16 map, UINT8 setgametype, boolean setencore, boolean rankrestricted);
void G_GPCupIntoRoundQueue(cupheader_t *cup, UINT8 setgametype, boolean setencore);

View file

@ -3640,7 +3640,7 @@ state_t states[NUMSTATES] =
{SPR_S_SP, FF_ANIMATE|FF_SEMIBRIGHT, -1, {NULL}, 3, 2, S_NULL}, // S_SLSTMACE
// MT_SEALEDSTAR_BUMPER
{SPR_SBMP, 0|FF_FULLBRIGHT, -1, {NULL}, 2, 8, S_SEALEDSTAR_BUMPER}, // S_SEALEDSTAR_BUMPER
{SPR_SBMP, 0|FF_FULLBRIGHT, -1, {A_GenericBumper}, 0, 56, S_SEALEDSTAR_BUMPER}, // S_SEALEDSTAR_BUMPER
{SPR_SBMP, 1|FF_ANIMATE|FF_FULLBRIGHT, 8, {NULL}, 1, 2, S_SEALEDSTAR_BUMPER}, // S_SEALEDSTAR_BUMPERHIT
// MT_SSCHAIN_SPAWNER
@ -13585,8 +13585,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
95*FRACUNIT, // radius
95*FRACUNIT, // height
108*FRACUNIT, // radius
50*FRACUNIT, // height
0, // display offset
100, // mass
0, // damage
@ -22233,7 +22233,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
0, // mass
0, // damage
sfx_None, // activesound
MF_SPECIAL|MF_NOGRAVITY, // flags
MF_NOGRAVITY|MF_SOLID, // flags
S_NULL // raisestate
},
{ // MT_SSCHAIN_SPAWNER

View file

@ -289,6 +289,7 @@ enum actionnum
A_MAKESSCANDLE,
A_HOLOGRAMRANDOMTRANSLUCENCY,
A_SSCHAINSHATTER,
A_GENERICBUMPER,
NUMACTIONS
};
@ -557,6 +558,7 @@ void A_BlendEyePuyoHack();
void A_MakeSSCandle();
void A_HologramRandomTranslucency();
void A_SSChainShatter();
void A_GenericBumper();
extern boolean actionsoverridden[NUMACTIONS];

View file

@ -2077,6 +2077,11 @@ void K_UpdateBotGameplayVars(player_t *player)
if (cv_levelskull.value)
player->botvars.difficulty = MAXBOTDIFFICULTY;
if (K_InRaceDuel())
player->botvars.rival = true;
else if (grandprixinfo.gp != true)
player->botvars.rival = false;
player->botvars.rubberband = K_UpdateRubberband(player);
player->botvars.turnconfirm += player->cmd.bot.turnconfirm;

View file

@ -75,7 +75,7 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2)
if (t1->type == MT_BALLHOGBOOM && t2->type == MT_BALLHOGBOOM)
return true; // Ballhogs don't collide with eachother
if (K_TryPickMeUp(t1, t2))
if (K_TryPickMeUp(t1, t2, false))
return true;
if (t2->player)
@ -178,7 +178,7 @@ boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2)
if (t1->health <= 0 || t2->health <= 0)
return true;
if (K_TryPickMeUp(t1, t2))
if (K_TryPickMeUp(t1, t2, false))
return true;
if (!P_CanPickupItem(t2->player, PICKUP_EGGBOX))
@ -434,7 +434,7 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2)
if (t1->health <= 0 || t2->health <= 0)
return true;
if (K_TryPickMeUp(t1, t2))
if (K_TryPickMeUp(t1, t2, false))
return true;
if (t2->player)
@ -544,7 +544,7 @@ boolean K_DropTargetCollide(mobj_t *t1, mobj_t *t2)
if (t2->player && (t2->player->hyudorotimer || t2->player->justbumped))
return true;
if (K_TryPickMeUp(t1, t2))
if (K_TryPickMeUp(t1, t2, false))
return true;
if (draggeddroptarget && P_MobjWasRemoved(draggeddroptarget))
@ -777,6 +777,12 @@ static inline BlockItReturn_t PIT_LightningShieldAttack(mobj_t *thing)
return BMIT_CONTINUE;
}
// see if it went over / under
if (lightningSource->z - lightningDist > thing->z + thing->height)
return BMIT_CONTINUE; // overhead
if (lightningSource->z + lightningSource->height + lightningDist < thing->z)
return BMIT_CONTINUE; // underneath
#if 0
if (P_CheckSight(lightningSource, thing) == false)
{
@ -967,6 +973,7 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim)
attackerPlayer->spindashboost = 0;
attackerPlayer->sneakertimer = 0;
attackerPlayer->panelsneakertimer = 0;
attackerPlayer->weaksneakertimer = 0;
attackerPlayer->instaWhipCharge = 0;
attackerPlayer->flashing = 0;
@ -1036,7 +1043,14 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim)
}
else if (victim->type == MT_DROPTARGET || victim->type == MT_DROPTARGET_SHIELD)
{
K_DropTargetCollide(victim, shield);
if (K_TryPickMeUp(attacker, victim, true))
{
shield->hitlag = attacker->hitlag; // players hitlag is handled in K_TryPickMeUp, and we need to set for the shield too
}
else
{
K_DropTargetCollide(victim, shield);
}
return true;
}
else
@ -1053,8 +1067,13 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim)
shield->extravalue1 = 1;
}
if (P_DamageMobj(victim, shield, attacker, 1, DMG_NORMAL))
if (K_TryPickMeUp(attacker, victim, true))
{
shield->hitlag = attacker->hitlag; // players hitlag is handled in K_TryPickMeUp, and we need to set for the shield too
}
else
{
P_DamageMobj(victim, shield, attacker, 1, DMG_NORMAL);
K_AddHitLag(attacker, attackerHitlag, false);
shield->hitlag = attacker->hitlag;
}
@ -1068,7 +1087,7 @@ boolean K_KitchenSinkCollide(mobj_t *t1, mobj_t *t2)
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 (K_TryPickMeUp(t1, t2))
if (K_TryPickMeUp(t1, t2, false))
return true;
if (t2->player)
@ -1193,7 +1212,7 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2)
{
auto shouldSteal = [](mobj_t *t1, mobj_t *t2)
{
return ((t1->player->sneakertimer > 0 || t1->player->panelsneakertimer > 0)
return ((t1->player->sneakertimer > 0 || t1->player->panelsneakertimer > 0 || t1->player->weaksneakertimer > 0)
&& !P_PlayerInPain(t1->player)
&& (t1->player->flashing == 0));
};

View file

@ -67,31 +67,6 @@ INT16 K_CalculateGPRankPoints(UINT16 exp, UINT8 position, UINT8 numplayers)
points = exp;
// Give bonus to high-ranking players, depending on player count
// This rounds out the point gain when you get 1st every race,
// and gives bots able to catch up in points if a player gets an early lead.
// The maximum points you can get in a cup is: ((number of players - 1) + (max extra points)) * (number of races)
// 8P: (7 + 5) * 5 = 60 maximum points
// 12P: (11 + 5) * 5 = 80 maximum points
// 16P: (15 + 5) * 5 = 100 maximum points
switch (numplayers)
{
case 0: case 1: case 2: // 1v1
break; // No bonus needed.
case 3: case 4: // 3-4P
if (position == 1) { points += 5; } // 1st gets +1 extra point
break;
case 5: case 6: // 5-6P
if (position == 1) { points += 10; } // 1st gets +3 extra points
// else if (position == 2) { points += 4; } // 2nd gets +1 extra point
break;
default: // Normal matches
if (position == 1) { points += 10; } // 1st gets +5 extra points
// else if (position == 2) { points += 5; } // 2nd gets +3 extra points
// else if (position == 3) { points += 2; } // 3rd gets +1 extra point
break;
}
// somehow underflowed?
if (points < 0)
{

View file

@ -235,6 +235,13 @@ static patch_t *kp_team_underlay[2][2];
static patch_t *kp_team_minihead;
static patch_t *kp_team_you;
static patch_t *kp_duel_foe;
static patch_t *kp_duel_you;
static patch_t *kp_duel_sticker;
static patch_t *kp_duel_under;
static patch_t *kp_duel_over;
static patch_t *kp_duel_margin[24];
patch_t *kp_autoroulette;
patch_t *kp_autoring;
@ -1061,6 +1068,20 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_team_underlay[1][1], "TEAM4UR");
HU_UpdatePatch(&kp_team_minihead, "TEAM4H");
HU_UpdatePatch(&kp_team_you, "TEAM_YOU");
HU_UpdatePatch(&kp_duel_foe, "DUEL_FOE");
HU_UpdatePatch(&kp_duel_sticker, "DUEL_S");
HU_UpdatePatch(&kp_duel_under, "DUEL_B");
HU_UpdatePatch(&kp_duel_over, "DUEL_B2");
HU_UpdatePatch(&kp_duel_you, "DUEL_YOU");
sprintf(buffer, "DUELMBxx");
for (i = 0; i < MARGINLEVELS; i++)
{
buffer[6] = '0'+(i/10);
buffer[7] = '0'+(i%10);
HU_UpdatePatch(&kp_duel_margin[i], "%s", buffer);
}
}
// For the item toggle menu
@ -1515,6 +1536,46 @@ void K_DrawLikeMapThumbnail(fixed_t x, fixed_t y, fixed_t width, UINT32 flags, p
);
}
void K_DrawMapAsFace(INT32 x, INT32 y, UINT32 flags, UINT16 map, const UINT8 *colormap)
{
const fixed_t iconHeight = (14 << FRACBITS);
const fixed_t iconWidth = (iconHeight * 320) / 200;
INT32 unit = 1;
fixed_t mul = FRACUNIT;
if (flags & V_NOSCALESTART)
{
unit = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
mul = 1;
}
V_DrawFill(
x,
y,
16 * unit,
16 * unit,
(flags & ~V_FLIP)
);
V_SetClipRect(
(x + unit) * mul,
(y + unit) * mul,
(14 * unit) * mul,
(14 * unit) * mul,
(flags & ~V_FLIP)
);
K_DrawMapThumbnail(
((x + unit) * FRACUNIT) - (iconWidth - iconHeight)/2,
((y + unit) * FRACUNIT),
iconWidth,
flags,
map,
colormap
);
V_ClearClipRect();
}
// see also K_DrawNameTagItemSpy
static void K_drawKartItem(void)
{
@ -2970,6 +3031,9 @@ static boolean K_drawKartPositionFaces(void)
if (!LUA_HudEnabled(hud_minirankings))
return false; // Don't proceed but still return true for free play above if HUD is disabled.
if (K_InRaceDuel())
return false;
switch (r_splitscreen)
{
case 0:
@ -3229,11 +3293,292 @@ INT32 K_GetTransFlagFromFixed(fixed_t value)
}
}
static tic_t duel_lastleveltime = 0;
static INT32 duel_marginanim = 0;
static INT32 duel_lastmargin = 0;
static INT32 youheight = 0;
static void K_drawKartDuelScores(void)
{
if (!K_InRaceDuel())
return;
using srb2::Draw;
player_t *foe = K_DuelOpponent(stplyr);
INT32 basex = 0;
INT32 basey = 40;
INT32 flags = V_SNAPTOLEFT|V_HUDTRANS|V_SLIDEIN;
// score bars, here barheight is the size of bars at tied score
INT32 barx = 8;
INT32 bary = 61;
INT32 barheight = 48;
INT32 barwidth = 6;
// portraits
INT32 foex = 16;
INT32 foey = 21;
INT32 youx = 16;
INT32 youy = 85;
// scores
INT32 foescorex = 16;
INT32 foescorey = 38;
INT32 youscorex = 16;
INT32 youscorey = 69;
Draw::Font scorefont = Draw::Font::kThinTimer;
UINT8 ri = 6;
INT32 youfill = skincolors[stplyr->skincolor].ramp[ri];
INT32 foefill = skincolors[foe->skincolor].ramp[ri];
V_DrawScaledPatch(basex, basey, flags, kp_duel_sticker);
INT32 scoredelta = stplyr->duelscore - foe->duelscore;
INT32 clutchscore = DUELWINNINGSCORE - 1; // we want the bar to be full when NEXT checkpoint wins...
INT32 savemargin = 3; // ...minus a little bit.
if (leveltime/(TICRATE/2) % 2)
savemargin += ((leveltime/2)%2);
if (clutchscore == 0)
clutchscore = 1; // Fuck it, just don't crash
INT32 targetyouheight = barheight*abs(clutchscore+scoredelta)/clutchscore;
if (targetyouheight == 0)
{
targetyouheight = savemargin;
}
else if (targetyouheight >= 2*barheight)
{
targetyouheight = 2*barheight - savemargin;
}
if (leveltime != duel_lastleveltime)
{
INT32 slide = std::max(1, abs(targetyouheight - youheight)/3);
if (targetyouheight > youheight)
youheight += slide;
else if (targetyouheight < youheight)
youheight -= slide;
}
INT32 foeheight = 2*barheight-youheight; // barheight is a single tied bar, so total height of the full gauge is 2x barheight
V_DrawFill(basex+barx, basey+bary-barheight, barwidth, foeheight, foefill|flags);
V_DrawFill(basex+barx, basey+bary-barheight+foeheight, barwidth, youheight, youfill|flags);
V_DrawScaledPatch(basex, basey, flags, kp_duel_under);
V_DrawScaledPatch(basex, basey-barheight+foeheight, flags, kp_duel_over);
V_DrawScaledPatch(basex, basey, flags, kp_duel_foe);
V_DrawScaledPatch(basex, basey, flags, kp_duel_you);
Draw foenum = Draw(basex+foescorex, basey+foescorey).flags(flags).font(scorefont).align(Draw::Align::kLeft);
Draw younum = Draw(basex+youscorex, basey+youscorey).flags(flags).font(scorefont).align(Draw::Align::kLeft);
if (abs(scoredelta) == clutchscore && ((leveltime % 2) || cv_reducevfx.value))
{
if (foe->duelscore > stplyr->duelscore)
foenum = foenum.colorize(SKINCOLOR_GOLD);
else
younum = younum.colorize(SKINCOLOR_GOLD);
}
foenum.text("{}", foe->duelscore);
younum.text("{}", stplyr->duelscore);
// minirankings shamelessly copypasted because i know that shit works already
// and SURELY we will never need to use this somewhere else, right?
UINT8 workingskin;
UINT8 *colormap;
INT32 xoff, yoff, flipflag, skinflags;
for (UINT8 draw = 0; draw < 2; draw++)
{
UINT8 drawme = draw ? (stplyr - players) : (foe - players);
UINT8 drawx = basex + (draw ? youx : foex);
UINT8 drawy = basey + (draw ? youy : foey);
if (!playeringame[drawme] || players[drawme].spectator)
continue;
if (!players[drawme].mo || P_MobjWasRemoved(players[drawme].mo))
continue;
skinflags = (demo.playback)
? demo.skinlist[demo.currentskinid[drawme]].flags
: skins[players[drawme].skin].flags;
// Flip SF_IRONMAN portraits, but only if they're transformed
if (skinflags & SF_IRONMAN
&& !(players[drawme].charflags & SF_IRONMAN) )
{
flipflag = V_FLIP|V_VFLIP; // blonic flip
xoff = yoff = 16;
} else
{
flipflag = 0;
xoff = yoff = 0;
}
if ((skin_t*)players[drawme].mo->skin)
workingskin = (skin_t*)players[drawme].mo->skin - skins;
else
workingskin = players[drawme].skin;
colormap = R_GetTranslationColormap(workingskin, static_cast<skincolornum_t>(players[drawme].mo->color), GTC_CACHE);
if (players[drawme].mo->colorized)
colormap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(players[drawme].mo->color), GTC_CACHE);
else
colormap = R_GetTranslationColormap(workingskin, static_cast<skincolornum_t>(players[drawme].mo->color), GTC_CACHE);
V_DrawMappedPatch(drawx+xoff, drawy+yoff, flags|flipflag, faceprefix[workingskin][FACE_RANK], colormap);
}
// Dogshit. Should have just figured out how to do log base 5 in C++.
// However, I think this works anyway.
// I did my best to comment this but the algorithm is honestly just bad and hard to
// reason about. Please don't try to maintain this, just yell at me if it needs any
// adjustments. -Tyron 2025-05-29
// DESIGN INTENT: Create realistic-looking Puyo garbage stacks, while using the
// leading garbage symbol as an indicator of the current Margin Boost value.
INT32 rawmargin = overtimecheckpoints; // The actual Margin Boost value.
INT32 boostspersymbol = 3; // How many boosts should it take to see a new symbol?
// rawmargin = (leveltime/10)%(3*boostspersymbol);
if (duel_lastleveltime != leveltime) // Trigger the "slide" animation when rawmargin changes.
{
duel_marginanim = std::min(duel_marginanim + 1, 100); // not magic just arbitrary
if (duel_lastmargin != rawmargin)
{
duel_marginanim = 0;
duel_lastmargin = rawmargin;
}
}
duel_lastleveltime = leveltime;
// CONS_Printf("=== RAWMARGIN %d\n", rawmargin);
if (rawmargin == 0)
return;
rawmargin--; // Start at 0, idiot
// We're invoking the RNG to get a slightly chaotic symbol distribution,
// but we're a HUD hook, so we need to keep the results of the call consistent.
P_SetRandSeed(PR_NUISANCE, 69 + rawmargin);
INT32 highsymbol = rawmargin/boostspersymbol + 1; // Highest symbol that should appear.
INT32 symbolsperupgrade = 5; // What is each symbol worth relative to each other? Like, 5 Stars = 1 Moon, etc.
// Okay, so we would LOVE to do this in a way that isn't a big clusterfuck, like just
// doing rawmargin^3 and then subtracting powers of 5 out of that. Unfortunately, UINT64
// is too small for the values that feel intuitively right here, so we have to do some of
// the math on a limited set of symbols, then shift up. This is the concept of "symbol
// headroom" that's in use here.
//
// (Note that Puyo~n uses a super inconsistent symbol table, probably to avoid this problem,
// but we're assholes and want things to feel logically consistent I guess?
// I dunno. I sort of feel like I should have just directly used the Puyo~n garbage table and
// avoided most of this, LOL)
INT32 symbolheadroom = 5; // Maximum # symbols we can "step down".
INT32 frac = rawmargin % boostspersymbol; // Used in intermediate calculations.
INT32 minsymbol = std::max(1, highsymbol - symbolheadroom); // The lowest symbol that should appear.
INT32 symbolheadroominuse = highsymbol - minsymbol; // The # of symbols we are stepping down.
INT32 minscore = std::pow(symbolsperupgrade, symbolheadroominuse+1);
INT32 maxscore = std::pow(symbolsperupgrade, symbolheadroominuse+2) - 1;
// CONS_Printf("min %d max %d\n", minscore, maxscore);
// We show the player successive combos with the same leading symbol, but we
// waht them to feel intuitively like they're increasing each time.
// Maxscore and minscore have been mapped to the correct power-of-N, so any
// point we pick between them will lead with the correct symbol once we adjust
// for symbol headroom. Pick a point that's appropriate for how "far" into the
// current symbol we are.
fixed_t lobound = FRACUNIT * frac / boostspersymbol;
fixed_t hibound = FRACUNIT * (frac+1) / boostspersymbol;
fixed_t roll = P_RandomRange(PR_NUISANCE, lobound, hibound);
INT32 margin = Easing_Linear(roll, minscore, maxscore); // The score we're trying to draw a garbage stack for.
INT32 margindigits[5];
memset(margindigits, -1, sizeof(margindigits));
INT32 nummargindigits = 0;
// CONS_Printf("margin %d min %d max %d roll %d shiu %d ms %d\n", margin, minscore, maxscore, roll, symbolheadroominuse, minsymbol);
if (rawmargin/boostspersymbol >= (MARGINLEVELS-1))
{
// Capped out. Show 5 Chaos.
nummargindigits = 5;
for(UINT8 i = 0; i < nummargindigits; i++)
{
margindigits[i] = MARGINLEVELS-1;
}
}
else
{
// Subtract powers of N from our chosen score to create a decent-enough-looking
// garbage stack, then queue up the right patches to be drawn, shifting all the math
// up by "minsymbol"—remember, once maxsymbol goes above symbolheadroom, we are doing
// a low-precision version of the math that ignores low enough symbols.
while (margin > 0)
{
INT32 significant_margin = 0;
for (UINT8 i = symbolheadroominuse+1; i >= 0; i--)
{
INT32 test = std::pow(symbolsperupgrade, i);
// CONS_Printf("testing %d (%d)\n", i, test);
if (margin >= test)
{
significant_margin = i;
break;
}
}
INT32 index = significant_margin;
margindigits[nummargindigits] = index + minsymbol - 1;
// CONS_Printf("digit %d %d\n", nummargindigits, margindigits[nummargindigits]);
nummargindigits++;
// CONS_Printf("margin was %d ", margin);
margin -= std::pow(symbolsperupgrade, index);
// CONS_Printf("is %d\n", margin);
if (nummargindigits >= 3 + frac)
break;
}
}
INT32 marginspacing = std::min(6, duel_marginanim);
INT32 marginx = ((nummargindigits-1) * marginspacing)/2;
for (INT32 i = nummargindigits - 1; i >= 0; i--)
{
// CONS_Printf("draw %d - %d\n", i, margindigits[i]);
V_DrawScaledPatch(basex + marginx, basey, flags, kp_duel_margin[margindigits[i]]);
marginx -= marginspacing;
}
}
static INT32 easedallyscore = 0;
static tic_t scorechangecooldown = 0;
// Mildly ugly. Don't want to export this to khud when it's so nicely handled here,
// but HUD hooks run at variable timing based on your actual framerate.
static tic_t lastleveltime = 0;
static tic_t teams_lastleveltime = 0;
static void K_drawKartTeamScores(void)
{
@ -3309,6 +3654,9 @@ static void K_drawKartTeamScores(void)
UINT16 enemyscore = g_teamscores[enemies];
UINT16 totalscore = allyscore + enemyscore;
if (totalscore == 0)
return;
using srb2::Draw;
srb2::Draw::Font scorefont = Draw::Font::kTimer;
@ -3360,11 +3708,11 @@ static void K_drawKartTeamScores(void)
}
else
{
if (lastleveltime != leveltime) // Timing consistency
if (teams_lastleveltime != leveltime) // Timing consistency
{
INT32 delta = abs(easedallyscore - allyscore); // how wrong is display score?
if (scorechangecooldown == 0)
if (scorechangecooldown == 0 && delta)
{
if (allyscore > easedallyscore)
{
@ -3387,7 +3735,7 @@ static void K_drawKartTeamScores(void)
enemyscore = totalscore - allyscore;
}
lastleveltime = leveltime;
teams_lastleveltime = leveltime;
fixed_t enemypercent = FixedDiv(enemyscore*FRACUNIT, totalscore*FRACUNIT);
// fixed_t allypercent = FixedDiv(allyscore*FRACUNIT, totalscore*FRACUNIT);
@ -3531,6 +3879,11 @@ static void K_drawKartTeamScores(void)
*/
}
static boolean K_DrawingLaps()
{
return (numlaps != 1 && !K_InRaceDuel() && (UINT16)stplyr->exp != UINT16_MAX);
}
static boolean K_drawKartLaps(void)
{
INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN;
@ -3543,7 +3896,9 @@ static boolean K_drawKartLaps(void)
// I do not understand the way this system of offsets is laid out at all,
// so it's probably going to be pretty bad to maintain. Sorry.
if (numlaps != 1 && displayEXP != UINT16_MAX)
boolean drawinglaps = (numlaps != 1 && !K_InRaceDuel() && displayEXP != UINT16_MAX);
if (drawinglaps)
{
if (r_splitscreen > 1)
bump = 27;
@ -3551,7 +3906,7 @@ static boolean K_drawKartLaps(void)
bump = 40;
}
if (numlaps != 1)
if (drawinglaps)
{
if (r_splitscreen > 1)
{
@ -3968,7 +4323,16 @@ static void K_drawKartAccessibilityIcons(boolean gametypeinfoshown, INT32 fx)
}
else
{
fx = LAPS_X+44;
fx = LAPS_X + 33;
if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS)
fx -= 14;
if (K_DrawingLaps())
{
fx += 30;
}
fy = LAPS_Y;
if (R_GetViewNumber() & 1) // If we are not P1 or P3...
{
@ -4862,7 +5226,7 @@ playertagtype_t K_WhichPlayerTag(player_t *p)
}
else if (p->bot)
{
if (p->botvars.rival == true || cv_levelskull.value)
if ((p->botvars.rival == true || cv_levelskull.value) && (!K_InRaceDuel()))
{
return PLAYERTAG_RIVAL;
}
@ -6717,10 +7081,8 @@ static void K_DrawGPRankDebugger(void)
V_DrawThinString(0, 0, V_SNAPTOTOP|V_SNAPTOLEFT,
va("POS: %d / %d", grandprixinfo.rank.position, RANK_NEUTRAL_POSITION));
V_DrawThinString(0, 10, V_SNAPTOTOP|V_SNAPTOLEFT,
va("PTS: %d / %d", grandprixinfo.rank.winPoints, grandprixinfo.rank.totalPoints));
V_DrawThinString(0, 20, V_SNAPTOTOP|V_SNAPTOLEFT,
va("LAPS: %d / %d", grandprixinfo.rank.exp, grandprixinfo.rank.totalExp));
va("EXP: %d / %d", grandprixinfo.rank.exp, grandprixinfo.rank.totalExp));
V_DrawThinString(0, 30, V_SNAPTOTOP|V_SNAPTOLEFT,
va("CONTINUES: %d", grandprixinfo.rank.continuesUsed));
V_DrawThinString(0, 40, V_SNAPTOTOP|V_SNAPTOLEFT,
@ -6944,35 +7306,39 @@ static void K_DrawMessageFeed(void)
text.font(Draw::Font::kMenu);
UINT8 x = BASEVIDWIDTH/2;
UINT8 y = 10;
UINT32 vw = vid.width / vid.dupx;
UINT32 vh = vid.height / vid.dupy;
UINT32 x = vw / 2;
UINT32 y = 10;
SINT8 shift = 0;
if (r_splitscreen >= 2)
{
text.font(Draw::Font::kThin);
shift = -2;
x = BASEVIDWIDTH/4;
x = vw/4;
y = 5;
if (i % 2)
x += BASEVIDWIDTH/2;
x += vw / 2;
if (i >= 2)
y += BASEVIDHEIGHT / 2;
y += vh / 2;
}
else if (r_splitscreen >= 1)
{
y = 5;
if (i >= 1)
y += BASEVIDHEIGHT / 2;
y += vh / 2;
}
UINT16 sw = text.width();
K_DrawSticker(x - sw/2, y, sw, 0, true);
Draw(x, y+shift).align(Draw::Align::kCenter).text(text);
K_DrawSticker(x - sw/2, y, sw, V_SNAPTOTOP|V_SNAPTOLEFT, true);
Draw(x, y+shift).align(Draw::Align::kCenter).flags(V_SNAPTOTOP|V_SNAPTOLEFT).text(text);
}
}
@ -7253,9 +7619,14 @@ void K_drawKartHUD(void)
K_drawKartTeamScores();
}
if (K_InRaceDuel())
{
K_drawKartDuelScores();
}
if (LUA_HudEnabled(hud_gametypeinfo))
{
if (gametyperules & GTR_CIRCUIT)
if (gametyperules & GTR_CIRCUIT && !K_InRaceDuel())
{
K_drawKartLaps();
gametypeinfoshown = true;

View file

@ -25,6 +25,8 @@ extern "C" {
#define POS_DELAY_TIME 10
#define MARGINLEVELS 24
extern INT32 MINI_X, MINI_Y;
struct trackingResult_t
@ -58,6 +60,7 @@ void K_drawKart4PTimestamp(void);
void K_drawEmeraldWin(boolean overlay);
void K_DrawMapThumbnail(fixed_t x, fixed_t y, fixed_t width, UINT32 flags, UINT16 map, const UINT8 *colormap);
void K_DrawLikeMapThumbnail(fixed_t x, fixed_t y, fixed_t width, UINT32 flags, patch_t *patch, const UINT8 *colormap);
void K_DrawMapAsFace(INT32 x, INT32 y, UINT32 flags, UINT16 map, const UINT8 *colormap);
void K_drawTargetHUD(const vector3_t *origin, player_t *player);
void K_drawButton(fixed_t x, fixed_t y, INT32 flags, patch_t *button[2], boolean pressed);
void K_drawButtonAnim(INT32 x, INT32 y, INT32 flags, patch_t *button[2], tic_t animtic);

View file

@ -143,6 +143,8 @@ struct TargetTracking
return false;
default:
if (K_IsPickMeUpItem(mobj->type))
return false;
return true;
}
}
@ -277,28 +279,17 @@ private:
{{6, 2, {kp_spraycantarget_far[1]}, V_ADD}}, // 4P
}},
};
case MT_JAWZ:
case MT_JAWZ_SHIELD:
case MT_ORBINAUT:
case MT_ORBINAUT_SHIELD:
case MT_DROPTARGET:
case MT_DROPTARGET_SHIELD:
case MT_LANDMINE:
case MT_BANANA:
case MT_BANANA_SHIELD:
case MT_GACHABOM:
case MT_EGGMANITEM:
case MT_EGGMANITEM_SHIELD:
case MT_BUBBLESHIELDTRAP:
return {
{ // Near
{2, TICRATE/2, {kp_pickmeup}, 0}, // 1P
{{2, TICRATE/2, {kp_pickmeup}, 0}}, // 4P
},
};
default:
if (K_IsPickMeUpItem(mobj->type))
{
return {
{ // Near
{2, TICRATE/2, {kp_pickmeup}, 0}, // 1P
{{2, TICRATE/2, {kp_pickmeup}, 0}}, // 4P
},
};
}
return {
{ // Near
{8, 2, {kp_capsuletarget_near[0]}}, // 1P
@ -902,32 +893,17 @@ void K_drawTargetHUD(const vector3_t* origin, player_t* player)
if (tracking)
{
fixed_t itemOffset = 36*mobj->scale;
switch (mobj->type)
if (K_IsPickMeUpItem(mobj->type))
{
case MT_JAWZ:
case MT_JAWZ_SHIELD:
case MT_ORBINAUT:
case MT_ORBINAUT_SHIELD:
case MT_DROPTARGET:
case MT_DROPTARGET_SHIELD:
case MT_LANDMINE:
case MT_BANANA:
case MT_BANANA_SHIELD:
case MT_GACHABOM:
case MT_BUBBLESHIELDTRAP:
case MT_EGGMANITEM:
case MT_EGGMANITEM_SHIELD:
if (stplyr->mo->eflags & MFE_VERTICALFLIP)
{
pos.z -= itemOffset;
}
else
{
pos.z += itemOffset;
}
break;
default:
break;
if (stplyr->mo->eflags & MFE_VERTICALFLIP)
{
pos.z -= itemOffset;
}
else
{
pos.z += itemOffset;
}
}
K_ObjectTracking(&tr.result, &pos, false);

View file

@ -119,6 +119,27 @@ boolean K_DuelItemAlwaysSpawns(mapthing_t *mt)
return !!(mt->thing_args[0]);
}
boolean K_InRaceDuel(void)
{
return (inDuel && (gametyperules & GTR_CIRCUIT) && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)) && !specialstageinfo.valid;
}
player_t *K_DuelOpponent(player_t *player)
{
if (!K_InRaceDuel())
return player; // ????
else
{
for (UINT8 i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && !players[i].spectator && player - players != i)
return &players[i];
}
}
return player; // ????????????
}
static void K_SpawnDuelOnlyItems(void)
{
mapthing_t *mt = NULL;
@ -145,6 +166,7 @@ void K_TimerReset(void)
numbulbs = 1;
inDuel = rainbowstartavailable = false;
linecrossed = 0;
overtimecheckpoints = 0;
timelimitintics = extratimeintics = secretextratime = 0;
g_pointlimit = 0;
}
@ -273,6 +295,9 @@ void K_TimerInit(void)
introtime = (108) + 5; // 108 for rotation, + 5 for white fade
numbulbs += (numPlayers-2); // Extra POSITION!! time
}
if (K_InRaceDuel())
numlaps = 99;
}
}
@ -446,6 +471,9 @@ boolean K_IsPlayerScamming(player_t *player)
if (!M_NotFreePlay())
return false;
if (!(gametyperules & GTR_CIRCUIT))
return false;
// "Why 8?" Consistency
// "Why 2000?" Vibes
return (K_GetItemRouletteDistance(player, 8) < SCAMDIST);
@ -468,7 +496,10 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value)
if (cv_4thgear.value && !netgame && (!demo.playback || !demo.netgame) && !modeattacking)
value = 3;
return ((13 + (3*value)) << FRACBITS) / 16;
fixed_t base = ((13 + (3*value)) << FRACBITS) / 16;
fixed_t duel = overtimecheckpoints*(1<<FRACBITS)/32;
return base + duel;
}
// Array of states to pick the starting point of the animation, based on the actual time left for invincibility.
@ -2981,7 +3012,7 @@ void K_MomentumToFacing(player_t *player)
boolean K_ApplyOffroad(const player_t *player)
{
if (player->invincibilitytimer || player->hyudorotimer || player->sneakertimer || player->panelsneakertimer)
if (player->invincibilitytimer || player->hyudorotimer || player->sneakertimer || player->panelsneakertimer|| player->weaksneakertimer)
return false;
if (K_IsRidingFloatingTop(player))
return false;
@ -2990,7 +3021,7 @@ boolean K_ApplyOffroad(const player_t *player)
boolean K_SlopeResistance(const player_t *player)
{
if (player->invincibilitytimer || player->sneakertimer || player->panelsneakertimer || player->tiregrease || player->flamedash)
if (player->invincibilitytimer || player->sneakertimer || player->panelsneakertimer || player->weaksneakertimer || player->tiregrease || player->flamedash)
return true;
if (player->curshield == KSHIELD_TOP)
return true;
@ -3131,6 +3162,7 @@ boolean K_WaterRun(mobj_t *mobj)
if (mobj->player->invincibilitytimer
|| mobj->player->sneakertimer
|| mobj->player->panelsneakertimer
|| mobj->player->weaksneakertimer
|| mobj->player->tiregrease
|| mobj->player->flamedash
|| mobj->player->speed > minspeed)
@ -3495,19 +3527,29 @@ static void K_GetKartBoostPower(player_t *player)
UINT8 i;
for (i = 0; i < player->numsneakers; i++)
{
ADDBOOST(FRACUNIT*85/100, 8*FRACUNIT, HANDLESCALING+HANDLESCALING/3); // + 50% top speed, + 800% acceleration, +50% handling
ADDBOOST(FRACUNIT, 8*FRACUNIT, HANDLESCALING+HANDLESCALING/3); // + 100% top speed, + 800% acceleration, +50%(???) handling
}
}
if (player->panelsneakertimer) // Sneaker
if (player->panelsneakertimer) // Sneaker panel
{
UINT8 i;
for (i = 0; i < player->numpanelsneakers; i++)
{
ADDBOOST(FRACUNIT/2, 8*FRACUNIT, HANDLESCALING); // + 50% top speed, + 800% acceleration, +50% handling
ADDBOOST(FRACUNIT/2, 8*FRACUNIT, HANDLESCALING); // + 50% top speed, + 800% acceleration, +50%(???) handling
}
}
if (player->weaksneakertimer) // Rocket sneaker boost
{
UINT8 i;
for (i = 0; i < player->numweaksneakers; i++)
{
ADDBOOST((FRACUNIT*85)/100, 8*FRACUNIT, HANDLESCALING+HANDLESCALING/3); // + 85% top speed, + 800% acceleration, +50%(???) handling
}
}
//NOTE: The various sneaker booth strengths are also defined in K_DoSneaker()!
if (player->invincibilitytimer) // Invincibility
{
// S-Monitor: no extra %
@ -3564,7 +3606,7 @@ static void K_GetKartBoostPower(player_t *player)
Easing_InCubic(
player->overdrivepower,
0,
5*FRACUNIT/10
75*FRACUNIT/100
),
Easing_InSine(
player->overdrivepower,
@ -3640,7 +3682,7 @@ static void K_GetKartBoostPower(player_t *player)
{
fixed_t ringboost_base = FRACUNIT/4;
if (player->overdrive)
ringboost_base += FRACUNIT/2;
ringboost_base += FRACUNIT/4;
// This one's a little special: we add extra top speed per tic of ringboost stored up, to allow for Ring Box to really rocket away.
// (We compensate when decrementing ringboost to avoid runaway exponential scaling hell.)
fixed_t rb = FixedDiv(player->ringboost * FRACUNIT, max(FRACUNIT, K_RingDurationBoost(player)));
@ -3971,7 +4013,7 @@ SINT8 K_GetForwardMove(const player_t *player)
return 0;
}
if (player->sneakertimer || player->panelsneakertimer || player->spindashboost
if (player->sneakertimer || player->panelsneakertimer || player->weaksneakertimer || player->spindashboost
|| (((gametyperules & (GTR_ROLLINGSTART|GTR_CIRCUIT)) == (GTR_ROLLINGSTART|GTR_CIRCUIT)) && (leveltime < TICRATE/2)))
{
return MAXPLMOVE;
@ -4134,8 +4176,8 @@ void K_SpawnAmps(player_t *player, UINT8 amps, mobj_t *impact)
if (amps == 0)
return;
UINT32 itemdistance = max(0, min( FRACUNIT, K_GetItemRouletteDistance(player, D_NumPlayersInRace()))); // cap this to FRACUNIT, so it doesn't wrap when turning it into fixed_t
fixed_t itemdistmult = FRACUNIT + max( 0, min(FixedMul(FixedDiv(itemdistance<<FRACBITS, MAXAMPSCALINGDIST<<FRACBITS),FRACUNIT), FRACUNIT));
UINT32 itemdistance = max(0, min( FRACUNIT-1, K_GetItemRouletteDistance(player, D_NumPlayersInRace()))); // cap this to FRACUNIT-1, so it doesn't wrap when turning it into fixed_t
fixed_t itemdistmult = FRACUNIT + max( 0, min( FRACUNIT, (itemdistance<<FRACBITS) / MAXAMPSCALINGDIST));
UINT16 scaledamps = min(amps, amps * (10 + (9-player->kartspeed) - (9-player->kartweight)) / 10);
// Debug print for scaledamps calculation
// CONS_Printf("K_SpawnAmps: player=%s, amps=%d, kartspeed=%d, kartweight=%d, itemdistance=%d, itemdistmult=%0.2f, statscaledamps=%d, distscaledamps=%d\n",
@ -4239,7 +4281,7 @@ void K_CheckpointCrossAward(player_t *player)
if (gametype != GT_RACE)
return;
player->gradingfactor += K_GetGradingMultAdjustment(player);
player->gradingfactor += K_GetGradingFactorAdjustment(player);
player->gradingpointnum++;
player->exp = K_GetEXP(player);
//CONS_Printf("player: %s factor: %.2f exp: %d\n", player_names[player-players], FIXED_TO_FLOAT(player->gradingfactor), player->exp);
@ -4247,6 +4289,92 @@ void K_CheckpointCrossAward(player_t *player)
player->cangrabitems = 1;
K_AwardPlayerRings(player, (player->bot ? 20 : 10), true);
// Update Duel scoring.
if (K_InRaceDuel() && player->position == 1)
{
player->duelscore += 1;
if (leveltime > (tic_t)(TICRATE*DUELOVERTIME))
{
overtimecheckpoints++;
if (overtimecheckpoints > 1)
{
K_AddMessage(va("Margin Boost x%d!", overtimecheckpoints), true, false);
}
else
{
K_AddMessage("Margin Boost!", true, false);
g_darkness.start = leveltime;
g_darkness.end = INT32_MAX;
for (UINT8 i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
g_darkness.value[i] = FRACUNIT;
}
}
S_StartSound(NULL, sfx_gsha6);
}
player_t *opp = K_DuelOpponent(player);
boolean clutch = (player->duelscore - opp->duelscore == (DUELWINNINGSCORE-1));
boolean win = (player->duelscore - opp->duelscore == DUELWINNINGSCORE);
if (!win)
{
for (UINT8 i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
player_t *check = &players[displayplayers[i]];
if (check == player)
{
S_StartSound(NULL, sfx_mbs45);
if (clutch)
S_StartSoundAtVolume(NULL, sfx_s3k9c, 170);
}
else if (check == opp)
{
S_StartSound(NULL, sfx_mbs60);
if (clutch)
S_StartSoundAtVolume(NULL, sfx_kc4b, 150);
}
}
}
if (player->duelscore - opp->duelscore == DUELWINNINGSCORE)
{
opp->position = 2;
player->position = 1;
if (opp->distancetofinish - player->distancetofinish < 200) // Setting player.exiting changes distance reporting, check these first!
{
K_StartRoundWinCamera(
player->mo,
player->angleturn + ANGLE_180,
400*mapobjectscale,
6*TICRATE,
FRACUNIT/16
);
}
S_StartSound(NULL, sfx_s3k6a);
P_DoPlayerExit(player, 0);
P_DoAllPlayersExit(PF_NOCONTEST, 0);
}
else
{
// Doing this here because duel exit is a weird path, and we don't want to transform for endcam.
UINT32 skinflags = (demo.playback)
? demo.skinlist[demo.currentskinid[(player-players)]].flags
: skins[player->skin].flags;
if (skinflags & SF_IRONMAN)
{
SetRandomFakePlayerSkin(player, true, false);
}
}
}
}
boolean K_Overdrive(player_t *player)
@ -4948,7 +5076,7 @@ static boolean K_IsLosingWavedash(player_t *player)
if (!K_Sliptiding(player) && player->wavedash < MIN_WAVEDASH_CHARGE)
return true;
if (!K_Sliptiding(player) && player->drift == 0
&& P_IsObjectOnGround(player->mo) && player->sneakertimer == 0 && player->panelsneakertimer == 0
&& P_IsObjectOnGround(player->mo) && player->sneakertimer == 0 && player->panelsneakertimer == 0 && player->weaksneakertimer == 0
&& player->driftboost == 0)
return true;
return false;
@ -7087,8 +7215,14 @@ void K_DoSneaker(player_t *player, INT32 type)
fixed_t intendedboost = FRACUNIT/2;
// If you've already got an item sneaker type boost, panel sneakers will instead turn into item sneaker boosts
if (player->numsneakers && type == 0)
// If you've already got an rocket sneaker type boost, panel sneakers will instead turn into rocket sneaker boosts
if (player->numweaksneakers && type == 0)
{
type = 2;
}
// If you've already got an item sneaker type boost, other sneakers will instead turn into item sneaker boosts
if (player->numsneakers && type != 1)
{
type = 1;
}
@ -7099,10 +7233,13 @@ void K_DoSneaker(player_t *player, INT32 type)
intendedboost = FRACUNIT/2;
break;
case 1: // Single item sneaker
case 2: // Rocket sneaker
intendedboost = 100*FRACUNIT/100;
break;
case 2: // Rocket sneaker (aka. weaksneaker)
intendedboost = 85*FRACUNIT/100;
break;
}
//NOTE: The various sneaker booth strengths are also defined in K_GetKartBoostPower()!
if (player->roundconditions.touched_sneakerpanel == false
&& !(player->exiting || (player->pflags & PF_NOCONTEST))
@ -7118,7 +7255,7 @@ void K_DoSneaker(player_t *player, INT32 type)
const sfxenum_t smallsfx = sfx_cdfm40;
sfxenum_t sfx = normalsfx;
if (player->numsneakers || player->numpanelsneakers)
if (player->numsneakers || player->numpanelsneakers || player->numweaksneakers)
{
// Use a less annoying sound when stacking sneakers.
sfx = smallsfx;
@ -7134,17 +7271,19 @@ void K_DoSneaker(player_t *player, INT32 type)
switch (type)
{
case 0:
case 0: // Panel sneaker
player->numpanelsneakers++;
break;
case 1:
case 2:
case 1: // Single item sneaker
player->numsneakers++;
break;
case 2: // Rocket sneaker (aka. weaksneaker)
player->numweaksneakers++;
break;
}
}
if (player->sneakertimer == 0 && player->panelsneakertimer == 0)
if (player->sneakertimer == 0 && player->panelsneakertimer == 0 && player->weaksneakertimer == 0)
{
if (type == 2)
{
@ -7176,17 +7315,19 @@ void K_DoSneaker(player_t *player, INT32 type)
switch (type)
{
case 0:
case 0: // Panel sneaker
player->panelsneakertimer = sneakertime;
player->overshield += 1;
if (player->overshield > 0) {
player->overshield = min( player->overshield + TICRATE/3, max( TICRATE, player->overshield ));
}
break;
case 1:
case 1: // Single item sneaker
player->sneakertimer = sneakertime;
player->overshield += TICRATE/2;
player->overshield = max( player->overshield, 25 );
break;
case 2:
player->sneakertimer = 3*sneakertime/4;
player->overshield += TICRATE/2;
case 2: // Rocket sneaker (aka. weaksneaker)
player->weaksneakertimer = 3*sneakertime/4;
player->overshield = max( player->overshield, TICRATE/2 );
break;
}
@ -7321,7 +7462,7 @@ void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound)
}
}
if (mo->player->sneakertimer || mo->player->panelsneakertimer || mo->player->invincibilitytimer)
if (mo->player->sneakertimer || mo->player->panelsneakertimer || mo->player->weaksneakertimer || mo->player->invincibilitytimer)
{
thrust = FixedMul(thrust, (3*FRACUNIT)/2);
}
@ -8925,6 +9066,7 @@ static void K_UpdateTripwire(player_t *player)
boolean goodSpeed = (player->speed >= speedThreshold);
boolean boostExists = (player->tripwireLeniency > 0); // can't be checked later because of subtractions...
tripwirepass_t triplevel = K_TripwirePassConditions(player);
boolean mightplaysound = false;
if (triplevel != TRIPWIRE_NONE)
{
@ -8933,12 +9075,7 @@ static void K_UpdateTripwire(player_t *player)
mobj_t *front = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_TRIPWIREBOOST);
mobj_t *back = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_TRIPWIREBOOST);
if (P_IsDisplayPlayer(player))
{
S_StartSound(player->mo, sfx_s3k40);
S_StopSoundByID(player->mo, sfx_gshaf);
}
mightplaysound = true;
P_SetTarget(&front->target, player->mo);
P_SetTarget(&back->target, player->mo);
@ -8957,6 +9094,12 @@ static void K_UpdateTripwire(player_t *player)
if (triplevel != TRIPWIRE_CONSUME)
player->tripwireLeniency = max(player->tripwireLeniency, TRIPWIRETIME);
if (P_IsDisplayPlayer(player) && player->tripwireLeniency && mightplaysound)
{
S_StartSound(player->mo, TRIPWIRE_OK_SOUND);
S_StopSoundByID(player->mo, TRIPWIRE_NG_SOUND);
}
}
// TRIPWIRE_CONSUME is only applied in very specific cases (currently, riding Garden Top)
@ -9081,7 +9224,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
if (player->speed > 0)
{
// Speed lines
if (player->sneakertimer || player->panelsneakertimer || player->ringboost
if (player->sneakertimer || player->panelsneakertimer || player->weaksneakertimer || player->ringboost
|| player->driftboost || player->startboost
|| player->eggmanexplode || player->trickboost
|| player->gateBoost || player->wavedashboost)
@ -9322,7 +9465,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
if (((P_IsObjectOnGround(player->mo)
|| ( player->spinouttype & KSPIN_AIRTIMER ))
&& (!player->sneakertimer)
&& (!player->panelsneakertimer))
&& (!player->panelsneakertimer)
&& (!player->weaksneakertimer))
|| (player->respawn.state != RESPAWNST_NONE
&& player->spinouttimer > 1
&& (leveltime & 1)))
@ -9518,6 +9662,16 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
}
}
if (player->weaksneakertimer)
{
player->weaksneakertimer--;
if (player->weaksneakertimer <= 0)
{
player->numweaksneakers = 0;
}
}
if (player->trickboost)
player->trickboost--;
@ -9537,7 +9691,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
if (player->counterdash)
player->counterdash--;
if ((player->sneakertimer || player->panelsneakertimer) && player->wipeoutslow > 0 && player->wipeoutslow < wipeoutslowtime+1)
if ((player->sneakertimer || player->panelsneakertimer || player->weaksneakertimer) && player->wipeoutslow > 0 && player->wipeoutslow < wipeoutslowtime+1)
player->wipeoutslow = wipeoutslowtime+1;
if (player->floorboost > 0)
@ -9662,6 +9816,26 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
player->invincibilitytimer--;
if (player->invincibilitytimer && K_IsPlayerScamming(player))
player->invincibilitytimer--;
// Extra tripwire leniency for the end of invincibility
if (player->invincibilitytimer <= 0) {
player->tripwireLeniency = max( player->tripwireLeniency, TICRATE );
}
}
// The precise ordering of start-of-level made me want to cut my head off,
// so let's try this instead. Whatever!
if (leveltime <= starttime || player->gradingpointnum == 0)
{
if ((gametyperules & GTR_SPHERES)
|| (gametyperules & GTR_CATCHER)
|| G_TimeAttackStart()
|| (gametype == GT_TUTORIAL)
|| !M_NotFreePlay()
|| (K_GetNumWaypoints() == 0))
player->cangrabitems = EARLY_ITEM_FLICKER;
else
player->cangrabitems = 0;
}
if (player->cangrabitems && player->cangrabitems <= EARLY_ITEM_FLICKER)
@ -9826,6 +10000,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
player->tiregrease = 0;
player->sneakertimer = 0;
player->panelsneakertimer = 0;
player->weaksneakertimer = 0;
player->spindashboost = 0;
player->flashing = TICRATE/2;
player->ringboost = 0;
@ -10993,7 +11168,7 @@ static void K_UpdateDistanceFromFinishLine(player_t *const player)
const mapheader_t *mapheader = mapheaderinfo[gamemap - 1];
if ((mapheader->levelflags & LF_SECTIONRACE) == 0U)
{
const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection;
UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection;
player->distancetofinish += numfulllapsleft * K_GetCircuitLength();
}
}
@ -11041,6 +11216,7 @@ static void K_UpdatePlayerWaypoints(player_t *const player)
player->respawn.state == RESPAWNST_NONE && // Respawning should be a full reset.
old_currentwaypoint != NULL && // So should touching the first waypoint ever.
player->laps != 0 && // POSITION rooms may have unorthodox waypoints to guide bots.
player->exiting == 0 && // What the fuck? Why do duels antiskip the bot?
!(player->pflags & PF_TRUSTWAYPOINTS)) // Special exception.
{
extern consvar_t cv_debuglapcheat;
@ -11337,6 +11513,14 @@ INT16 K_GetKartTurnValue(const player_t *player, INT16 turnvalue)
turnfixed = FixedMul(turnfixed, 2*FRACUNIT); // Base increase to turning
}
/*
if (overtimecheckpoints)
{
fixed_t assistpercent = FRACUNIT * overtimecheckpoints / 32;
turnfixed += FixedMul(Easing_Linear(assistpercent, 0, FRACUNIT/2), turnfixed);
}
*/
if (player->drift != 0 && P_IsObjectOnGround(player->mo))
{
if (G_CompatLevel(0x000A))
@ -11393,7 +11577,7 @@ INT16 K_GetKartTurnValue(const player_t *player, INT16 turnvalue)
// If you're sliptiding, don't interact with handling boosts.
// You need turning power proportional to your speed, no matter what!
fixed_t topspeed = K_GetKartSpeed(player, false, false);
if (K_Sliptiding(player))
if (K_Sliptiding(player) || player->flamedash)
{
fixed_t sliptide_handle;
@ -12040,6 +12224,11 @@ void K_KartUpdatePosition(player_t *player)
realplayers++;
}
}
else if (K_InRaceDuel() && player->exiting)
{
// Positions directly set in K_CheckpointCrossAward, don't touch.
return;
}
else
{
for (i = 0; i < MAXPLAYERS; i++)
@ -12146,7 +12335,9 @@ void K_KartUpdatePosition(player_t *player)
/* except in FREE PLAY */
if (player->curshield == KSHIELD_TOP &&
(gametyperules & GTR_CIRCUIT) &&
realplayers > 1)
realplayers > 1 &&
!specialstageinfo.valid
&& !K_Cooperative())
{
/* grace period so you don't fall off INSTANTLY */
if (K_GetItemRouletteDistance(player, 8) < 2000 && player->topinfirst < 2*TICRATE) // "Why 8?" Literally no reason, but since we intend for constant-ish distance we choose a fake fixed playercount.
@ -13708,7 +13899,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{
UINT32 behind = K_GetItemRouletteDistance(player, player->itemRoulette.playing);
UINT32 behindScaled = behind * TICRATE / 4500;
behindScaled = min(behindScaled, 10*TICRATE);
behindScaled = min(behindScaled, 15*TICRATE);
K_DoInvincibility(player,
max(7u * TICRATE + behindScaled, player->invincibilitytimer + 5u*TICRATE));
@ -14261,7 +14452,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{
P_Thrust(
player->mo, player->mo->angle,
FixedMul((50*player->mo->scale), K_GetKartGameSpeedScalar(gamespeed))
FixedMul((80*player->mo->scale), K_GetKartGameSpeedScalar(gamespeed))
);
player->wavedashboost += TICRATE;
@ -15277,8 +15468,17 @@ void K_EggmanTransfer(player_t *source, player_t *victim)
if (victim->eggmanexplode)
return;
boolean prank = false;
if (victim->itemRoulette.eggman)
{
K_StopRoulette(&source->itemRoulette);
prank = true; // Give the transferring player the victim's eggbox roulette?!
}
K_AddHitLag(victim->mo, 5, false);
K_DropItems(victim);
victim->eggmanexplode = 6*TICRATE;
victim->eggmanblame = (source - players);
K_StopRoulette(&victim->itemRoulette);
@ -15287,9 +15487,20 @@ void K_EggmanTransfer(player_t *source, player_t *victim)
S_StartSound(NULL, sfx_itrole);
K_AddHitLag(source->mo, 5, false);
source->eggmanexplode = 0;
source->eggmanblame = -1;
K_StopRoulette(&source->itemRoulette);
if (prank)
{
source->eggmanexplode = 0;
source->eggmanblame = (victim - players);
K_StartEggmanRoulette(source);
S_StartSound(source->mo, sfx_s223);
}
else
{
source->eggmanexplode = 0;
source->eggmanblame = -1;
K_StopRoulette(&source->itemRoulette);
}
source->eggmanTransferDelay = 25;
victim->eggmanTransferDelay = 15;
@ -15517,7 +15728,7 @@ boolean K_PlayerCanUseItem(player_t *player)
return (player->mo->health > 0 && !player->spectator && !P_PlayerInPain(player) && !mapreset && leveltime > introtime);
}
fixed_t K_GetGradingMultAdjustment(player_t *player)
fixed_t K_GetGradingFactorAdjustment(player_t *player)
{
fixed_t power = 3*FRACUNIT/100; // adjust to change overall xp volatility
fixed_t stablerate = 3*FRACUNIT/10; // how low is your placement before losing XP? 4*FRACUNIT/10 = top 40% of race will gain
@ -15571,13 +15782,57 @@ fixed_t K_GetGradingMultAdjustment(player_t *player)
return result;
}
fixed_t K_GetGradingFactorMinMax(UINT32 gradingpointnum, boolean max)
{
// Create a dummy player structure for the theoretical last-place player
player_t dummy_player;
memset(&dummy_player, 0, sizeof(player_t));
dummy_player.gradingfactor = FRACUNIT; // Start at 1.0
if (G_GametypeHasTeams())
{
const UINT8 orange_count = G_CountTeam(TEAM_ORANGE);
const UINT8 blue_count = G_CountTeam(TEAM_BLUE);
if (orange_count <= blue_count)
{
dummy_player.team = TEAM_ORANGE;
}
else
{
dummy_player.team = TEAM_BLUE;
}
dummy_player.position = max ? 0 : D_NumPlayersInRace() + 1; // Ensures that all enemy players are counted, and our dummy won't overlap
}
else
{
dummy_player.position = max ? 1 : D_NumPlayersInRace();
}
// Apply the adjustment for each grading point
for (UINT32 i = 0; i < gradingpointnum; i++)
{
dummy_player.gradingfactor += K_GetGradingFactorAdjustment(&dummy_player);
}
return dummy_player.gradingfactor;
}
UINT16 K_GetEXP(player_t *player)
{
UINT32 numgradingpoints = K_GetNumGradingPoints();
// target is where you should be if you're doing good and at a 1.0 mult
fixed_t clampedmult = max(FRACUNIT/2, min(FRACUNIT*5/4, player->gradingfactor)); // clamp between 0.5 and 1.25
fixed_t targetexp = (TARGETEXP*player->gradingpointnum/max(1,numgradingpoints))<<FRACBITS;
UINT16 exp = FixedMul(clampedmult, targetexp)>>FRACBITS;
UINT16 targetminexp = (MINEXP*player->gradingpointnum/max(1,numgradingpoints)); // about what a last place player should be at this stage of the race
UINT16 targetexp = (MAXEXP*player->gradingpointnum/max(1,numgradingpoints)); // about what a 1.0 factor should be at this stage of the race
fixed_t factormin = K_GetGradingFactorMinMax(player->gradingpointnum, false);
fixed_t factormax = K_GetGradingFactorMinMax(player->gradingpointnum, true);
fixed_t clampedfactor = max(factormin, min(factormax, player->gradingfactor));
fixed_t range = factormax - factormin;
fixed_t normalizedfactor = FixedDiv(clampedfactor - factormin, range);
fixed_t easedexp = Easing_Linear(normalizedfactor, targetminexp, targetexp);
// fixed_t easedexp = Easing_Linear(normalizedfactor, MINEXP*FRACUNIT, MAXEXP*FRACUNIT);
UINT16 exp = easedexp;
// CONS_Printf("Player %s numgradingpoints=%d targetminexp=%d targetexp=%d factormin=%.2f factormax=%.2f clampedfactor=%.2f normalizedfactor=%.2f easedexp=%d\n",
// player_names[player - players], numgradingpoints, targetminexp, targetexp, FIXED_TO_FLOAT(factormin), FIXED_TO_FLOAT(factormax),
// FIXED_TO_FLOAT(clampedfactor), FIXED_TO_FLOAT(normalizedfactor), easedexp);
// UINT16 exp = (player->gradingfactor*100)>>FRACBITS;
return exp;
}
@ -15598,6 +15853,29 @@ void K_BotHitPenalty(player_t *player)
}
}
boolean K_IsPickMeUpItem(mobjtype_t type)
{
switch (type)
{
case MT_JAWZ:
case MT_JAWZ_SHIELD:
case MT_ORBINAUT:
case MT_ORBINAUT_SHIELD:
case MT_DROPTARGET:
case MT_DROPTARGET_SHIELD:
case MT_LANDMINE:
case MT_BANANA:
case MT_BANANA_SHIELD:
case MT_GACHABOM:
case MT_EGGMANITEM:
case MT_EGGMANITEM_SHIELD:
case MT_BUBBLESHIELDTRAP:
return true;
default:
return false;
}
}
static boolean K_PickUp(player_t *player, mobj_t *picked)
{
SINT8 type = -1;
@ -15688,7 +15966,7 @@ static boolean K_PickUp(player_t *player, mobj_t *picked)
}
// ACHTUNG this destroys items when returning true, make sure to bail out
boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2)
boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2, boolean allowHostile)
{
if (!m1 || P_MobjWasRemoved(m1))
return false;
@ -15723,7 +16001,7 @@ boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2)
if (inflictor->target->player && G_SameTeam(inflictor->target->player, victim->player))
allied = true;
if (!allied)
if (!allied && !allowHostile)
return false;
// CONS_Printf("target check passed\n");

View file

@ -64,6 +64,9 @@ Make sure this matches the actual number of states
#define EARLY_ITEM_FLICKER (NUMTRANSMAPS)
#define TRIPWIRE_OK_SOUND (sfx_s3k40)
#define TRIPWIRE_NG_SOUND (sfx_gshaf)
// 2023-08-26 +ang20 to Sal's OG values to make them friendlier - Tyron
#define STUMBLE_STEEP_VAL (ANG60 + ANG20)
#define STUMBLE_STEEP_VAL_AIR (ANG30 + ANG10 + ANG20)
@ -80,6 +83,9 @@ Make sure this matches the actual number of states
#define RINGVOLUMEREGEN 1
#define RINGTRANSPARENCYREGEN 3
#define DUELOVERTIME (cv_dueltimelimit.value)
#define DUELWINNINGSCORE (cv_duelscorelimit.value)
#define MIN_WAVEDASH_CHARGE ((11*TICRATE/16)*9)
#define MAXTOPACCEL (12*FRACUNIT)
@ -105,6 +111,8 @@ angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t
boolean K_IsDuelItem(mobjtype_t type);
boolean K_DuelItemAlwaysSpawns(mapthing_t *mt);
boolean K_InRaceDuel(void);
player_t *K_DuelOpponent(player_t *player);
void K_TimerReset(void);
void K_TimerInit(void);
@ -310,8 +318,8 @@ boolean K_ThunderDome(void);
boolean K_PlayerCanUseItem(player_t *player);
fixed_t K_GetGradingMultAdjustment(player_t *player);
fixed_t K_GetGradingFactorAdjustment(player_t *player);
fixed_t K_GetGradingFactorMinMax(UINT32 gradingpointnum, boolean max);
UINT16 K_GetEXP(player_t *player);
UINT32 K_GetNumGradingPoints(void);
@ -320,7 +328,9 @@ boolean K_LegacyRingboost(player_t *player);
void K_BotHitPenalty(player_t *player);
boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2);
boolean K_IsPickMeUpItem(mobjtype_t type);
boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2, boolean allowHostile);
fixed_t K_TeamComebackMultiplier(player_t *player);

View file

@ -593,10 +593,10 @@ extern menu_t PAUSE_PlaybackMenuDef;
typedef enum
{
playback_hide,
playback_restart,
playback_rewind,
playback_pause,
playback_fastforward,
playback_backframe,
playback_resume,
playback_advanceframe,
playback_viewcount,
@ -923,6 +923,7 @@ typedef struct levellist_s {
UINT8 guessgt;
levelsearch_t levelsearch;
boolean netgame; // Start the game in an actual server
boolean canqueue;
menu_t *backMenu;
} levellist_t;
@ -944,6 +945,8 @@ void M_CupSelectTick(void);
void M_LevelSelectHandler(INT32 choice);
void M_LevelSelectTick(void);
INT16 M_LevelFromScrolledList(INT16 add);
void M_MenuToLevelPreamble(UINT8 ssplayers, boolean nowipe);
void M_LevelSelected(INT16 add, boolean menuupdate);
boolean M_LevelSelectCupSwitch(boolean next, boolean skipones);

View file

@ -755,33 +755,40 @@ static void M_DrawMenuTyping(void)
}
// Largely replaced by boxed drawing mode in K_DrawGameControl and rich text
/*
static void M_DrawMediocreKeyboardKey(const char *text, INT32 *workx, INT32 worky, boolean push, boolean rightaligned)
static void M_DrawPauseRoundQueue(INT16 offset, boolean canqueue)
{
INT32 buttonwidth = V_StringWidth(text, 0) + 2;
y_data_t standings;
memset(&standings, 0, sizeof (standings));
if (rightaligned)
if (gamestate == GS_MENU)
{
(*workx) -= buttonwidth;
}
if (push)
{
worky += 2;
standings.mainplayer = MAXPLAYERS;
}
else
{
V_DrawFill((*workx)-1, worky+10, buttonwidth, 2, 24);
standings.mainplayer = (demo.playback ? displayplayers[0] : consoleplayer);
}
V_DrawFill((*workx)-1, worky, buttonwidth, 10, 16);
V_DrawString(
(*workx), worky + 1,
0, text
);
// See also G_GetNextMap, Y_CalculateMatchData
if (
canqueue == false
&& grandprixinfo.gp == true
&& netgame == false // TODO netgame Special Mode support
&& grandprixinfo.gamespeed >= KARTSPEED_NORMAL
&& roundqueue.size > 1
&& roundqueue.entries[roundqueue.size - 1].rankrestricted == true
&& (
gamedata->everseenspecial == true
|| roundqueue.position == roundqueue.size
)
)
{
// Additional cases in which it should always be shown.
standings.showrank = true;
}
Y_RoundQueueDrawer(&standings, offset, false, false, canqueue);
}
*/
// Draw the message popup submenu
void M_DrawMenuMessage(void)
@ -991,6 +998,16 @@ void M_Drawer(void)
// Draw message overlay when needed
M_DrawMenuMessage();
if (
(
currentMenu == &PLAY_LevelSelectDef
|| currentMenu == &PLAY_CupSelectDef
) && levellist.canqueue
)
{
M_DrawPauseRoundQueue(0, true);
}
}
if (menuwipe)
@ -6288,30 +6305,11 @@ void M_DrawPause(void)
V_DrawCenteredLSTitleLowString(220 + offset*2, 103, mainflags, word2);
}
const boolean rulescheck = (K_CanChangeRules(false) && (server || IsPlayerAdmin(consoleplayer)));
boolean drawqueue = (rulescheck && (menuqueue.size > 0));
if (gamestate != GS_INTERMISSION && roundqueue.size > 0)
{
y_data_t standings;
memset(&standings, 0, sizeof (standings));
standings.mainplayer = (demo.playback ? displayplayers[0] : consoleplayer);
// See also G_GetNextMap, Y_CalculateMatchData
if (
grandprixinfo.gp == true
&& netgame == false // TODO netgame Special Mode support
&& grandprixinfo.gamespeed >= KARTSPEED_NORMAL
&& roundqueue.size > 1
&& roundqueue.entries[roundqueue.size - 1].rankrestricted == true
&& (
gamedata->everseenspecial == true
|| roundqueue.position == roundqueue.size
)
)
{
// Additional cases in which it should always be shown.
standings.showrank = true;
}
if (roundqueue.position > 0 && roundqueue.position <= roundqueue.size)
{
patch_t *smallroundpatch = ST_getRoundPicture(true);
@ -6328,7 +6326,7 @@ void M_DrawPause(void)
V_DrawCenteredMenuString(24, 167 + offset/2, V_YELLOWMAP, M_GetGameplayMode());
Y_RoundQueueDrawer(&standings, offset/2, false, false);
drawqueue = true;
}
else if (gametype == GT_TUTORIAL)
{
@ -6347,6 +6345,11 @@ void M_DrawPause(void)
{
V_DrawMenuString(4, 188 + offset/2, V_YELLOWMAP, M_GetGameplayMode());
}
if (drawqueue)
{
M_DrawPauseRoundQueue(offset/2, rulescheck);
}
}
void M_DrawKickHandler(void)
@ -6485,7 +6488,7 @@ void M_DrawPlaybackMenu(void)
else if (currentMenu->menuitems[i].patch && W_CheckNumForName(currentMenu->menuitems[i].patch) != LUMPERROR)
icon = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE);
if ((i == playback_fastforward && cv_playbackspeed.value > 1) || (i == playback_rewind && demo.rewinding))
if ((i == playback_fastforward && cv_playbackspeed.value > 1))
V_DrawMappedPatch(currentMenu->x + currentMenu->menuitems[i].mvar1, currentMenu->y, V_SNAPTOTOP, icon, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_JAWZ, GTC_MENUCACHE));
else
V_DrawMappedPatch(currentMenu->x + currentMenu->menuitems[i].mvar1, currentMenu->y, V_SNAPTOTOP, icon, (i == itemOn) ? activemap : inactivemap);

View file

@ -466,7 +466,7 @@ boolean M_Responder(event_t *ev)
if (Playing() && !demo.playback)
{
// Quick Retry (Y in modeattacking)
if (modeattacking && G_PlayerInputDown(0, gc_y, splitscreen + 1) == true)
if (modeattacking && G_PlayerInputDown(0, gc_respawn, splitscreen + 1) == true)
{
M_TryAgain(0);
return true;

View file

@ -411,7 +411,6 @@ void Obj_SSGobletMobjThink(mobj_t* mo);
void Obj_SSLampMapThingSpawn(mobj_t* mo, mapthing_t* mt);
void Obj_SSWindowMapThingSpawn(mobj_t* mo, mapthing_t* mt);
void Obj_SLSTMaceMobjThink(mobj_t* mo);
void Obj_SSBumperTouchSpecial(mobj_t* special, mobj_t* toucher);
void Obj_SSBumperMobjSpawn(mobj_t* mo);
void Obj_SSChainMobjThink(mobj_t* mo);
void Obj_SSGachaTargetMobjSpawn(mobj_t* mo);

View file

@ -19,6 +19,7 @@
#include "k_grandprix.h"
#include "k_profiles.h"
#include "k_serverstats.h"
#include "k_kart.h" // K_InRaceDuel
// Client-sided calculations done for Power Levels.
// This is done so that clients will never be able to hack someone else's score over the server.
@ -213,6 +214,10 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit)
CONS_Debug(DBG_PWRLV, "========\n");
yourPower = clientpowerlevels[playerNum][powerType];
if (K_InRaceDuel())
yourPower += clientPowerAdd[playerNum];
if (yourPower == 0)
{
// Guests don't record power level changes.
@ -225,6 +230,8 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit)
CONS_Debug(DBG_PWRLV, "%s's gametype score: %d\n", player_names[playerNum], yourScore);
CONS_Debug(DBG_PWRLV, "========\n");
boolean dueling = K_InRaceDuel();
for (i = 0; i < MAXPLAYERS; i++)
{
UINT16 theirScore = 0;
@ -254,6 +261,9 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit)
CONS_Debug(DBG_PWRLV, "%s VS %s:\n", player_names[playerNum], player_names[i]);
theirPower = clientpowerlevels[i][powerType];
if (K_InRaceDuel())
theirPower += clientPowerAdd[i];
if (theirPower == 0)
{
// No power level (splitscreen guests, bots)
@ -295,11 +305,13 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit)
}
}
if (exitBonus == false)
if (dueling)
{
INT16 prevInc = inc;
inc /= max(numlaps-1, 1);
// INT32 winnerscore = (yourScore > theirScore) ? player->duelscore : players[i].duelscore;
INT32 multiplier = 2;
inc *= multiplier;
if (inc == 0)
{
@ -313,7 +325,32 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit)
}
}
CONS_Debug(DBG_PWRLV, "Reduced (%d / %d = %d) because it's not the end of the race\n", prevInc, numlaps, inc);
// CONS_Printf("%s PWR UPDATE: %d\n", player_names[player - players], inc);
CONS_Debug(DBG_PWRLV, "DUELING: Boosted (%d * %d = %d)\n", prevInc, multiplier, inc);
}
else
{
if (exitBonus == false)
{
INT16 prevInc = inc;
inc /= max(numlaps-1, 1);
if (inc == 0)
{
if (prevInc > 0)
{
inc = 1;
}
else if (prevInc < 0)
{
inc = -1;
}
}
CONS_Debug(DBG_PWRLV, "Reduced (%d / %d = %d) because it's not the end of the race\n", prevInc, numlaps, inc);
}
}
CONS_Debug(DBG_PWRLV, "========\n");
@ -346,6 +383,10 @@ void K_UpdatePowerLevelsFinalize(player_t *player, boolean onForfeit)
INT16 lapsLeft = 0;
UINT8 i;
// No remaining laps in Duel.
if (K_InRaceDuel())
return;
lapsLeft = (numlaps - player->latestlap) + 1;
if (lapsLeft <= 0)
@ -394,7 +435,7 @@ INT16 K_FinalPowerIncrement(player_t *player, INT16 yourPower, INT16 baseInc)
if (inc <= 0)
{
if (player->position == 1 && numPlayers > 1)
if (player->position == 1 && numPlayers > 1 && !(K_InRaceDuel()))
{
// Won the whole match?
// Get at least one point.

View file

@ -46,6 +46,9 @@ boolean level_tally_t::UseBonuses(void)
return false;
}
if (K_InRaceDuel())
return false;
// No bonuses / ranking in FREE PLAY or Time Attack
return (grandprixinfo.gp == true || K_TimeAttackRules() == false);
}
@ -218,7 +221,7 @@ INT32 level_tally_t::CalculateGrade(void)
}
}
const INT32 positionWeight = (position > 0 && numPlayers > 2) ? 50 : 0;
const INT32 positionWeight = (position > 0 && numPlayers > 2) ? 20 : 0;
const INT32 total = positionWeight + bonusWeights[0] + bonusWeights[1];
INT32 ours = 0;
@ -242,10 +245,7 @@ INT32 level_tally_t::CalculateGrade(void)
}
case TALLY_BONUS_EXP:
{
// Use a special curve for this.
// Low Exp amounts are guaranteed, higher than half is where skill expression starts
// Magic numbers here are to reduce the range from 50-125 to 0-75 and compare with a max of 58, 85% of which is 49.3, which should put an even 100 or higher exp at A rank
const fixed_t frac = std::min(FRACUNIT, ((exp-50) * FRACUNIT) / std::max(1, static_cast<int>(totalExp-42)));
const fixed_t frac = std::min(FRACUNIT, ((exp-15) * FRACUNIT) / std::max(1, static_cast<int>(totalExp)));
ours += Easing_Linear(frac, 0, bonusWeights[i]);
break;
}

View file

@ -109,6 +109,7 @@
// Give time for the animations to finish before finalizing the vote stages.
#define SELECT_DELAY_TIME (TICRATE*4)
#define PICK_DELAY_TIME (TICRATE/2)
#define STRIKE_DELAY_TIME (TICRATE/3)
#define MAP_ANGER_MAX (2)
@ -179,12 +180,21 @@ typedef struct
{
INT32 timer;
INT32 tic, endtic;
INT32 selectFinalize, pickFinalize;
INT32 selectFinalize, pickFinalize, strikeFinalize;
boolean notYetPicked;
boolean loaded;
SINT8 deferredLevel;
y_vote_player players[MAXSPLITSCREENPLAYERS];
y_vote_roulette roulette;
// If both of these players are valid,
// and they're the only players in the server,
// then we want stage striking!
player_t *strike_loser;
player_t *strike_winner;
boolean strike_turn;
boolean strike_time_out;
boolean stage_striking;
} y_vote_data;
// Voting level drawing
@ -209,6 +219,8 @@ typedef struct
patch_t *ruby_icon;
fixed_t ruby_height;
patch_t *strike_icon;
patch_t *bg_planet[PLANET_FRAMES];
patch_t *bg_checker;
patch_t *bg_levelText;
@ -230,13 +242,54 @@ typedef struct
static y_vote_data vote = {0};
static y_vote_draw vote_draw = {0};
static void Y_SetVoteTimer(void)
{
vote.timer = cv_votetime.value * TICRATE;
if (vote.stage_striking == true)
{
vote.timer /= 2;
}
}
static UINT8 Y_CountStriked(void)
{
INT32 i;
UINT8 num_striked = 0;
for (i = 0; i < VOTE_NUM_LEVELS; i++)
{
if (g_votes_striked[i] == true)
{
num_striked++;
}
}
return num_striked;
}
static boolean Y_VoteIDIsSpecial(const UINT8 playerId)
{
switch (playerId)
{
case VOTE_SPECIAL:
case VOTE_TIMEOUT_LOSER:
case VOTE_TIMEOUT_WINNER:
{
// Special vote spot, always allow
return true;
}
default:
{
return false;
}
}
}
boolean Y_PlayerIDCanVote(const UINT8 playerId)
{
player_t *player = NULL;
if (playerId == VOTE_SPECIAL)
if (Y_VoteIDIsSpecial(playerId) == true)
{
// Special vote spot, always allow
return true;
}
@ -245,7 +298,7 @@ boolean Y_PlayerIDCanVote(const UINT8 playerId)
return false;
}
player = &players[playerId];
const player_t *player = &players[playerId];
if (player->spectator == true)
{
return false;
@ -260,8 +313,48 @@ boolean Y_PlayerIDCanVote(const UINT8 playerId)
return true;
}
static boolean Y_IsPlayersTurn(const UINT8 playerId)
{
if (Y_VoteIDIsSpecial(playerId) == true)
{
return true;
}
if (vote.stage_striking == false)
{
// Not stage striking -- we can always vote.
return true;
}
// Is it our turn to strike a stage?
const player_t *player = &players[playerId];
if (vote.strike_turn == true)
{
return (player == vote.strike_winner);
}
else
{
return (player == vote.strike_loser);
}
}
static boolean Y_PlayerIDCanVoteRightNow(const UINT8 playerId)
{
if (Y_IsPlayersTurn(playerId) == false)
{
return false;
}
return Y_PlayerIDCanVote(playerId);
}
static boolean Y_PlayerCanSelect(const UINT8 localId)
{
if (localId > splitscreen)
{
return false;
}
const UINT8 p = g_localplayers[localId];
if (g_pickedVote != VOTE_NOT_PICKED)
@ -280,7 +373,7 @@ static boolean Y_PlayerCanSelect(const UINT8 localId)
return false;
}
return Y_PlayerIDCanVote(p);
return Y_PlayerIDCanVoteRightNow(p);
}
static void Y_SortPile(void)
@ -381,21 +474,73 @@ static void Y_SortPile(void)
}
}
void Y_SetPlayersVote(const UINT8 playerId, SINT8 newVote)
void Y_SetPlayersVote(const UINT8 inputPlayerId, SINT8 newVote)
{
y_vote_pile *const pile = &vote.roulette.pile[playerId];
y_vote_catcher *const catcher = &pile->catcher;
INT32 i;
if (gamestate != GS_VOTING)
{
return;
}
UINT8 playerId = inputPlayerId;
// Manually overwrite these players for timed out votes.
// Loser/winner is encoded in the vote ID to prevent race
// race conditions with real votes causing problems.
if (inputPlayerId == VOTE_TIMEOUT_LOSER)
{
playerId = (vote.strike_loser - players);
}
else if (inputPlayerId == VOTE_TIMEOUT_WINNER)
{
playerId = (vote.strike_winner - players);
}
if (newVote < 0 || newVote >= VOTE_NUM_LEVELS)
{
newVote = VOTE_NOT_PICKED;
}
if (playerId < MAXPLAYERS)
{
if (Y_PlayerIDCanVoteRightNow(playerId) == false)
{
// Not your turn, dude!
return;
}
if (vote.stage_striking == true)
{
if (newVote != VOTE_NOT_PICKED
&& g_votes_striked[newVote] == false
&& Y_CountStriked() < VOTE_NUM_LEVELS-1)
{
// Strike a stage, instead of voting.
g_votes_striked[newVote] = true;
// Change turn.
vote.strike_turn = !vote.strike_turn;
// Reset variables.
Y_SetVoteTimer();
for (i = 0; i <= splitscreen; i++)
{
vote.players[i].sentTimeOutVote = false;
vote.players[i].delay = NEWTICRATE/7;
}
vote.strike_time_out = false;
// TODO: striking animation
}
return;
}
}
y_vote_pile *const pile = &vote.roulette.pile[playerId];
y_vote_catcher *const catcher = &pile->catcher;
g_votes[playerId] = newVote;
Y_SortPile();
@ -416,12 +561,12 @@ void Y_SetPlayersVote(const UINT8 playerId, SINT8 newVote)
if (vote.timer == -1)
{
// Someone has voted, so start the timer now.
vote.timer = cv_votetime.value * TICRATE;
Y_SetVoteTimer();
}
#endif
}
static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t width, INT32 flags, SINT8 v, boolean dim, SINT8 playerID)
static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t width, INT32 flags, SINT8 v, boolean dim, SINT8 playerID, boolean from_selection)
{
const boolean encore = vote_draw.levels[v].encore;
const fixed_t height = (width * BASEVIDHEIGHT) / BASEVIDWIDTH;
@ -468,29 +613,64 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt
V_AdjustXYWithSnap(&fx, &fy, flags, dupx, dupy);
boolean striked = false;
if (from_selection == true)
{
striked = g_votes_striked[v];
}
V_DrawFill(
fx - dupx, fy - dupy,
fw + (dupx << 1), fh + (dupy << 1),
0|flags|V_NOSCALESTART
);
K_DrawMapThumbnail(
x, y,
width, flags | ((encore == true) ? V_FLIP : 0),
g_voteLevels[v][0],
(dim == true ? R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE) : NULL)
);
if (encore == true)
if (striked == true)
{
const fixed_t rubyScale = width / 72;
const fixed_t strikeScale = width / 32;
V_DrawFixedPatch(
center_x, center_y - FixedMul(vote_draw.ruby_height << 1, rubyScale),
rubyScale, flags,
vote_draw.ruby_icon,
center_x - (strikeScale * 25 / 2), center_y - (strikeScale * 22 / 2),
strikeScale, flags,
vote_draw.strike_icon,
NULL
);
}
else
{
K_DrawMapThumbnail(
x, y,
width, flags | ((encore == true) ? V_FLIP : 0),
g_voteLevels[v][0],
(dim == true ? R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE) : NULL)
);
if (encore == true)
{
const fixed_t rubyScale = width / 72;
V_DrawFixedPatch(
center_x, center_y - FixedMul(vote_draw.ruby_height << 1, rubyScale),
rubyScale, flags,
vote_draw.ruby_icon,
NULL
);
}
if (vote.stage_striking == true
&& from_selection == true
&& dim == false)
{
if (Y_CountStriked() < VOTE_NUM_LEVELS-1)
{
const fixed_t strikeScale = width / 32;
V_DrawFixedPatch(
center_x - (strikeScale * 25 / 2), center_y - (strikeScale * 22 / 2),
strikeScale, flags /*| V_TRANSLUCENT*/,
vote_draw.strike_icon,
NULL
);
}
}
}
if (dim == true)
{
@ -506,7 +686,7 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt
{
const INT32 whiteSq = 16 * dupx;
if (playerID < MAXPLAYERS)
if (playerID < MAXPLAYERS) // Player vote
{
UINT8 *playerMap = R_GetTranslationColormap(players[playerID].skin, players[playerID].skincolor, GTC_CACHE);
patch_t *playerPatch = faceprefix[players[playerID].skin][FACE_RANK];
@ -518,37 +698,15 @@ static void Y_DrawVoteThumbnail(fixed_t center_x, fixed_t center_y, fixed_t widt
playerPatch, playerMap
);
}
else
else if (vote.stage_striking == false) // Angry map
{
const fixed_t iconHeight = (14 << FRACBITS);
const fixed_t iconWidth = (iconHeight * 320) / 200;
V_DrawFill(
K_DrawMapAsFace(
fx + fw - whiteSq + dupx,
fy + fh - whiteSq + dupy,
whiteSq,
whiteSq,
0|flags|V_NOSCALESTART
);
V_SetClipRect(
fx + fw - whiteSq + (2 * dupx),
fy + fh - whiteSq + (2 * dupy),
whiteSq - (2 * dupx),
whiteSq - (2 * dupy),
flags|V_NOSCALESTART
);
K_DrawMapThumbnail(
((fx + fw - whiteSq + (2 * dupx)) * FRACUNIT) - (iconWidth - iconHeight),
(fy + fh - whiteSq + (2 * dupy)) * FRACUNIT,
iconWidth,
flags | V_NOSCALESTART | ((encore == true) ? V_FLIP : 0),
g_voteLevels[v][0],
NULL
);
V_ClearClipRect();
}
}
}
@ -618,7 +776,7 @@ static void Y_DrawCatcher(y_vote_catcher *catcher)
baseX, catcher->y,
((catcher->small == true) ? PILE_WIDTH : SELECTION_WIDTH), 0,
catcher->level, false,
catcher->player
catcher->player, false
);
}
@ -813,7 +971,7 @@ static void Y_DrawVoteSelection(fixed_t offset)
continue;
}
if (g_votes[p] != VOTE_NOT_PICKED || Y_PlayerIDCanVote(p) == false)
if (g_votes[p] != VOTE_NOT_PICKED || Y_PlayerIDCanVoteRightNow(p) == false)
{
continue;
}
@ -881,12 +1039,41 @@ static void Y_DrawVoteSelection(fixed_t offset)
x, y - vote_draw.levels[i].hop,
SELECTION_WIDTH, 0,
i, (selected == false),
-1
-1, true
);
x += SELECTION_SPACING_W;
}
if (vote.stage_striking == true && Y_CountStriked() < VOTE_NUM_LEVELS-1)
{
UINT8 current_strike_player = (
(vote.strike_turn == true)
? (vote.strike_winner - players)
: (vote.strike_loser - players)
);
for (i = 0; i <= splitscreen; i++)
{
if (g_localplayers[i] == current_strike_player)
{
break;
}
}
if (i > splitscreen)
{
const char *wait_str = va("Waiting for %s...", player_names[current_strike_player]);
V_DrawThinString(
BASEVIDWIDTH / 2 - (V_ThinStringWidth(wait_str, 0) / 2),
180,
0,
wait_str
);
}
}
//
// Draw our catchers
//
@ -944,7 +1131,7 @@ static void Y_DrawVotePile(void)
PILE_WIDTH, 0,
g_votes[i],
(i != vote.roulette.anim || g_pickedVote == VOTE_NOT_PICKED),
i
i, false
);
}
@ -1060,8 +1247,30 @@ static void Y_VoteStops(SINT8 pick, SINT8 level)
}
}
static void Y_PlayerSendStrike(const UINT8 localPlayer)
{
y_vote_player *const player = &vote.players[localPlayer];
y_vote_catcher *const catcher = &player->catcher;
if (g_votes_striked[player->selection] == true)
{
// TODO: "Can't select" animation
return;
}
D_ModifyClientVote(g_localplayers[localPlayer], player->selection);
catcher->action = CATCHER_NA;
catcher->delay = 5;
}
static void Y_PlayerSendVote(const UINT8 localPlayer)
{
if (vote.stage_striking == true)
{
Y_PlayerSendStrike(localPlayer);
return;
}
y_vote_player *const player = &vote.players[localPlayer];
y_vote_catcher *const catcher = &player->catcher;
@ -1188,7 +1397,11 @@ static void Y_TickPlayerCatcher(const UINT8 localPlayer)
{
if (catcher->x == catcher->destX && catcher->y == catcher->destY)
{
D_ModifyClientVote(g_localplayers[localPlayer], vote.players[localPlayer].selection);
if (vote.stage_striking == false)
{
D_ModifyClientVote(g_localplayers[localPlayer], vote.players[localPlayer].selection);
}
catcher->action = CATCHER_NA;
catcher->delay = 5;
S_StopSoundByNum(sfx_kc37);
@ -1434,6 +1647,201 @@ static SINT8 Y_TryMapAngerVote(void)
return angryMaps[pick];
}
static void Y_ExitStageStrike(void)
{
INT32 i;
vote.stage_striking = false;
for (i = 0; i < VOTE_NUM_LEVELS; i++)
{
g_votes_striked[i] = false;
}
vote.strike_loser = NULL;
vote.strike_winner = NULL;
vote.strike_turn = false;
vote.strike_time_out = false;
Y_SetVoteTimer();
}
static boolean Y_CheckStageStrikeStatus(void)
{
INT32 i;
UINT8 num_voters = 0;
for (i = 0; i < MAXPLAYERS; i++)
{
if (Y_PlayerIDCanVote(i) == false)
{
continue;
}
num_voters++;
if (num_voters > 2)
{
break;
}
}
if (num_voters != 2)
{
// Someone joined or left. Stage striking is broken!
return false;
}
if (vote.strike_loser == NULL || Y_PlayerIDCanVote(vote.strike_loser - players) == false)
{
// Loser is invalidated!
return false;
}
if (vote.strike_winner == NULL || Y_PlayerIDCanVote(vote.strike_winner - players) == false)
{
// Winner is invalidated!
return false;
}
// Looks good, we can tick stage striking.
return true;
}
static void Y_TickVoteStageStrike(void)
{
INT32 i;
if (Y_CheckStageStrikeStatus() == false)
{
Y_ExitStageStrike();
return;
}
SINT8 the_only_level = VOTE_NOT_PICKED;
for (i = 0; i < VOTE_NUM_LEVELS; i++)
{
if (g_votes_striked[i] == true)
{
continue;
}
if (the_only_level != VOTE_NOT_PICKED)
{
// More than 1 valid level.
// Unset and stop iterating.
the_only_level = VOTE_NOT_PICKED;
break;
}
the_only_level = i;
}
if (the_only_level != VOTE_NOT_PICKED)
{
vote.timer = 0;
vote.strikeFinalize = STRIKE_DELAY_TIME;
if (vote.selectFinalize < SELECT_DELAY_TIME)
{
if (vote.selectFinalize == 0)
{
for (i = 0; i <= splitscreen; i++)
{
UINT8 p = g_localplayers[i];
if (p != (vote.strike_loser - players)
&& p != (vote.strike_winner - players))
{
continue;
}
y_vote_player *const player = &vote.players[i];
y_vote_catcher *const catcher = &player->catcher;
player->selection = the_only_level;
catcher->action = CATCHER_FG_LOWER;
catcher->x = catcher->destX = SELECTION_X + (SELECTION_SPACING_W * player->selection);
catcher->y = CATCHER_OFFSCREEN;
catcher->destY = SELECTION_Y - SELECTION_HOP;
catcher->spr = 0;
catcher->level = VOTE_NOT_PICKED;
S_StartSound(NULL, sfx_kc37);
}
}
vote.selectFinalize++;
}
if (vote.selectFinalize >= SELECT_DELAY_TIME)
{
if (vote.pickFinalize < PICK_DELAY_TIME)
{
vote.pickFinalize++;
}
else if (vote.endtic == -1)
{
vote.notYetPicked = false; /* don't pick vote twice */
if (server)
{
D_PickVote( the_only_level );
}
}
}
}
else
{
if (vote.timer == 0)
{
if (vote.strikeFinalize < STRIKE_DELAY_TIME)
{
vote.strikeFinalize++;
}
}
else
{
vote.strikeFinalize = 0;
}
if (vote.strikeFinalize >= STRIKE_DELAY_TIME)
{
// We didn't get their timeout strike net command.
// Maybe they hacked their exe, or connection was
// interrupted, or some other issue.
// Let's just strike a random stage for them.
if (server && vote.strike_time_out == false)
{
INT32 rng = M_RandomKey(VOTE_NUM_LEVELS);
for (i = 0; i < VOTE_NUM_LEVELS; i++)
{
if (g_votes_striked[i] == false)
{
break;
}
rng++;
if (rng >= VOTE_NUM_LEVELS)
{
rng = 0;
}
}
D_ModifyClientVote((vote.strike_turn == true) ? VOTE_TIMEOUT_WINNER : VOTE_TIMEOUT_LOSER, rng);
}
vote.strike_time_out = true;
}
else if (vote.timer > 0)
{
vote.timer--;
vote.selectFinalize = 0;
vote.pickFinalize = 0;
}
}
}
static void Y_TickVoteSelection(void)
{
boolean everyone_voted = true;/* the default condition */
@ -1463,6 +1871,22 @@ static void Y_TickVoteSelection(void)
// Time's up, send our vote ASAP.
if (vote.players[i].sentTimeOutVote == false)
{
// Move off of striked stages for the timeout vote.
INT32 j;
for (j = 0; j < VOTE_NUM_LEVELS; j++)
{
if (g_votes_striked[vote.players[i].selection] == false)
{
break;
}
vote.players[i].selection++;
if (vote.players[i].selection >= VOTE_NUM_LEVELS)
{
vote.players[i].selection = 0;
}
}
Y_PlayerSendVote(i);
vote.players[i].sentTimeOutVote = true;
vote.players[i].delay = NEWTICRATE/7;
@ -1514,12 +1938,27 @@ static void Y_TickVoteSelection(void)
continue;
}
if (players[i].bot == true && g_votes[i] == VOTE_NOT_PICKED)
if (server && players[i].bot == true && Y_PlayerIDCanVoteRightNow(i) == true && g_votes[i] == VOTE_NOT_PICKED)
{
if (( M_RandomFixed() % 100 ) == 0)
{
// bots vote randomly
D_ModifyClientVote(i, M_RandomKey(VOTE_NUM_LEVELS));
INT32 rng = M_RandomKey(VOTE_NUM_LEVELS);
for (i = 0; i < VOTE_NUM_LEVELS; i++)
{
if (g_votes_striked[i] == false)
{
break;
}
rng++;
if (rng >= VOTE_NUM_LEVELS)
{
rng = 0;
}
}
D_ModifyClientVote(i, rng);
}
}
@ -1529,6 +1968,13 @@ static void Y_TickVoteSelection(void)
}
}
if (vote.stage_striking == true)
{
// Use the same selection logic, otherwise use separate ending logic.
Y_TickVoteStageStrike();
return;
}
if (everyone_voted == true)
{
vote.timer = 0;
@ -1649,6 +2095,7 @@ static void Y_InitVoteDrawing(void)
INT32 i = 0, j = 0;
vote_draw.ruby_icon = W_CachePatchName("RUBYICON", PU_STATIC);
vote_draw.strike_icon = W_CachePatchName("K_NOBLNS", PU_STATIC);
for (i = 0; i < PLANET_FRAMES; i++)
{
@ -1736,6 +2183,103 @@ static void Y_InitVoteDrawing(void)
vote_draw.selectTransition = FRACUNIT;
}
static boolean Y_DetermineStageStrike(void)
{
player_t *a = NULL;
player_t *b = NULL;
UINT8 num_voters = 0;
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (Y_PlayerIDCanVote(i) == false)
{
continue;
}
num_voters++;
// Just set the pointers for now, sort them later.
if (a == NULL)
{
a = &players[i];
}
else if (b == NULL)
{
b = &players[i];
}
else
{
// Too many players
return false;
}
}
if (num_voters != 2 || a == NULL || b == NULL)
{
// Requires exactly 2 of them.
return false;
}
UINT32 score_a = 0;
UINT32 score_b = 0;
intertype_t scoring_type = Y_GetIntermissionType();
switch (scoring_type)
{
case int_time:
{
score_a = UINT32_MAX - a->realtime;
score_b = UINT32_MAX - b->realtime;
break;
}
case int_score:
{
score_a = a->score;
score_b = b->realtime;
break;
}
default:
{
// Invalid, exit now.
return false;
}
}
if (a->pflags & PF_NOCONTEST)
{
score_a = 0;
}
if (b->pflags & PF_NOCONTEST)
{
score_b = 0;
}
if (score_a == score_b)
{
// TODO: should be a coin flip, but how
// should the RNG for this be handled?
score_a++;
}
if (score_a > score_b)
{
vote.strike_loser = b;
vote.strike_winner = a;
}
else
{
vote.strike_loser = a;
vote.strike_winner = b;
}
vote.stage_striking = true;
return true;
}
void Y_StartVote(void)
{
INT32 i = 0;
@ -1749,12 +2293,6 @@ void Y_StartVote(void)
vote.tic = vote.endtic = -1;
#ifdef VOTE_TIME_WAIT_FOR_VOTE
vote.timer = -1; // Timer is not set until the first vote is added
#else
vote.timer = cv_votetime.value * TICRATE;
#endif
g_pickedVote = VOTE_NOT_PICKED;
vote.notYetPicked = true;
@ -1782,6 +2320,19 @@ void Y_StartVote(void)
catcher->player = i;
}
for (i = 0; i < VOTE_NUM_LEVELS; i++)
{
g_votes_striked[i] = false;
}
Y_DetermineStageStrike();
#ifdef VOTE_TIME_WAIT_FOR_VOTE
vote.timer = -1; // Timer is not set until the first vote is added
#else
Y_SetVoteTimer();
#endif
Y_InitVoteDrawing();
vote.loaded = true;
@ -1803,6 +2354,7 @@ static void Y_UnloadVoteData(void)
}
UNLOAD(vote_draw.ruby_icon);
UNLOAD(vote_draw.strike_icon);
for (i = 0; i < PLANET_FRAMES; i++)
{
@ -1941,4 +2493,25 @@ void Y_SetupVoteFinish(SINT8 pick, SINT8 level, SINT8 anger)
vote.timer = -1;
vote.selectFinalize = SELECT_DELAY_TIME;
vote.pickFinalize = PICK_DELAY_TIME;
vote.strikeFinalize = STRIKE_DELAY_TIME;
}
//
// Y_VoteContext
//
enum
{
VOTE_CTX_NORMAL = 0,
VOTE_CTX_DUEL,
};
UINT8 Y_VoteContext(void)
{
if (vote.stage_striking == true)
{
return VOTE_CTX_DUEL;
}
return VOTE_CTX_NORMAL;
}

View file

@ -19,9 +19,6 @@
extern "C" {
#endif
#define VOTE_NUM_LEVELS (4)
#define VOTE_NOT_PICKED (-1)
#define VOTE_MOD_ENCORE (0x01)
boolean Y_PlayerIDCanVote(const UINT8 playerId);
@ -32,6 +29,7 @@ void Y_VoteTicker(void);
void Y_StartVote(void);
void Y_EndVote(void);
void Y_SetupVoteFinish(SINT8 pick, SINT8 level, SINT8 anger);
UINT8 Y_VoteContext(void);
#ifdef __cplusplus
} // extern "C"

View file

@ -111,6 +111,7 @@ enum mobj_e {
mobj_reappear,
mobj_punt_ref,
mobj_owner,
mobj_relinkplayer,
};
static const char *const mobj_opt[] = {
@ -201,6 +202,7 @@ static const char *const mobj_opt[] = {
"reappear",
"punt_ref",
"owner",
"relinkplayer",
NULL};
#define UNIMPLEMENTED luaL_error(L, LUA_QL("mobj_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", mobj_opt[field])
@ -519,6 +521,9 @@ static int mobj_get(lua_State *L)
}
LUA_PushUserdata(L, mo->owner, META_MOBJ);
break;
case mobj_relinkplayer:
lua_pushinteger(L, mo->relinkplayer);
break;
default: // extra custom variables in Lua memory
lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
I_Assert(lua_istable(L, -1));
@ -934,6 +939,9 @@ static int mobj_set(lua_State *L)
P_SetTarget(&mo->owner, owner);
}
break;
case mobj_relinkplayer:
mo->relinkplayer = luaL_checkinteger(L, 3);
break;
default:
lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
I_Assert(lua_istable(L, -1));

View file

@ -498,6 +498,14 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->sneakertimer);
else if (fastcmp(field,"numsneakers"))
lua_pushinteger(L, plr->numsneakers);
else if (fastcmp(field,"panelsneakertimer"))
lua_pushinteger(L, plr->panelsneakertimer);
else if (fastcmp(field,"numpanelsneakers"))
lua_pushinteger(L, plr->numpanelsneakers);
else if (fastcmp(field,"weaksneakertimer"))
lua_pushinteger(L, plr->weaksneakertimer);
else if (fastcmp(field,"numweaksneakers"))
lua_pushinteger(L, plr->numweaksneakers);
else if (fastcmp(field,"floorboost"))
lua_pushinteger(L, plr->floorboost);
else if (fastcmp(field,"growshrinktimer"))
@ -709,6 +717,8 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->checkskip);
else if (fastcmp(field,"cheatchecknum"))
lua_pushinteger(L, plr->cheatchecknum);
else if (fastcmp(field,"duelscore"))
lua_pushinteger(L, plr->duelscore);
else if (fastcmp(field,"lastsidehit"))
lua_pushinteger(L, plr->lastsidehit);
else if (fastcmp(field,"lastlinehit"))
@ -1312,6 +1322,8 @@ static int player_set(lua_State *L)
plr->checkskip = (INT32)luaL_checkinteger(L, 3);
else if (fastcmp(field,"cheatchecknum"))
plr->cheatchecknum = (INT32)luaL_checkinteger(L, 3);
else if (fastcmp(field,"duelscore"))
plr->duelscore = (INT16)luaL_checkinteger(L, 3);
else if (fastcmp(field,"lastsidehit"))
plr->lastsidehit = (INT16)luaL_checkinteger(L, 3);
else if (fastcmp(field,"lastlinehit"))

View file

@ -1858,7 +1858,10 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
&& (gamespeed != KARTSPEED_EASY)
&& (player->tally.active == true)
&& (player->tally.totalExp > 0) // Only true if not Time Attack
&& (player->tally.exp >= player->tally.totalExp));
&& (
(player->tally.exp >= player->tally.totalExp)
|| (K_InRaceDuel() && player->duelscore == DUELWINNINGSCORE)
));
case UCRP_FINISHALLPRISONS:
return (battleprisons
&& !(player->pflags & PF_NOCONTEST)

View file

@ -92,6 +92,8 @@ typedef enum
PR_ITEM_SPAWNER = PROLDDEMO, // Battle mode item spawners
PR_TEAMS, // Teamplay shuffling
PR_NUISANCE, // Margin Boost HUD
PRNUMSYNCED,
PR_INTERPHUDRANDOM = PRNUMSYNCED, // Interpolation-accomodating HUD randomisation

View file

@ -1272,6 +1272,7 @@ void M_GonerTutorial(INT32 choice)
// Please also see M_LevelSelectInit as called in extras-1.c
levellist.netgame = false;
levellist.canqueue = false;
levellist.levelsearch.checklocked = true;
levellist.levelsearch.grandprix = false;
levellist.levelsearch.timeattack = false;

View file

@ -52,6 +52,17 @@ menuitem_t OPTIONS_Gameplay[] =
NULL, {.cvar = &cv_kartbumpers}, 0, 0},
{IT_HEADER, "Duel...", NULL,
NULL, {NULL}, 0, 0},
{IT_STRING | IT_CVAR, "Duel Time Limit", "How long it takes for Margin Boost to kick in (seconds).",
NULL, {.cvar = &cv_dueltimelimit}, 0, 0},
{IT_STRING | IT_CVAR, "Duel Score Limit", "How many points a player must be ahead to win a Duel.",
NULL, {.cvar = &cv_duelscorelimit}, 0, 0},
{IT_SPACE | IT_DYBIGSPACE, NULL, NULL,
NULL, {NULL}, 0, 0},

View file

@ -39,7 +39,7 @@ menuitem_t OPTIONS_Server[] =
{IT_STRING | IT_CVAR, "CPU Level", "Bots can fill unused slots. How strong should they be?",
NULL, {.cvar = &cv_kartbot}, 0, 0},
{IT_STRING | IT_CVAR, "Use PWR.LV", "Should players should be rated on their performance?",
{IT_STRING | IT_CVAR, "Use Mobiums", "Should players should be rated on their performance?",
NULL, {.cvar = &cv_kartusepwrlv}, 0, 0},
{IT_STRING | IT_CVAR, "Antigrief Timer (seconds)", "How long can players stop progressing before they're removed?",

View file

@ -72,6 +72,24 @@ static boolean input_routine(INT32)
return false;
}
static void init_routine(void)
{
if (!netgame)
{
S_SoundInputSetEnabled(true);
}
}
static boolean quit_routine(void)
{
if (!netgame)
{
S_SoundInputSetEnabled(false);
}
return true;
}
menu_t OPTIONS_VoiceDef = {
sizeof (OPTIONS_Voice) / sizeof (menuitem_t),
&OPTIONS_MainDef,
@ -85,7 +103,7 @@ menu_t OPTIONS_VoiceDef = {
draw_routine,
M_DrawOptionsCogs,
tick_routine,
NULL,
NULL,
init_routine,
quit_routine,
input_routine,
};

View file

@ -614,34 +614,19 @@ void M_StartTimeAttack(INT32 choice)
{
modeattacking |= ATTACKING_SPB;
}
if (gamestate == GS_MENU)
{
encoremode = true; // guarantees short wipe
}
modeprefix = "spb-";
}
// DON'T SOFTLOCK
CON_ToggleOff();
// Still need to reset devmode
cht_debug = 0;
if (demo.playback)
G_StopDemo();
splitscreen = 0;
SplitScreen_OnChange();
S_StartSound(NULL, sfx_s3k63);
paused = false;
S_StopMusicCredit();
// Early fadeout to let the sound finish playing
F_WipeStartScreen();
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
F_WipeEndScreen();
F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false);
SV_StartSinglePlayerServer(levellist.newgametype, false);
M_MenuToLevelPreamble(0, (gamestate != GS_MENU || cv_dummyspbattack.value == 1));
gpath = va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s",
srb2home, timeattackfolder);
@ -659,8 +644,17 @@ void M_StartTimeAttack(INT32 choice)
restoreMenu = &PLAY_TimeAttackDef;
D_MapChange(
levellist.choosemap+1,
levellist.newgametype,
(cv_dummyspbattack.value == 1),
true,
1,
false,
false
);
M_ClearMenus(true);
D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_dummyspbattack.value == 1), 1, 1, false, false);
G_UpdateTimeStickerMedals(levellist.choosemap, true);
}

View file

@ -172,6 +172,7 @@ void M_MPSetupNetgameMapSelect(INT32 choice)
// Yep, we'll be starting a netgame.
levellist.netgame = true;
levellist.canqueue = true;
// Make sure we reset those
levellist.levelsearch.timeattack = false;
levellist.levelsearch.checklocked = true;

View file

@ -57,26 +57,7 @@ static void M_StartCup(UINT8 entry)
entry = UINT8_MAX;
}
S_StartSound(NULL, sfx_s3k63);
paused = false;
S_StopMusicCredit();
// Early fadeout to let the sound finish playing
F_WipeStartScreen();
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
F_WipeEndScreen();
F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false);
if (cv_maxconnections.value < ssplayers+1)
CV_SetValue(&cv_maxconnections, ssplayers+1);
if (splitscreen != ssplayers)
{
splitscreen = ssplayers;
SplitScreen_OnChange();
}
M_MenuToLevelPreamble(ssplayers, false);
if (entry == UINT8_MAX)
{
@ -135,10 +116,6 @@ static void M_StartCup(UINT8 entry)
}
}
paused = false;
SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame);
M_ClearMenus(true);
restoreMenu = &PLAY_CupSelectDef;

View file

@ -21,6 +21,7 @@
#include "../../v_video.h"
#include "../../g_game.h" // G_GetBackupCupData
#include "../../p_saveg.h" // cupsavedata
#include "../../d_netcmd.h" // Handle_MapQueueSend
cupheader_t dummy_lostandfound;
@ -657,14 +658,22 @@ void M_LevelSelectInit(INT32 choice)
case 0:
levellist.levelsearch.grandprix = false;
levellist.levelsearch.timeattack = false;
levellist.canqueue = true;
CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string);
CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto");
CV_StealthSet(&cv_kartspeed, (cv_dummykartspeed.value == KARTSPEED_NORMAL) ? "Auto Gear" : cv_dummykartspeed.string);
break;
case 1:
levellist.levelsearch.grandprix = false;
levellist.levelsearch.timeattack = true;
levellist.canqueue = false;
break;
case 2:
levellist.levelsearch.grandprix = true;
levellist.levelsearch.timeattack = false;
levellist.canqueue = false;
break;
default:
CONS_Alert(CONS_WARNING, "Bad level select init\n");
@ -676,10 +685,14 @@ void M_LevelSelectInit(INT32 choice)
gt = menugametype;
}
if (levellist.levelsearch.timeattack && gt == GT_RACE && skins[R_SkinAvailableEx(cv_skin[0].string, false)].flags & SF_HIVOLT)
if (levellist.levelsearch.timeattack && gt == GT_RACE)
{
M_StartMessage("A long-forgotten power...", "You are using a \x82prototype engine\x80.\nRecords will not be saved.", NULL, MM_NOTHING, NULL, NULL);
S_StartSound(NULL, sfx_s3k81);
const INT32 skinid = R_SkinAvailableEx(cv_skin[0].string, false);
if (skinid >= 0 && (skins[skinid].flags & SF_HIVOLT))
{
M_StartMessage("A long-forgotten power...", "You are using a \x82prototype engine\x80.\nRecords will not be saved.", NULL, MM_NOTHING, NULL, NULL);
S_StartSound(NULL, sfx_s3k81);
}
}
if (!M_LevelListFromGametype(gt))
@ -689,7 +702,41 @@ void M_LevelSelectInit(INT32 choice)
}
}
void M_LevelSelected(INT16 add, boolean menuupdate)
void M_MenuToLevelPreamble(UINT8 ssplayers, boolean nowipe)
{
cht_debug = 0;
if (demo.playback)
G_StopDemo();
if (cv_maxconnections.value < ssplayers+1)
CV_SetValue(&cv_maxconnections, ssplayers+1);
if (splitscreen != ssplayers)
{
splitscreen = ssplayers;
SplitScreen_OnChange();
}
paused = false;
S_StopMusicCredit();
if (!nowipe)
{
S_StartSound(NULL, sfx_s3k63);
// Early fadeout to let the sound finish playing
F_WipeStartScreen();
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
F_WipeEndScreen();
F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false);
}
SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame);
}
INT16 M_LevelFromScrolledList(INT16 add)
{
UINT8 i = 0;
INT16 map = M_GetFirstLevelInList(&i, &levellist.levelsearch);
@ -706,6 +753,13 @@ void M_LevelSelected(INT16 add, boolean menuupdate)
add--;
}
return map;
}
void M_LevelSelected(INT16 add, boolean menuupdate)
{
INT16 map = M_LevelFromScrolledList(add);
if (map >= nummapheaders)
{
// This shouldn't happen
@ -733,75 +787,116 @@ void M_LevelSelected(INT16 add, boolean menuupdate)
{
if (gamestate == GS_MENU)
{
UINT8 ssplayers = levellist.levelsearch.tutorial ? 0 : cv_splitplayers.value-1;
netgame = false;
multiplayer = true;
strlcpy(connectedservername, cv_servername.string, MAXSERVERNAME);
M_MenuToLevelPreamble(
(levellist.levelsearch.tutorial
? 0
: cv_splitplayers.value-1
),
(
(cv_kartencore.value == 1)
&& (gametypes[levellist.newgametype]->rules & GTR_ENCORE)
)
);
// Still need to reset devmode
cht_debug = 0;
if (demo.playback)
G_StopDemo();
/*if (levellist.choosemap == 0)
levellist.choosemap = G_RandMap(G_TOLFlag(levellist.newgametype), -1, 0, 0, false, NULL);*/
if (cv_maxconnections.value < ssplayers+1)
CV_SetValue(&cv_maxconnections, ssplayers+1);
if (splitscreen != ssplayers)
{
splitscreen = ssplayers;
SplitScreen_OnChange();
}
S_StartSound(NULL, sfx_s3k63);
paused = false;
S_StopMusicCredit();
// Early fadeout to let the sound finish playing
F_WipeStartScreen();
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
F_WipeEndScreen();
F_RunWipe(wipe_level_toblack, wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false);
SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame);
if (!levellist.netgame)
CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string);
CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto");
CV_StealthSet(&cv_kartspeed, (cv_dummykartspeed.value == KARTSPEED_NORMAL) ? "Auto Gear" : cv_dummykartspeed.string);
D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false);
if (levellist.netgame == true)
{
restoreMenu = &PLAY_MP_OptSelectDef;
}
else /*if (!M_GameTrulyStarted() ||
levellist.levelsearch.tutorial)*/
{
restoreMenu = currentMenu;
}
restoreMenu = (levellist.netgame)
? &PLAY_MP_OptSelectDef
: currentMenu;
restorelevellist = levellist;
}
else
{
// directly do the map change
D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false);
}
D_MapChange(
levellist.choosemap+1,
levellist.newgametype,
(cv_kartencore.value == 1),
true,
1,
false,
false
);
M_ClearMenus(true);
}
}
static void M_MenuQueueStopSend(INT32 ch)
{
(void)ch;
memset(&menuqueue, 0, sizeof(struct menuqueue));
}
static void M_MenuQueueSelectedLocal(void)
{
UINT8 i = 0;
for (; i < menuqueue.size; i++)
{
G_MapIntoRoundQueue(
menuqueue.entries[i].mapnum,
menuqueue.entries[i].gametype,
menuqueue.entries[i].encore,
false
);
}
roundqueue.netcommunicate = true;
if (gamestate == GS_MENU)
{
levellist.choosemap = roundqueue.entries[0].mapnum;
multiplayer = true;
M_MenuToLevelPreamble(
cv_splitplayers.value-1,
(
roundqueue.entries[0].encore
&& (gametypes[roundqueue.entries[0].gametype]->rules & GTR_ENCORE)
)
);
restoreMenu = (levellist.netgame)
? &PLAY_MP_OptSelectDef
: currentMenu;
restorelevellist = levellist;
roundqueue.position = roundqueue.roundnum = 1;
D_MapChange(
roundqueue.entries[0].mapnum + 1,
roundqueue.entries[0].gametype,
roundqueue.entries[0].encore,
true,
1,
false,
roundqueue.entries[0].rankrestricted
);
M_MenuQueueStopSend(MA_NONE);
M_ClearMenus(true);
}
else
{
if (gamestate == GS_LEVEL && roundqueue.position == 0)
{
menuqueue.sending = UINT8_MAX;
}
else
{
S_StartSound(NULL, sfx_chchng);
M_MenuQueueStopSend(MA_NONE);
M_ClearMenus(true);
}
}
}
boolean M_LevelSelectCupSwitch(boolean next, boolean skipones)
{
levelsearch_t templevelsearch = levellist.levelsearch;
@ -883,6 +978,44 @@ boolean M_LevelSelectCupSwitch(boolean next, boolean skipones)
}
}
static void M_MenuQueueResponse(INT32 ch)
{
M_ClearMenus(false);
if (ch != MA_YES)
return;
if (!(server || (IsPlayerAdmin(consoleplayer))))
return;
if (!(gamestate == GS_LEVEL && roundqueue.position == 0))
return;
SendNetXCmd(XD_EXITLEVEL, NULL, 0);
}
static void M_ClearQueueResponse(INT32 ch)
{
if (ch != MA_YES)
return;
if (!(server || (IsPlayerAdmin(consoleplayer))))
return;
S_StartSound(NULL, sfx_slip);
if (netgame)
{
if (roundqueue.size)
{
Handle_MapQueueSend(0, ROUNDQUEUE_CMD_CLEAR, false);
}
return;
}
memset(&roundqueue, 0, sizeof(struct roundqueue));
}
void M_LevelSelectHandler(INT32 choice)
{
const UINT8 pid = 0;
@ -894,6 +1027,11 @@ void M_LevelSelectHandler(INT32 choice)
return;
}
if (menuqueue.sending)
{
return;
}
if (menucmd[pid].dpad_ud > 0)
{
levellist.cursor++;
@ -926,10 +1064,96 @@ void M_LevelSelectHandler(INT32 choice)
M_LevelSelectScrollDest();
if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/)
if (M_MenuConfirmPressed(pid))
{
// Starting immediately OR importing queue
M_SetMenuDelay(pid);
M_LevelSelected(levellist.cursor, true);
while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX)
menuqueue.size--;
if (!levellist.canqueue || !menuqueue.size)
{
M_LevelSelected(levellist.cursor, true);
}
else if (netgame)
{
menuqueue.anchor = roundqueue.size;
menuqueue.sending = 1;
M_StartMessage("Queueing Rounds",
va(M_GetText(
"Attempting to send %d Round%s...\n"
"\n"
"If this is taking longer than you\n"
"expect, exit out of this message.\n"
), menuqueue.size, (menuqueue.size == 1 ? "" : "s")
), &M_MenuQueueStopSend, MM_NOTHING,
NULL,
"This is taking too long..."
);
}
else
{
M_MenuQueueSelectedLocal();
}
}
else if (levellist.canqueue && M_MenuButtonPressed(pid, MBT_Z))
{
// Adding to queue
INT16 map = NEXTMAP_INVALID;
M_SetMenuDelay(pid);
if ((roundqueue.size + menuqueue.size) < ROUNDQUEUE_MAX)
{
map = M_LevelFromScrolledList(levellist.cursor);
}
if (map < nummapheaders)
{
memset(menuqueue.entries+menuqueue.size, 0, sizeof(roundentry_t));
menuqueue.entries[menuqueue.size].mapnum = map;
menuqueue.entries[menuqueue.size].gametype = levellist.newgametype;
menuqueue.entries[menuqueue.size].encore = (cv_kartencore.value == 1);
menuqueue.size++;
S_StartSound(NULL, sfx_s3k4a);
}
else
{
S_StartSound(NULL, sfx_s3kb2);
}
}
else if (levellist.canqueue && M_MenuExtraPressed(pid))
{
while ((menuqueue.size + roundqueue.size) > ROUNDQUEUE_MAX)
menuqueue.size--;
if (menuqueue.size)
{
S_StartSound(NULL, sfx_shldls);
menuqueue.size--;
}
else if (roundqueue.size)
{
M_StartMessage("Queue Clearing",
va(M_GetText(
"There %s %d Round%s of play queued.\n"
"\n"
"Do you want to empty the queue?\n"
),
(roundqueue.size == 1 ? "is" : "are"),
roundqueue.size,
(roundqueue.size == 1 ? "" : "s")
), &M_ClearQueueResponse, MM_YESNO,
"Time to start fresh",
"Not right now"
);
}
}
else if (M_MenuBackPressed(pid))
{
@ -944,4 +1168,55 @@ void M_LevelSelectHandler(INT32 choice)
void M_LevelSelectTick(void)
{
if (!menuqueue.sending)
return;
if ((menuqueue.sending <= menuqueue.size) // Sending
&& (roundqueue.size >= menuqueue.anchor)) // Didn't get it wiped
{
if (!netgame)
return;
const UINT8 idcount = (roundqueue.size - menuqueue.anchor);
if (idcount == menuqueue.sending-1)
{
Handle_MapQueueSend(
menuqueue.entries[idcount].mapnum,
menuqueue.entries[idcount].gametype,
menuqueue.entries[idcount].encore
);
}
if (idcount >= menuqueue.sending-1)
{
menuqueue.sending++;
}
return;
}
if (menuqueue.size)
{
S_StartSound(NULL, sfx_chchng);
if (roundqueue.position || gamestate != GS_LEVEL)
{
M_StopMessage(MA_NONE);
}
else
{
M_StartMessage("Round Queue",
va(M_GetText(
"You just queued %d Round%s of play.\n"
"\n"
"Do you want to skip the current\n"
"course and start them immediately?\n"
), menuqueue.size, (menuqueue.size == 1 ? "" : "s")
), &M_MenuQueueResponse, MM_YESNO,
"Let's get going!",
"Not right now"
);
}
}
M_MenuQueueStopSend(MA_NONE);
}

View file

@ -39,21 +39,21 @@ menuitem_t PAUSE_PlaybackMenu[] =
{IT_CALL | IT_STRING, "Hide Menu", NULL, "M_PHIDE", {.routine = M_SelectableClearMenus}, 0, 0},
{IT_CALL | IT_STRING, "Restart", NULL, "M_PRSTRT", {.routine = M_PlaybackRewind}, 20, 0},
{IT_CALL | IT_STRING, "Pause", NULL, "M_PPAUSE", {.routine = M_PlaybackPause}, 36, 0},
{IT_CALL | IT_STRING, "Fast-Forward", NULL, "M_PFFWD", {.routine = M_PlaybackFastForward}, 52, 0},
{IT_CALL | IT_STRING, "Restart", NULL, "M_PRSTRT", {.routine = M_PlaybackRewind}, 20, 0},
{IT_CALL | IT_STRING, "Resume", NULL, "M_PRESUM", {.routine = M_PlaybackPause}, 36, 0},
{IT_CALL | IT_STRING, "Advance Frame", NULL, "M_PFADV", {.routine = M_PlaybackAdvance}, 52, 0},
{IT_CALL | IT_STRING, "Rewind 5 seconds", NULL, "M_PREW", {.routine = M_PlaybackRewind}, 36, 0},
{IT_CALL | IT_STRING, "Pause", NULL, "M_PPAUSE", {.routine = M_PlaybackPause}, 52, 0},
{IT_CALL | IT_STRING, "Fast-Forward", NULL, "M_PFFWD", {.routine = M_PlaybackFastForward}, 68, 0},
{IT_CALL | IT_STRING, "Resume", NULL, "M_PRESUM", {.routine = M_PlaybackPause}, 52, 0},
{IT_CALL | IT_STRING, "Advance Frame", NULL, "M_PFADV", {.routine = M_PlaybackAdvance}, 68, 0},
{IT_ARROWS | IT_STRING, "View Count", NULL, "M_PVIEWS", {.routine = M_PlaybackSetViews}, 72, 0},
{IT_ARROWS | IT_STRING, "Viewpoint", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 88, 0},
{IT_ARROWS | IT_STRING, "Viewpoint 2", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 104, 0},
{IT_ARROWS | IT_STRING, "Viewpoint 3", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 120, 0},
{IT_ARROWS | IT_STRING, "Viewpoint 4", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 136, 0},
{IT_ARROWS | IT_STRING, "View Count", NULL, "M_PVIEWS", {.routine = M_PlaybackSetViews}, 88, 0},
{IT_ARROWS | IT_STRING, "Viewpoint", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 104, 0},
{IT_ARROWS | IT_STRING, "Viewpoint 2", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 120, 0},
{IT_ARROWS | IT_STRING, "Viewpoint 3", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 136, 0},
{IT_ARROWS | IT_STRING, "Viewpoint 4", NULL, "M_PNVIEW", {.routine = M_PlaybackAdjustView}, 152, 0},
{IT_CALL | IT_STRING, "Toggle Director", NULL, "UN_IC11A", {.routine = M_PlaybackToggleDirector}, 156, 0},
{IT_CALL | IT_STRING, "Toggle Free Camera", NULL, "M_PVIEWS", {.routine = M_PlaybackToggleFreecam}, 172, 0},
{IT_CALL | IT_STRING, "Stop Playback", NULL, "M_PEXIT", {.routine = M_PlaybackQuit}, 188, 0},
{IT_CALL | IT_STRING, "Toggle Director", NULL, "UN_IC11A", {.routine = M_PlaybackToggleDirector}, 172, 0},
{IT_CALL | IT_STRING, "Toggle Free Camera", NULL, "M_PVIEWS", {.routine = M_PlaybackToggleFreecam}, 188, 0},
{IT_CALL | IT_STRING, "Stop Playback", NULL, "M_PEXIT", {.routine = M_PlaybackQuit}, 204, 0},
};
menu_t PAUSE_PlaybackMenuDef = {
@ -151,21 +151,21 @@ static void M_PlaybackTick(void)
playback_last_menu_interaction_leveltime = leveltime - 6*TICRATE;
// Toggle items
if (paused && !demo.rewinding)
if (paused)
{
PAUSE_PlaybackMenu[playback_pause].status = PAUSE_PlaybackMenu[playback_fastforward].status = PAUSE_PlaybackMenu[playback_rewind].status = IT_DISABLED;
PAUSE_PlaybackMenu[playback_resume].status = PAUSE_PlaybackMenu[playback_advanceframe].status = PAUSE_PlaybackMenu[playback_backframe].status = IT_CALL|IT_STRING;
PAUSE_PlaybackMenu[playback_pause].status = PAUSE_PlaybackMenu[playback_fastforward].status = IT_DISABLED;
PAUSE_PlaybackMenu[playback_resume].status = PAUSE_PlaybackMenu[playback_advanceframe].status = IT_CALL|IT_STRING;
if (itemOn >= playback_rewind && itemOn <= playback_fastforward)
itemOn += playback_backframe - playback_rewind;
if (itemOn >= playback_pause && itemOn <= playback_fastforward)
itemOn += playback_resume - playback_pause;
}
else
{
PAUSE_PlaybackMenu[playback_pause].status = PAUSE_PlaybackMenu[playback_fastforward].status = PAUSE_PlaybackMenu[playback_rewind].status = IT_CALL|IT_STRING;
PAUSE_PlaybackMenu[playback_resume].status = PAUSE_PlaybackMenu[playback_advanceframe].status = PAUSE_PlaybackMenu[playback_backframe].status = IT_DISABLED;
PAUSE_PlaybackMenu[playback_pause].status = PAUSE_PlaybackMenu[playback_fastforward].status = IT_CALL|IT_STRING;
PAUSE_PlaybackMenu[playback_resume].status = PAUSE_PlaybackMenu[playback_advanceframe].status = IT_DISABLED;
if (itemOn >= playback_backframe && itemOn <= playback_advanceframe)
itemOn -= playback_backframe - playback_rewind;
if (itemOn >= playback_resume && itemOn <= playback_advanceframe)
itemOn -= playback_resume - playback_pause;
}
if (modeattacking)
@ -173,10 +173,10 @@ static void M_PlaybackTick(void)
for (i = playback_viewcount; i <= playback_director; i++)
PAUSE_PlaybackMenu[i].status = IT_DISABLED;
PAUSE_PlaybackMenu[playback_freecam].mvar1 = 72;
PAUSE_PlaybackMenu[playback_quit].mvar1 = 88;
PAUSE_PlaybackMenu[playback_freecam].mvar1 = 88;
PAUSE_PlaybackMenu[playback_quit].mvar1 = 104;
currentMenu->x = BASEVIDWIDTH/2 - 52;
currentMenu->x = BASEVIDWIDTH/2 - 60;
}
else
{
@ -189,11 +189,11 @@ static void M_PlaybackTick(void)
for (i = r_splitscreen+1; i < 4; i++)
PAUSE_PlaybackMenu[playback_view1+i].status = IT_DISABLED;
PAUSE_PlaybackMenu[playback_freecam].mvar1 = 172;
PAUSE_PlaybackMenu[playback_quit].mvar1 = 188;
PAUSE_PlaybackMenu[playback_freecam].mvar1 = 188;
PAUSE_PlaybackMenu[playback_quit].mvar1 = 204;
//currentMenu->x = BASEVIDWIDTH/2 - 94;
currentMenu->x = BASEVIDWIDTH/2 - 96;
//currentMenu->x = BASEVIDWIDTH/2 - 102;
currentMenu->x = BASEVIDWIDTH/2 - 104;
}
}
@ -204,34 +204,38 @@ void M_SetPlaybackMenuPointer(void)
void M_PlaybackRewind(INT32 choice)
{
#if 0
static tic_t lastconfirmtime;
const tic_t curleveltime = leveltime;
(void)choice;
if (!demo.rewinding)
if (choice == playback_rewind)
{
if (paused)
demo.simplerewind = (paused ? DEMO_REWIND_PAUSE : DEMO_REWIND_RESUME);
menuactive = false;
}
G_DoPlayDemo(NULL); // Restart the current demo
if (demo.simplerewind)
{
if (curleveltime > 5*TICRATE)
{
G_ConfirmRewind(leveltime-1);
paused = true;
S_PauseAudio();
g_fast_forward = curleveltime - (5 * TICRATE);
g_fast_forward_clock_stop = INFTICS; //I_GetTime() + 2 * TICRATE; -- maybe?
}
else
demo.rewinding = paused = true;
{
if (demo.simplerewind == DEMO_REWIND_PAUSE)
{
paused = true;
S_PauseAudio();
}
demo.simplerewind = DEMO_REWIND_OFF;
}
menuactive = true;
}
else if (lastconfirmtime + TICRATE/2 < I_GetTime())
else
{
lastconfirmtime = I_GetTime();
G_ConfirmRewind(leveltime);
M_ClearMenus(true);
}
CV_SetValue(&cv_playbackspeed, 1);
#else
(void)choice;
G_DoPlayDemo(NULL); // Restart the current demo
M_ClearMenus(true);
#endif
}
void M_PlaybackPause(INT32 choice)
@ -240,13 +244,7 @@ void M_PlaybackPause(INT32 choice)
paused = !paused;
if (demo.rewinding)
{
G_ConfirmRewind(leveltime);
paused = true;
S_PauseAudio();
}
else if (paused)
if (paused)
S_PauseAudio();
else
S_ResumeAudio();
@ -258,12 +256,6 @@ void M_PlaybackFastForward(INT32 choice)
{
(void)choice;
if (demo.rewinding)
{
G_ConfirmRewind(leveltime);
paused = false;
S_ResumeAudio();
}
CV_SetValue(&cv_playbackspeed, cv_playbackspeed.value == 1 ? 4 : 1);
}

View file

@ -495,7 +495,24 @@ struct CheckpointManager
else // Checkpoint isn't in the list, find any associated tagged lines and make the pair
{
if (chk->linetag())
lines_.try_emplace(chk->linetag(), tagged_lines(chk->linetag()));
{
auto lines = tagged_lines(chk->linetag());
if (lines.empty() && gametype != GT_TUTORIAL)
{
CONS_Alert(CONS_WARNING, "Checkpoint thing %d, has linetag %d, but no lines found. Please ensure all checkpoints have associated lines.\n", chk->spawnpoint - mapthings, chk->linetag());
}
else
{
lines_.try_emplace(chk->linetag(), lines);
}
}
else
{
if (gametype != GT_TUTORIAL)
{
CONS_Alert(CONS_WARNING, "Checkpoint thing %d, has no linetag. Please ensure all checkpoint things have a linetag.\n", chk->spawnpoint - mapthings);
}
}
list_.push_front(chk);
count_ += 1; // Mobjlist can't have a count on it, so we keep it here
}
@ -567,6 +584,11 @@ void Obj_CheckpointThink(mobj_t* end)
void Obj_CrossCheckpoints(player_t* player, fixed_t old_x, fixed_t old_y)
{
if (player->exiting) // can't cross checkpoints when exiting
{
return;
}
LineOnDemand ray(old_x, old_y, player->mo->x, player->mo->y, player->mo->radius);
auto it = std::find_if(

View file

@ -110,7 +110,7 @@ struct Side : Graphic
struct Toucher : Mobj
{
bool boosting() const { return player && (player->sneakertimer || player->panelsneakertimer || K_PlayerCanPunt(player)); }
bool boosting() const { return player && (player->sneakertimer || player->panelsneakertimer || player->weaksneakertimer || K_PlayerCanPunt(player)); }
};
struct AnyBox : Graphic

View file

@ -60,6 +60,7 @@ struct Visual : Mobj
}
move_origin(shield()->pos());
scale(5 * shield()->follow()->scale() / 4);
renderflags = (renderflags & ~RF_DONTDRAW) |
(shield()->state()->num() == S_INVISIBLE ? 0 : RF_DONTDRAW);

View file

@ -599,7 +599,8 @@ hyudoro_patrol_hit_player
P_SetTarget(&hyudoro_target(hyu), master);
K_SpawnAmps(master->player, K_PvPAmpReward(20, master->player, player), toucher);
if (master && !P_MobjWasRemoved(master))
K_SpawnAmps(master->player, K_PvPAmpReward(20, master->player, player), toucher);
if (center)
P_RemoveMobj(center);

View file

@ -55,6 +55,7 @@ struct Visual : Mobj
}
move_origin(shield()->pos());
scale(5 * shield()->follow()->scale() / 4);
dispoffset = state()->num() == S_THNB1 ? -1 : 1;
return true;

View file

@ -190,7 +190,7 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2)
return true;
}
if (K_TryPickMeUp(t1, t2))
if (K_TryPickMeUp(t1, t2, false))
return true;
if (t1->type == MT_GARDENTOP)

View file

@ -96,7 +96,7 @@ private:
{
const player_t* p = toucher->player;
if (p->sneakertimer || p->panelsneakertimer || p->invincibilitytimer || p->growshrinktimer > 0 || p->hyudorotimer)
if (p->sneakertimer || p->panelsneakertimer || p->weaksneakertimer || p->invincibilitytimer || p->growshrinktimer > 0 || p->hyudorotimer)
{
return;
}

View file

@ -542,58 +542,6 @@ void Obj_SLSTMaceMobjThink(mobj_t* mo)
}
}
#define BUMPER_STRENGTH (56)
void Obj_SSBumperTouchSpecial(mobj_t* special, mobj_t* toucher)
{
angle_t hang;
angle_t vang;
fixed_t str;
int i;
hang = R_PointToAngle2(special->x, special->y, toucher->x, toucher->y);
vang = 0;
if (P_IsObjectOnGround(toucher) == false)
{
vang = R_PointToAngle2(
FixedHypot(special->x, special->y), special->z + (special->height >> 1),
FixedHypot(toucher->x, toucher->y), toucher->z + (toucher->height >> 1)
);
}
str = (BUMPER_STRENGTH * special->scale) >> 1;
toucher->momx = FixedMul(FixedMul(str, FCOS(hang)), abs(FCOS(vang)));
toucher->momy = FixedMul(FixedMul(str, FSIN(hang)), abs(FCOS(vang)));
toucher->momz = FixedMul(str, FSIN(vang));
if (toucher->player)
{
if (toucher->player->tiregrease == 0)
{
for (i = 0; i < 2; i++)
{
mobj_t *grease = P_SpawnMobjFromMobj(toucher, 0, 0, 0, MT_TIREGREASE);
P_SetTarget(&grease->target, toucher);
grease->angle = toucher->angle;
grease->extravalue1 = i;
}
}
if (toucher->player->tiregrease < 2*TICRATE) // greasetics
{
toucher->player->tiregrease = 2*TICRATE;
}
}
if (special->state != &states[special->info->seestate])
{
S_StartSound(special, special->info->deathsound);
P_SetMobjState(special, special->info->seestate);
}
}
void Obj_SSBumperMobjSpawn(mobj_t* mo)
{
mo->shadowscale = FRACUNIT;

View file

@ -895,7 +895,7 @@ static UINT8 GetUFODamage(mobj_t *inflictor, UINT8 damageType)
{
// Players deal damage relative to how many sneakers they used.
targetdamaging = UFOD_BOOST;
ret = 15 * max(1, inflictor->player->numsneakers + inflictor->player->numpanelsneakers);
ret = 15 * max(1, inflictor->player->numsneakers + inflictor->player->numpanelsneakers + inflictor->player->numweaksneakers);
break;
}
case MT_SPECIAL_UFO:
@ -1056,6 +1056,8 @@ void Obj_PlayerUFOCollide(mobj_t *ufo, mobj_t *other)
other->player->numsneakers = 0;
other->player->panelsneakertimer = 0;
other->player->numpanelsneakers = 0;
other->player->weaksneakertimer = 0;
other->player->numweaksneakers = 0;
// Copied from Obj_OrbinautThrown
const ffloor_t *rover = P_IsObjectFlipped(other) ? other->ceilingrover : other->floorrover;

View file

@ -321,6 +321,7 @@ void A_BlendEyePuyoHack(mobj_t *actor);
void A_MakeSSCandle(mobj_t *actor);
void A_HologramRandomTranslucency(mobj_t *actor);
void A_SSChainShatter(mobj_t *actor);
void A_GenericBumper(mobj_t *actor);
//for p_enemy.c
@ -12668,3 +12669,54 @@ void A_SSChainShatter(mobj_t* actor)
actor->fuse = 1;
}
// var1 = If -1, triggered by collision event
// var2 = Strength value
//
// mobjinfo dependencies:
// - deathsound - bumper noise
// - seestate - bumper flashing state
//
void A_GenericBumper(mobj_t* actor)
{
if (var1 != -1)
return;
mobj_t *other = actor->target;
if (!other)
return;
// This code was ported from Lua
// Original was Balloon Park's bumpers?
INT32 hang = R_PointToAngle2(
actor->x, actor->y,
other->x, other->y
);
INT32 vang = 0;
if (!P_IsObjectOnGround(other))
{
vang = R_PointToAngle2(
FixedHypot(actor->x, actor->y), actor->z + (actor->height / 2),
FixedHypot(other->x, other->y), other->z + (other->height / 2)
);
}
INT32 baseStrength = abs(astate->var2);
fixed_t strength = (baseStrength * actor->scale) / 2;
other->momx = FixedMul(FixedMul(strength, FCOS(hang)), abs(FCOS(vang)));
other->momy = FixedMul(FixedMul(strength, FSIN(hang)), abs(FCOS(vang)));
other->momz = FixedMul(strength, FSIN(vang));
if (other->player)
K_SetTireGrease(other->player, max(other->player->tiregrease, 2*TICRATE));
if (actor->state != &states[actor->info->seestate])
{
S_StartSound(actor, actor->info->deathsound);
P_SetMobjState(actor, actor->info->seestate);
}
}

View file

@ -667,7 +667,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
if (!player->mo || player->spectator)
return;
if (K_TryPickMeUp(special, toucher))
if (K_TryPickMeUp(special, toucher, false))
return;
// attach to player!
@ -1083,10 +1083,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
Obj_TrickBalloonTouchSpecial(special, toucher);
return;
case MT_SEALEDSTAR_BUMPER:
Obj_SSBumperTouchSpecial(special, toucher);
return;
case MT_PULLUPHOOK:
Obj_PulleyHookTouch(special, toucher);
return;
@ -3292,6 +3288,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
player->sneakertimer = player->numsneakers = 0;
player->panelsneakertimer = player->numpanelsneakers = 0;
player->weaksneakertimer = player->numweaksneakers = 0;
player->driftboost = player->strongdriftboost = 0;
player->gateBoost = 0;
player->fastfall = 0;

View file

@ -21,6 +21,7 @@
svg_rocks
using link = mobj_t*;
using each_ref = std::initializer_list<std::reference_wrapper<mobj_t*>>;

View file

@ -612,6 +612,9 @@ void P_InitTIDHash(void);
void P_AddThingTID(mobj_t *mo);
void P_RemoveThingTID(mobj_t *mo);
void P_SetThingTID(mobj_t *mo, mtag_t tid);
// This function cannot be safely called after *i is removed!
// Please call at start of loops if *i is to be mutated
mobj_t *P_FindMobjFromTID(mtag_t tid, mobj_t *i, mobj_t *activator);
void P_DeleteMobjStringArgs(mobj_t *mobj);

View file

@ -1552,7 +1552,19 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
if (!K_PuntCollide(thing, g_tm.thing))
{
K_KartSolidBounce(g_tm.thing, thing);
state_t *st = &states[thing->info->spawnstate];
if (st->action.acp1 == A_GenericBumper)
{
P_SetTarget(&thing->target, g_tm.thing);
var1 = -1;
var2 = 0;
astate = st;
st->action.acp1(thing);
}
else
K_KartSolidBounce(g_tm.thing, thing);
}
return BMIT_CONTINUE;
}

View file

@ -7864,10 +7864,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
if (!mobj->target || !mobj->target->health
|| !mobj->target->player || !mobj->target->player->tripwireLeniency)
{
if (mobj->target && mobj->target->player && P_IsDisplayPlayer(mobj->target->player))
if (mobj->target && mobj->target->player
&& P_IsDisplayPlayer(mobj->target->player)
&& !(mobj->target->player->curshield == KSHIELD_TOP))
{
S_StopSoundByID(mobj->target, sfx_s3k40);
S_StartSound(mobj->target, sfx_gshaf);
S_StopSoundByID(mobj->target, TRIPWIRE_OK_SOUND);
S_StartSound(mobj->target, TRIPWIRE_NG_SOUND);
}
P_RemoveMobj(mobj);
@ -7989,7 +7991,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
if (p)
{
UINT16 timer = max(p->sneakertimer, p->panelsneakertimer);
UINT16 timer = max(max(p->sneakertimer, p->panelsneakertimer), p->weaksneakertimer);
if (timer > mobj->movecount)
P_SetMobjState(mobj, S_BOOSTFLAME);
mobj->movecount = timer;
@ -9110,7 +9112,10 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
&& (gamespeed != KARTSPEED_EASY)
&& (newplayer->tally.active == true)
&& (newplayer->tally.totalExp > 0) // Only true if not Time Attack
&& (newplayer->tally.exp >= newplayer->tally.totalExp)
&& (
(newplayer->tally.exp >= newplayer->tally.totalExp) ||
(K_InRaceDuel() && newplayer->duelscore == DUELWINNINGSCORE)
)
)
{
UINT8 pnum = (newplayer-players);
@ -10350,9 +10355,24 @@ void P_MobjThinker(mobj_t *mobj)
I_Assert(mobj != NULL);
I_Assert(!P_MobjWasRemoved(mobj));
if (P_IsKartItem(mobj->type) && mobj->target && !P_MobjWasRemoved(mobj->target))
{
player_t *link = mobj->target->player;
if (link && playeringame[link-players] && !link->spectator)
mobj->relinkplayer = (link-players) + 1;
}
// Remove dead target/tracer.
if (mobj->target && P_MobjWasRemoved(mobj->target))
{
P_SetTarget(&mobj->target, NULL);
if (P_IsKartItem(mobj->type) && mobj->relinkplayer && mobj->relinkplayer <= MAXPLAYERS)
{
player_t *relink = &players[mobj->relinkplayer-1];
if (playeringame[relink-players] && !relink->spectator && relink->mo && !P_MobjWasRemoved(relink->mo))
P_SetTarget(&mobj->target, relink->mo);
}
}
if (mobj->tracer && P_MobjWasRemoved(mobj->tracer))
P_SetTarget(&mobj->tracer, NULL);
if (mobj->hnext && P_MobjWasRemoved(mobj->hnext))
@ -15414,6 +15434,8 @@ void P_SetThingTID(mobj_t *mo, mtag_t tid)
//
// P_FindMobjFromTID
// Mobj tag search function.
// This function cannot be safely called after *i is removed!
// Please call at start of loops if *i is to be mutated
//
mobj_t *P_FindMobjFromTID(mtag_t tid, mobj_t *i, mobj_t *activator)
{

View file

@ -450,6 +450,8 @@ struct mobj_t
INT32 po_movecount; // Polyobject carrying (NOT savegame, NOT Lua)
UINT8 relinkplayer; // reassociate kartitem target when it dies. ACHTUNG 1-INDEXED
// WARNING: New fields must be added separately to savegame and Lua.
};

View file

@ -291,6 +291,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT32(save->p, players[i].exp);
WRITEINT32(save->p, players[i].gradingfactor);
WRITEUINT16(save->p, players[i].gradingpointnum);
WRITEINT16(save->p, players[i].duelscore);
WRITEINT32(save->p, players[i].cheatchecknum);
WRITEINT32(save->p, players[i].checkpointId);
@ -549,6 +550,8 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT8(save->p, players[i].numsneakers);
WRITEUINT16(save->p, players[i].panelsneakertimer);
WRITEUINT8(save->p, players[i].numpanelsneakers);
WRITEUINT16(save->p, players[i].weaksneakertimer);
WRITEUINT8(save->p, players[i].numweaksneakers);
WRITEUINT8(save->p, players[i].floorboost);
@ -983,6 +986,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].exp = READUINT32(save->p);
players[i].gradingfactor = READINT32(save->p);
players[i].gradingpointnum = READUINT16(save->p);
players[i].duelscore = READINT16(save->p);
players[i].cheatchecknum = READINT32(save->p);
players[i].checkpointId = READINT32(save->p);
@ -1194,6 +1198,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].numsneakers = READUINT8(save->p);
players[i].panelsneakertimer = READUINT16(save->p);
players[i].numpanelsneakers = READUINT8(save->p);
players[i].weaksneakertimer = READUINT16(save->p);
players[i].numweaksneakers = READUINT8(save->p);
players[i].floorboost = READUINT8(save->p);
players[i].growshrinktimer = READINT16(save->p);
@ -2997,6 +3003,7 @@ typedef enum
MD3_REAPPEAR = 1<<1,
MD3_PUNT_REF = 1<<2,
MD3_OWNER = 1<<3,
MD3_RELINK_PLAYER = 1<<4,
} mobj_diff3_t;
typedef enum
@ -3320,6 +3327,8 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8
diff3 |= MD3_PUNT_REF;
if (mobj->owner)
diff3 |= MD3_OWNER;
if (mobj->relinkplayer)
diff3 |= MD3_RELINK_PLAYER;
if (diff3 != 0)
diff2 |= MD2_MORE;
@ -3610,6 +3619,10 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8
{
WRITEUINT32(save->p, mobj->owner->mobjnum);
}
if (diff3 & MD3_RELINK_PLAYER)
{
WRITEUINT8(save->p, mobj->relinkplayer);
}
WRITEUINT32(save->p, mobj->mobjnum);
}
@ -4923,6 +4936,10 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker)
{
mobj->owner = (mobj_t *)(size_t)READUINT32(save->p);
}
if (diff3 & MD3_OWNER)
{
mobj->relinkplayer = READUINT8(save->p);
}
// link tid set earlier
P_AddThingTID(mobj);
@ -6757,6 +6774,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending)
{
WRITEUINT16(save->p, g_voteLevels[i][0]);
WRITEUINT16(save->p, g_voteLevels[i][1]);
WRITEUINT8(save->p, g_votes_striked[i]);
}
for (i = 0; i < VOTE_TOTAL; i++)
@ -6834,6 +6852,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending)
WRITESINT8(save->p, spbplace);
WRITEUINT8(save->p, rainbowstartavailable);
WRITEUINT8(save->p, inDuel);
WRITEUINT8(save->p, overtimecheckpoints);
WRITEUINT32(save->p, introtime);
WRITEUINT32(save->p, starttime);
@ -7143,6 +7162,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading)
{
g_voteLevels[i][0] = READUINT16(save->p);
g_voteLevels[i][1] = READUINT16(save->p);
g_votes_striked[i] = (boolean)READUINT8(save->p);
}
for (i = 0; i < VOTE_TOTAL; i++)
@ -7216,6 +7236,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading)
spbplace = READSINT8(save->p);
rainbowstartavailable = (boolean)READUINT8(save->p);
inDuel = (boolean)READUINT8(save->p);
overtimecheckpoints = (boolean)READUINT8(save->p);
introtime = READUINT32(save->p);
starttime = READUINT32(save->p);

View file

@ -8388,6 +8388,46 @@ void P_LoadLevelMusic(void)
Music_ResetLevelVolume();
}
void P_FreeLevelState(void)
{
if (numsectors)
{
F_EndTextPrompt(false, true);
K_UnsetDialogue();
ACS_InvalidateMapScope();
LUA_InvalidateLevel();
Obj_ClearCheckpoints();
sector_t *ss;
for (ss = sectors; sectors+numsectors != ss; ss++)
{
Z_Free(ss->attached);
Z_Free(ss->attachedsolid);
}
// This is the simplest guard against double frees.
// No valid map has zero sectors. Or, come to think
// of it, less than two in general! ~toast 310525
numsectors = 0;
}
// Clear pointers that would be left dangling by the purge
R_FlushTranslationColormapCache();
#ifdef HWRENDER
// Free GPU textures before freeing patches.
if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED))
HWR_ClearAllTextures();
#endif
G_FreeGhosts(); // ghosts are allocated with PU_LEVEL
Patch_FreeTag(PU_PATCH_LOWPRIORITY);
Patch_FreeTag(PU_PATCH_ROTATED);
Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1);
}
/** Loads a level from a lump or external wad.
*
* \param fromnetsave If true, skip some stuff because we're loading a netgame snapshot.
@ -8401,7 +8441,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
// 99% of the things already did, so.
// Map header should always be in place at this point
INT32 i, ranspecialwipe = 0;
sector_t *ss;
virtlump_t *encoreLump = NULL;
levelloading = true;
@ -8474,7 +8513,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
wipegamestate = gamestate; // Don't fade if reloading the gamestate
// Encore mode fade to pink to white
// This is handled BEFORE sounds are stopped.
else if (encoremode && !prevencoremode && modeattacking == ATTACKING_NONE && !demo.rewinding)
else if (encoremode && !prevencoremode && !demo.simplerewind)
{
if (rendermode != render_none)
{
@ -8545,7 +8584,14 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
// Let's fade to white here
// But only if we didn't do the encore startup wipe
if (!demo.rewinding && !reloadinggamestate)
if (demo.attract || demo.simplerewind)
{
// Leave the music alone! We're already playing what we want!
// Pull from RNG even though music will never change
// To silence playback has desynced warning
P_Random(PR_MUSICSELECT);
}
else if (!reloadinggamestate)
{
int wipetype = wipe_level_toblack;
@ -8558,15 +8604,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
FixedDiv((F_GetWipeLength(wipedefs[wipe_level_toblack])-2)*NEWTICRATERATIO, NEWTICRATE), MUSICRATE));
#endif
if (demo.attract)
{
; // Leave the music alone! We're already playing what we want!
// Pull from RNG even though music will never change
// To silence playback has desynced warning
P_Random(PR_MUSICSELECT);
}
else if (K_PodiumSequence())
if (K_PodiumSequence())
{
// mapmusrng is set by local player position in K_ResetCeremony
P_LoadLevelMusic();
@ -8643,44 +8681,42 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
}
F_RunWipe(wipetype, wipedefs[wipetype], false, ((levelfadecol == 0) ? "FADEMAP1" : "FADEMAP0"), false, false);
// Hold respawn to keep waiting until you're ready
if (G_IsModeAttackRetrying() && !demo.playback)
{
nowtime = lastwipetic;
while (G_PlayerInputDown(0, gc_respawn, splitscreen + 1) == true)
{
while (!((nowtime = I_GetTime()) - lastwipetic))
{
I_Sleep(cv_sleep.value);
I_UpdateTime();
} \
I_OsPolling();
G_ResetAllDeviceResponding();
for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1))
{
HandleGamepadDeviceEvents(&events[eventtail]);
G_MapEventsToControls(&events[eventtail]);
}
lastwipetic = nowtime;
if (moviemode && rendermode == render_opengl)
M_LegacySaveFrame();
else if (moviemode && rendermode == render_soft)
I_CaptureVideoFrame();
NetKeepAlive();
}
//wipestyleflags |= (WSF_FADEOUT|WSF_TOWHITE);
}
}
}
/*
if (!titlemapinaction)
wipegamestate = GS_LEVEL;
*/
// Close text prompt before freeing the old level
F_EndTextPrompt(false, true);
K_UnsetDialogue();
ACS_InvalidateMapScope();
LUA_InvalidateLevel();
Obj_ClearCheckpoints();
for (ss = sectors; sectors+numsectors != ss; ss++)
{
Z_Free(ss->attached);
Z_Free(ss->attachedsolid);
}
// Clear pointers that would be left dangling by the purge
R_FlushTranslationColormapCache();
#ifdef HWRENDER
// Free GPU textures before freeing patches.
if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED))
HWR_ClearAllTextures();
#endif
G_FreeGhosts(); // ghosts are allocated with PU_LEVEL
Patch_FreeTag(PU_PATCH_LOWPRIORITY);
Patch_FreeTag(PU_PATCH_ROTATED);
Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1);
P_FreeLevelState();
R_InitializeLevelInterpolators();

View file

@ -107,6 +107,7 @@ extern mapthing_t *mapthings;
void P_SetupLevelSky(const char *skytexname, boolean global);
void P_RespawnThings(void);
void P_FreeLevelState(void);
void P_ResetLevelMusic(void);
boolean P_UseContinuousLevelMusic(void);
void P_LoadLevelMusic(void);

View file

@ -53,6 +53,7 @@
#include "m_easing.h"
#include "music.h"
#include "k_battle.h" // battleprisons
#include "k_endcam.h" // K_EndCameraIsFreezing()
// Not sure if this is necessary, but it was in w_wad.c, so I'm putting it here too -Shadow Hog
#include <errno.h>
@ -1995,7 +1996,7 @@ static void K_HandleLapIncrement(player_t *player)
}
// finished race exit setup
if (player->laps > numlaps)
if (player->laps > numlaps && !K_InRaceDuel())
{
pflags_t applyflags = 0;
if (specialstageinfo.valid == true)
@ -2021,7 +2022,8 @@ static void K_HandleLapIncrement(player_t *player)
: skins[player->skin].flags;
if (skinflags & SF_IRONMAN)
{
SetRandomFakePlayerSkin(player, true, false);
if ((player->laps == 1 && lapisfresh) || !K_InRaceDuel()) // We'll do this in K_CheckpointCrossAward if necessary.
SetRandomFakePlayerSkin(player, true, false);
}
// Always trust waypoints entering the first lap.
@ -2064,24 +2066,32 @@ static void K_HandleLapIncrement(player_t *player)
if (rainbowstartavailable == true && player->mo->hitlag == 0)
{
S_StartSound(player->mo, sfx_s23c);
player->startboost = 125;
K_SpawnDriftBoostExplosion(player, 4);
K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false);
K_SpawnAmps(player, 35, player->mo);
if (g_teamplay)
if (K_InRaceDuel())
{
for (UINT8 j = 0; i < MAXPLAYERS; i++)
K_SpawnDriftElectricSparks(player, player->skincolor, false);
K_SpawnAmps(player, 20, player->mo);
}
else
{
S_StartSound(player->mo, sfx_s23c);
player->startboost = 125;
K_SpawnDriftBoostExplosion(player, 4);
K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false);
K_SpawnAmps(player, (K_InRaceDuel()) ? 20 : 35, player->mo);
if (g_teamplay)
{
if (!playeringame[j] || players[j].spectator || !players[j].mo || P_MobjWasRemoved(players[j].mo))
continue;
if (!G_SameTeam(player, &players[j]))
continue;
if (player == &players[j])
continue;
K_SpawnAmps(&players[j], 10, player->mo);
for (UINT8 j = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[j] || players[j].spectator || !players[j].mo || P_MobjWasRemoved(players[j].mo))
continue;
if (!G_SameTeam(player, &players[j]))
continue;
if (player == &players[j])
continue;
K_SpawnAmps(&players[j], 10, player->mo);
}
}
}
@ -2105,7 +2115,9 @@ static void K_HandleLapIncrement(player_t *player)
}
else if (P_IsDisplayPlayer(player))
{
if (numlaps > 1 && player->laps == numlaps) // final lap
if (K_InRaceDuel())
S_StartSound(NULL, sfx_s221);
else if (numlaps > 1 && player->laps == numlaps) // final lap
S_StartSound(NULL, sfx_s3k68);
else if ((player->laps > 1) && (player->laps < numlaps)) // non-final lap
S_StartSound(NULL, sfx_s221);
@ -2118,7 +2130,7 @@ static void K_HandleLapIncrement(player_t *player)
}
else
{
if ((player->laps > numlaps) && (player->position == 1))
if ((player->laps > numlaps) && (player->position == 1) && (!K_InRaceDuel()))
{
// opponent finished
S_StartSound(NULL, sfx_s253);
@ -3263,8 +3275,13 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha
return false;
}
while ((targetThing = P_FindMobjFromTID(args[1], targetThing, mo)) != NULL)
mobj_t *next = P_FindMobjFromTID(args[1], targetThing, mo);
while ((targetThing = next) != NULL)
{
// First in case of deletion. (Can't check for state == S_NULL because of A_ calls, etc)
next = P_FindMobjFromTID(args[1], targetThing, mo);
if (targetThing->player != NULL)
{
continue;
@ -4752,7 +4769,7 @@ void P_SetupSignExit(player_t *player, boolean tie)
return;
// SRB2Kart: FINALLY, add in an alternative if no place is found
if (player->mo && !P_MobjWasRemoved(player->mo))
if (player->mo && !P_MobjWasRemoved(player->mo) && !K_EndCameraIsFreezing())
{
thing = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->floorz, MT_SIGN);
thing->angle = bestAngle;

View file

@ -599,9 +599,9 @@ static inline void P_DeviceRumbleTick(void)
{
low = high = 65536 / 2;
}
else if (player->sneakertimer > (sneakertime-(TICRATE/2)) || player->panelsneakertimer > (sneakertime-(TICRATE/2)))
else if (player->sneakertimer > (sneakertime-(TICRATE/2)) || player->panelsneakertimer > (sneakertime-(TICRATE/2)) || player->weaksneakertimer > (sneakertime-(TICRATE/2)))
{
low = high = 65536 / (3+player->numsneakers+player->numpanelsneakers);
low = high = 65536 / (3+player->numsneakers+player->numpanelsneakers+player->numweaksneakers);
}
else if (((player->boostpower < FRACUNIT) || (player->stairjank > 8))
&& P_IsObjectOnGround(player->mo) && player->speed != 0)
@ -739,15 +739,7 @@ void P_Ticker(boolean run)
// Check for pause or menu up in single player
if (paused || P_AutoPause())
{
if (demo.rewinding && leveltime > 0)
{
leveltime = (leveltime-1) & ~3;
if (timeinmap > 0)
timeinmap = (timeinmap-1) & ~3;
G_PreviewRewind(leveltime);
}
else
P_RunChaseCameras(); // special case: allow freecam to MOVE during pause!
P_RunChaseCameras(); // special case: allow freecam to MOVE during pause!
return;
}
@ -762,17 +754,23 @@ void P_Ticker(boolean run)
if (demo.recording)
{
G_WriteDemoExtraData();
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i])
G_WriteDemoTiccmd(&players[i].cmd, i);
if (!G_ConsiderEndingDemoWrite())
{
G_WriteDemoExtraData();
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i])
G_WriteDemoTiccmd(&players[i].cmd, i);
}
}
if (demo.playback && !demo.waitingfortally)
{
G_ReadDemoExtraData();
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i])
G_ReadDemoTiccmd(&players[i].cmd, i);
if (!G_ConsiderEndingDemoRead())
{
G_ReadDemoExtraData();
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i])
G_ReadDemoTiccmd(&players[i].cmd, i);
}
}
LUA_ResetTicTimers();
@ -1078,7 +1076,7 @@ void P_Ticker(boolean run)
}
}
if (g_fast_forward == 0)
if (g_fast_forward == 0 || demo.simplerewind)
{
timeinmap++;
}
@ -1168,14 +1166,21 @@ void P_Ticker(boolean run)
if (demo.recording)
{
G_WriteAllGhostTics();
if (cv_recordmultiplayerdemos.value && demo.savebutton && demo.savebutton + 3*TICRATE < leveltime)
G_CheckDemoTitleEntry();
if (!G_ConsiderEndingDemoWrite())
{
G_WriteAllGhostTics();
}
}
else if (demo.playback && !demo.waitingfortally) // Use Ghost data for consistency checks.
else if (demo.playback && !demo.waitingfortally)
{
G_ConsAllGhostTics();
if (!G_ConsiderEndingDemoRead())
{
// Use Ghost data for consistency checks.
G_ConsAllGhostTics();
}
}
if (modeattacking)
@ -1238,9 +1243,6 @@ void P_Ticker(boolean run)
P_MapEnd();
if (demo.playback)
G_StoreRewindInfo();
for (i = 0; i < MAXPLAYERS; i++)
{
G_CopyTiccmd(&players[i].oldcmd, &players[i].cmd, 1);

View file

@ -1389,7 +1389,7 @@ void P_DoPlayerExit(player_t *player, pflags_t flags)
void P_DoAllPlayersExit(pflags_t flags, boolean trygivelife)
{
UINT8 i;
const boolean dofinishsound = (musiccountdown == 0);
const boolean dofinishsound = (musiccountdown == 0) && (!K_InRaceDuel());
if (grandprixinfo.gp == false
|| grandprixinfo.eventmode == GPEVENT_SPECIAL
@ -2348,7 +2348,7 @@ static void P_UpdatePlayerAngle(player_t *player)
// This is the hardest case for the turn solver, because your handling properties on
// client side are very different than your handling properties on server side—at least,
// until your drift status makes the full round-trip and is reflected in your gamestate.
if (player->drift && abs(player->drift) < 5)
if (player->drift && abs(player->drift) < 5 && player->cmd.latency)
{
steeringRight = KART_FULLTURN;
steeringLeft = -KART_FULLTURN;
@ -2621,7 +2621,7 @@ void P_MovePlayer(player_t *player)
////////////////////////////
// SRB2kart - Drifting smoke and fire
if ((player->sneakertimer || player->panelsneakertimer || player->flamedash)
if ((player->sneakertimer || player->panelsneakertimer || player->weaksneakertimer || player->flamedash)
&& onground && (leveltime & 1))
K_SpawnBoostTrail(player);
@ -3766,7 +3766,6 @@ boolean P_SpectatorJoinGame(player_t *player)
}
player->spectator = false;
player->pflags &= ~PF_WANTSTOJOIN;
player->spectatewait = 0;
player->team = TEAM_UNASSIGNED; // We will auto-assign later.
player->playerstate = PST_REBORN;
player->enteredGame = true;
@ -3781,6 +3780,10 @@ boolean P_SpectatorJoinGame(player_t *player)
// a surprise tool that will help us later...
text = va("\x82*%s entered the game.", player_names[player-players]);
if (P_IsMachineLocalPlayer(player) && player->spectatewait > TICRATE)
S_StartSound(NULL, sfx_s3ka9);
player->spectatewait = 0;
HU_AddChatText(text, false);
return true; // no more player->mo, cannot continue.
}
@ -4251,6 +4254,12 @@ void P_PlayerThink(player_t *player)
player->airtime++;
}
if ((player->pflags & PF_FAULT) || (player->pflags & PF_VOID))
{
player->lastairtime = 0;
player->airtime = 0;
}
cmd = &player->cmd;
// SRB2kart

View file

@ -3797,6 +3797,12 @@ boolean R_ThingVisible (mobj_t *thing)
case MT_BATTLECAPSULE_PIECE:
case MT_SPRAYCAN:
case MT_PLAYER:
case MT_LANDMINE:
case MT_SSMINE:
case MT_SSMINE_SHIELD:
case MT_CHECKPOINT_END:
case MT_SIGNSPARKLE:
case MT_THOK: // checkpoint parts
return false;
default:

View file

@ -198,7 +198,6 @@ static void (*music_fade_callback)();
static SDL_AudioDeviceID g_device_id;
static SDL_AudioDeviceID g_input_device_id;
static boolean g_input_device_paused;
void* I_GetSfx(sfxinfo_t* sfx)
{
@ -999,14 +998,14 @@ void I_UpdateAudioRecorder(void)
boolean I_SoundInputIsEnabled(void)
{
return g_input_device_id != 0 && !g_input_device_paused;
return g_input_device_id != 0;
}
boolean I_SoundInputSetEnabled(boolean enabled)
{
if (g_input_device_id == 0 && enabled)
{
if (SDL_GetNumAudioDevices(true) == 0)
if (!sound_started || SDL_GetNumAudioDevices(true) == 0)
{
return false;
}
@ -1023,21 +1022,17 @@ boolean I_SoundInputSetEnabled(boolean enabled)
CONS_Alert(CONS_WARNING, "Failed to open input audio device: %s\n", SDL_GetError());
return false;
}
g_input_device_paused = true;
}
if (enabled && g_input_device_paused)
{
SDL_PauseAudioDevice(g_input_device_id, SDL_FALSE);
g_input_device_paused = false;
}
else if (!enabled && !g_input_device_paused)
else if (g_input_device_id != 0 && !enabled)
{
SDL_PauseAudioDevice(g_input_device_id, SDL_TRUE);
SDL_ClearQueuedAudio(g_input_device_id);
g_input_device_paused = true;
SDL_CloseAudioDevice(g_input_device_id);
g_input_device_id = 0;
}
return !g_input_device_paused;
return enabled;
}
UINT32 I_SoundInputDequeueSamples(void *data, UINT32 len)

View file

@ -737,6 +737,8 @@ void ST_startTitleCard(void)
lt_ticker = lt_exitticker = lt_lasttic = 0;
lt_endtime = 4*TICRATE; // + (10*NEWTICRATERATIO);
lt_fade = 0;
WipeStageTitle = false;
}
//
@ -788,7 +790,7 @@ patch_t *ST_getRoundPicture(boolean small)
//
void ST_runTitleCard(void)
{
boolean run = !(paused || P_AutoPause() || g_fast_forward > 0);
boolean run = !(paused || P_AutoPause() || (g_fast_forward > 0 && demo.simplerewind == DEMO_REWIND_OFF));
INT32 auxticker;
boolean doroundicon = (ST_getRoundPicture(false) != NULL);

View file

@ -76,7 +76,6 @@ TYPEDEF (plrconfig);
TYPEDEF (filesneededconfig_pak);
TYPEDEF (doomdata_t);
TYPEDEF (serverelem_t);
TYPEDEF (rewind_t);
TYPEDEF (clientkey_pak);
TYPEDEF (serverchallenge_pak);
TYPEDEF (challengeall_pak);

View file

@ -951,6 +951,18 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
i--;
}
while (true);
if (standings->rankingsmode)
{
if (standings->isduel)
{
Y_DrawRankMode(BASEVIDWIDTH / 2 + xoffset, BASEVIDHEIGHT - 19, true);
}
else
{
Y_DrawRankMode(x + 122, returny - yspacing + 7, false);
}
}
}
//
@ -959,11 +971,15 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
// Handles drawing the bottom-of-screen progression.
// Currently requires intermission y_data for animation only.
//
void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, boolean widescreen)
void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, boolean widescreen, boolean adminmode)
{
if (roundqueue.size == 0)
{
return;
if (!adminmode
|| menuqueue.size == 0)
{
return;
}
}
// The following is functionally a hack.
@ -1029,6 +1045,10 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
prize_dot[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK4", PU_PATCH));
prize_dot[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK6", PU_PATCH));
patch_t *rpmark[2];
rpmark[0] = static_cast<patch_t*>(W_CachePatchName("R_RPMARK", PU_PATCH));
rpmark[1] = static_cast<patch_t*>(W_CachePatchName("R_R2MARK", PU_PATCH));
UINT8 *colormap = NULL, *oppositemap = NULL;
fixed_t playerx = 0, playery = 0;
UINT8 pskin = MAXSKINS;
@ -1067,10 +1087,37 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
upwa = true;
}
workingqueuesize--;
if (!adminmode)
{
workingqueuesize--;
}
}
INT32 widthofroundqueue = 24*(workingqueuesize - 1);
INT32 widthofroundqueue, totalsteps;
INT32 menusendoffset = 0;
if (menuqueue.sending)
{
if (menuqueue.sending > menuqueue.size)
{
menusendoffset = menuqueue.size;
}
else
{
menusendoffset = menuqueue.sending-1;
}
}
if (adminmode)
{
totalsteps = std::min(workingqueuesize + (menuqueue.size - menusendoffset), ROUNDQUEUE_MAX);
}
else
{
totalsteps = workingqueuesize;
}
widthofroundqueue = (totalsteps - 1) * 24;
INT32 x = (BASEVIDWIDTH - widthofroundqueue) / 2;
INT32 y, basey = 167 + offset;
@ -1079,7 +1126,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
// The following block handles horizontal easing of the
// progression bar on the last non-rankrestricted round.
if (standings->showrank == true)
if (!adminmode && standings->showrank == true)
{
fixed_t percentslide = 0;
SINT8 deferxoffs = 0;
@ -1139,12 +1186,22 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
V_DrawMappedPatch(xiter, basey, baseflags, queuebg_upwa, greymap);
}
// Draw to left side of screen
while (xiter > -bufferspace)
{
xiter -= 24;
V_DrawMappedPatch(xiter, basey, baseflags, queuebg_flat, greymap);
}
// Draw to right side of screen
xiter = x + widthofroundqueue;
while (xiter < BASEVIDWIDTH + bufferspace)
{
xiter += 24;
V_DrawMappedPatch(xiter, basey, baseflags, queuebg_flat, greymap);
}
// Actually queued maps
for (i = 0; i < workingqueuesize; i++)
{
// Draw the background, and grab the appropriate line, to the right of the dot
@ -1174,7 +1231,13 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
}
else
{
V_DrawMappedPatch(x, basey, baseflags, queuebg_flat, greymap);
V_DrawMappedPatch(
x,
basey,
baseflags,
((workingqueuesize == totalsteps) ? queuebg_flat : queuebg_upwa),
greymap
);
}
}
@ -1309,17 +1372,9 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
}
else
{
// No more line! Fill in background to right edge of screen
xiter = x;
while (xiter < BASEVIDWIDTH + bufferspace)
{
xiter += 24;
V_DrawMappedPatch(xiter, basey, baseflags, queuebg_flat, greymap);
}
// Handle special entry on the end
// (has to be drawn before the semifinal dot due to overlap)
if (standings->showrank == true)
if (!adminmode && standings->showrank == true)
{
const fixed_t x2 = x + spacetospecial;
@ -1330,7 +1385,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
}
else if (
doanimations == true
&& roundqueue.position == roundqueue.size-1
&& roundqueue.position == workingqueuesize
&& timer - interpoffs <= 2*TICRATE
)
{
@ -1510,13 +1565,62 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
x += 24;
}
totalsteps -= i;
// Maps in the progress of being queued on the menu
if (adminmode && totalsteps)
{
for (i = menusendoffset; i < (totalsteps + menusendoffset); i++)
{
upwa ^= true;
if (upwa == false)
{
y = basey + 4;
V_DrawMappedPatch(x, basey, baseflags, queuebg_down, greymap);
}
else
{
y = basey + 12;
if (i+1 != menuqueue.size) // no more line?
{
V_DrawMappedPatch(x, basey, baseflags, queuebg_upwa, greymap);
}
else
{
V_DrawMappedPatch(x, basey, baseflags, queuebg_flat, greymap);
}
}
V_DrawMappedPatch(
x - 8, y,
baseflags,
level_dot[BPP_AHEAD],
NULL
);
V_DrawMappedPatch(
x - 10, y - 14,
baseflags,
rpmark[0],
NULL
);
K_DrawMapAsFace(
x - 9, y - 13,
(baseflags|((menuqueue.entries[i].encore) ? V_FLIP : 0)),
menuqueue.entries[i].mapnum,
NULL
);
x += 24;
}
}
// Draw the player position through the round queue!
if (playery != 0)
{
patch_t *rpmark[2];
rpmark[0] = static_cast<patch_t*>(W_CachePatchName("R_RPMARK", PU_PATCH));
rpmark[1] = static_cast<patch_t*>(W_CachePatchName("R_R2MARK", PU_PATCH));
// Change alignment
playerx -= (10 * FRACUNIT);
playery -= (14 * FRACUNIT);
@ -1627,6 +1731,88 @@ void Y_DrawIntermissionButton(INT32 startslide, INT32 through, boolean widescree
}
}
//
// Y_DrawRankMode
//
// Draws EXP or MOBIUMS label depending on context.
// x and y designate the coordinates of the most bottom-right pixel to draw from (because it is the left extent and patch heights that vary),
// or the bottom-center if center is true.
//
void Y_DrawRankMode(INT32 x, INT32 y, boolean center)
{
boolean useMobiums = (powertype != PWRLV_DISABLED);
INT32 textWidth, middleLeftEdge, middleRightEdge, middleWidth;
char text[8];
char iconPatchName[8];
UINT8 iconWidth; // the graphic paddings are inconsistent...
UINT8 *iconColormap;
UINT8 *stickerColormap;
patch_t *iconPatch;
patch_t *stickerTail = static_cast<patch_t*>(W_CachePatchName("INT_STK1", PU_CACHE));
patch_t *stickerMiddle = static_cast<patch_t*>(W_CachePatchName("INT_STK2", PU_CACHE));
patch_t *stickerHead = center ? stickerTail : static_cast<patch_t*>(W_CachePatchName("INT_STK3", PU_CACHE));
UINT32 stickerHeadFlags = 0;
UINT8 stickerHeadOffset = 0;
if (useMobiums)
{
snprintf(text, sizeof text, "MOBIUMS");
snprintf(iconPatchName, sizeof iconPatchName, "K_STMOB");
iconWidth = 22;
iconColormap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(SKINCOLOR_NONE), GTC_CACHE);
stickerColormap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(SKINCOLOR_TEA), GTC_CACHE);
}
else
{
snprintf(text, sizeof text, "EXP");
snprintf(iconPatchName, sizeof iconPatchName, "K_STEXP");
iconWidth = 16;
iconColormap = R_GetTranslationColormap(TC_RAINBOW, static_cast<skincolornum_t>(SKINCOLOR_MUSTARD), GTC_CACHE);
stickerColormap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(SKINCOLOR_MUSTARD), GTC_CACHE);
}
iconPatch = static_cast<patch_t*>(W_CachePatchName(iconPatchName, PU_CACHE));
textWidth = (INT32)V_ThinStringWidth(text, 0);
middleLeftEdge = x - iconWidth - textWidth - 8;
middleRightEdge = x - stickerHead->width;
middleWidth = middleRightEdge - middleLeftEdge;
if (center)
{
// flip the right-hand sticker tail and keep it left-aligned
stickerHeadFlags |= V_FLIP;
stickerHeadOffset += stickerHead->width;
// sliiightly extend the right side of the sticker
middleWidth += 2;
middleRightEdge += 2;
// shift all components to the right so that our x coordinates are center-aligned
#define CENTER_SHIFT (stickerHead->width + middleWidth / 2)
x += CENTER_SHIFT;
middleLeftEdge += CENTER_SHIFT;
middleRightEdge += CENTER_SHIFT;
#undef CENTER_SHIFT
}
// draw sticker
V_DrawMappedPatch(middleRightEdge + stickerHeadOffset, y - stickerHead->height, stickerHeadFlags, stickerHead, stickerColormap);
V_DrawStretchyFixedPatch(
middleLeftEdge << FRACBITS,
(y - stickerMiddle->height) << FRACBITS,
(middleWidth << FRACBITS) / stickerMiddle->width + 1,
FRACUNIT,
0, stickerMiddle, stickerColormap
);
V_DrawMappedPatch(middleLeftEdge - stickerTail->width, y - stickerTail->height, 0, stickerTail, stickerColormap);
// draw icon and text
V_DrawMappedPatch(x - iconPatch->width - 6, y - iconPatch->height + 4, 0, iconPatch, iconColormap);
V_DrawThinString(middleLeftEdge - 1, y - 9, 0, text);
}
void Y_DrawIntermissionHeader(fixed_t x, fixed_t y, boolean gotthrough, const char *headerstring, boolean showroundnum, boolean small)
{
const INT32 v_width = (small ? BASEVIDWIDTH/2 : BASEVIDWIDTH);
@ -1840,7 +2026,7 @@ skiptallydrawer:
goto finalcounter;
// Returns early if there's no roundqueue entries to draw
Y_RoundQueueDrawer(&data, 0, true, false);
Y_RoundQueueDrawer(&data, 0, true, false, false);
if (netgame)
{
@ -2081,7 +2267,9 @@ void Y_Ticker(void)
// Basic bitch points
if (data.increase[data.num[q]])
{
if (--data.increase[data.num[q]])
data.increase[data.num[q]] = std::max(data.increase[data.num[q]] - 3, 0);
if (data.increase[data.num[q]] != 0)
kaching = false;
}
}
@ -2117,6 +2305,35 @@ boolean Y_ShouldDoIntermission(void)
return true;
}
//
// Y_GetIntermissionType
//
// Returns the intermission type from the current gametype.
//
intertype_t Y_GetIntermissionType(void)
{
intertype_t ret = static_cast<intertype_t>(gametypes[gametype]->intermission);
if (ret == int_scoreortimeattack)
{
UINT8 i = 0, nump = 0;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
{
continue;
}
nump++;
}
ret = (nump < 2 ? int_time : int_score);
}
return ret;
}
//
// Y_DetermineIntermissionType
//
@ -2131,21 +2348,7 @@ void Y_DetermineIntermissionType(void)
return;
}
// set initially
intertype = static_cast<intertype_t>(gametypes[gametype]->intermission);
// special cases
if (intertype == int_scoreortimeattack)
{
UINT8 i = 0, nump = 0;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
continue;
nump++;
}
intertype = (nump < 2 ? int_time : int_score);
}
intertype = Y_GetIntermissionType();
}
static UINT8 Y_PlayersBestPossiblePosition(player_t *const player)
@ -2433,9 +2636,10 @@ void Y_StartIntermission(void)
}
K_CashInPowerLevels();
SV_BumpMatchStats();
}
SV_BumpMatchStats();
if (!timer)
{
Y_EndIntermission();

View file

@ -54,16 +54,14 @@ void Y_Ticker(void);
// Specific sub-drawers
void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset);
void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, boolean widescreen);
void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, boolean widescreen, boolean adminmode);
void Y_DrawIntermissionButton(INT32 startslide, INT32 through, boolean widescreen);
void Y_DrawRankMode(INT32 x, INT32 y, boolean center);
void Y_StartIntermission(void);
void Y_MidIntermission(void);
void Y_EndIntermission(void);
boolean Y_ShouldDoIntermission(void);
void Y_DetermineIntermissionType(void);
void Y_PlayIntermissionMusic(void);
boolean Y_IntermissionPlayerLock(void);
@ -78,6 +76,10 @@ typedef enum
extern intertype_t intertype;
boolean Y_ShouldDoIntermission(void);
intertype_t Y_GetIntermissionType(void);
void Y_DetermineIntermissionType(void);
#ifdef __cplusplus
} // extern "C"
#endif