Merge remote-tracking branch 'origin/master' into lua-roulette

This commit is contained in:
Antonio Martinez 2025-07-18 20:17:56 -04:00
commit b6cee4850e
25 changed files with 1007 additions and 180 deletions

View file

@ -687,6 +687,7 @@ consvar_t cv_items[] = {
UnsavedNetVar("gardentop", "On").on_off(), UnsavedNetVar("gardentop", "On").on_off(),
UnsavedNetVar("gachabom", "On").on_off(), UnsavedNetVar("gachabom", "On").on_off(),
UnsavedNetVar("stoneshoe", "On").on_off(), UnsavedNetVar("stoneshoe", "On").on_off(),
UnsavedNetVar("toxomister", "On").on_off(),
UnsavedNetVar("dualsneaker", "On").on_off(), UnsavedNetVar("dualsneaker", "On").on_off(),
UnsavedNetVar("triplesneaker", "On").on_off(), UnsavedNetVar("triplesneaker", "On").on_off(),
UnsavedNetVar("triplebanana", "On").on_off(), UnsavedNetVar("triplebanana", "On").on_off(),

View file

@ -166,7 +166,7 @@ UINT8 *PutFileNeeded(UINT16 firstfile)
for (; i < numwadfiles; i++) //mainwads+1, otherwise we start on the first mainwad for (; i < numwadfiles; i++) //mainwads+1, otherwise we start on the first mainwad
{ {
// If it has only music/sound lumps, don't put it in the list // If it has only music/sound lumps, don't put it in the list
if (i > mainwads && !wadfiles[i]->important) if (!wadfiles[i]->important)
continue; continue;
if (firstfile) if (firstfile)
@ -175,6 +175,8 @@ UINT8 *PutFileNeeded(UINT16 firstfile)
continue; continue;
} }
// CONS_Printf("putting %d (%s) - mw %d\n", i, wadfiles[i]->filename, mainwads);
nameonly(strcpy(wadfilename, wadfiles[i]->filename)); nameonly(strcpy(wadfilename, wadfiles[i]->filename));
// Look below at the WRITE macros to understand what these numbers mean. // Look below at the WRITE macros to understand what these numbers mean.
@ -564,6 +566,9 @@ INT32 CL_CheckFiles(void)
#endif #endif
for (i = 0; i < fileneedednum || j < numwadfiles;) for (i = 0; i < fileneedednum || j < numwadfiles;)
{ {
// CONS_Printf("checking %d of %d / %d of %d?\n", i, fileneedednum, j, numwadfiles);
// CONS_Printf("i: %s / j: %s \n", fileneeded[i].filename, wadfiles[j]->filename);
if (j < numwadfiles && !wadfiles[j]->important) if (j < numwadfiles && !wadfiles[j]->important)
{ {
// Unimportant on our side. // Unimportant on our side.
@ -574,11 +579,17 @@ INT32 CL_CheckFiles(void)
// If this test is true, we've reached the end of one file list // If this test is true, we've reached the end of one file list
// and the other still has a file that's important // and the other still has a file that's important
if (i >= fileneedednum || j >= numwadfiles) if (i >= fileneedednum || j >= numwadfiles)
{
return 2; return 2;
}
// For the sake of speed, only bother with a md5 check // For the sake of speed, only bother with a md5 check
if (memcmp(wadfiles[j]->md5sum, fileneeded[i].md5sum, 16)) if (memcmp(wadfiles[j]->md5sum, fileneeded[i].md5sum, 16))
{
return 2; return 2;
}
// It's accounted for! let's keep going. // It's accounted for! let's keep going.
CONS_Debug(DBG_NETPLAY, "'%s' accounted for\n", fileneeded[i].filename); CONS_Debug(DBG_NETPLAY, "'%s' accounted for\n", fileneeded[i].filename);

View file

@ -199,7 +199,8 @@ Run this macro, then #undef FOREACH afterward
FOREACH (DROPTARGET, 21),\ FOREACH (DROPTARGET, 21),\
FOREACH (GARDENTOP, 22),\ FOREACH (GARDENTOP, 22),\
FOREACH (GACHABOM, 23),\ FOREACH (GACHABOM, 23),\
FOREACH (STONESHOE, 24) FOREACH (STONESHOE, 24),\
FOREACH (TOXOMISTER, 25)
typedef enum typedef enum
{ {
@ -1068,6 +1069,7 @@ struct player_t
mobj_t *hand; mobj_t *hand;
mobj_t *flickyAttacker; mobj_t *flickyAttacker;
mobj_t *stoneShoe; mobj_t *stoneShoe;
mobj_t *toxomisterCloud;
SINT8 pitblame; // Index of last player that hit you, resets after being in control for a bit. If you deathpit, credit the old attacker! SINT8 pitblame; // Index of last player that hit you, resets after being in control for a bit. If you deathpit, credit the old attacker!

View file

@ -3120,6 +3120,11 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
"S_FLYBOT767", "S_FLYBOT767",
"S_STON", "S_STON",
"S_TOXAA",
"S_TOXAA_DEAD",
"S_TOXAB",
"S_TOXBA",
}; };
// RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1",
@ -4028,6 +4033,10 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_STONESHOE", "MT_STONESHOE",
"MT_STONESHOE_CHAIN", "MT_STONESHOE_CHAIN",
"MT_TOXOMISTER_POLE",
"MT_TOXOMISTER_EYE",
"MT_TOXOMISTER_CLOUD",
}; };
const char *const MOBJFLAG_LIST[] = { const char *const MOBJFLAG_LIST[] = {

View file

@ -45,7 +45,8 @@ void ScreenshotPass::capture(Rhi& rhi)
{ {
// Read the aligned data into unaligned linear memory, flipping the rows in the process. // Read the aligned data into unaligned linear memory, flipping the rows in the process.
uint32_t pixel_data_row = (height_ - row) - 1; uint32_t pixel_data_row = (height_ - row) - 1;
std::move(&pixel_data_[pixel_data_row * read_stride], &pixel_data_[pixel_data_row * read_stride + stride], &packed_data_[row * stride]); uint8_t* pixel_data_row_ptr = &pixel_data_[pixel_data_row * read_stride];
std::move(pixel_data_row_ptr, pixel_data_row_ptr + stride, &packed_data_[row * stride]);
} }
if (g_takemapthumbnail != TMT_NO) if (g_takemapthumbnail != TMT_NO)

View file

@ -801,6 +801,8 @@ char sprnames[NUMSPRITES + 1][5] =
"STUN", "STUN",
"STON", "STON",
"TOXA",
"TOXB",
// Pulley // Pulley
"HCCH", "HCCH",
@ -3264,7 +3266,7 @@ state_t states[NUMSTATES] =
{SPR_WAYP, 1|FF_FLOORSPRITE, 1, {NULL}, 0, 0, S_NULL}, // S_WAYPOINTSPLAT {SPR_WAYP, 1|FF_FLOORSPRITE, 1, {NULL}, 0, 0, S_NULL}, // S_WAYPOINTSPLAT
{SPR_EGOO, 0, 1, {NULL}, 0, 0, S_NULL}, // S_EGOORB {SPR_EGOO, 0, 1, {NULL}, 0, 0, S_NULL}, // S_EGOORB
{SPR_AMPA, FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 41, 1, S_NULL}, // S_AMPS {SPR_AMPA, FF_FULLBRIGHT|FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 41, 1, S_NULL}, // S_AMPS
{SPR_EXPC, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_EXP {SPR_EXPC, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_EXP
// Water Trail // Water Trail
@ -3704,6 +3706,11 @@ state_t states[NUMSTATES] =
{SPR_STUN, FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 4, 4, S_NULL}, // S_FLYBOT767 {SPR_STUN, FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 4, 4, S_NULL}, // S_FLYBOT767
{SPR_STON, 0, -1, {NULL}, 0, 0, S_STON}, // S_STON {SPR_STON, 0, -1, {NULL}, 0, 0, S_STON}, // S_STON
//
{SPR_TOXA, 0, -1, {NULL}, 0, 0, S_TOXAA}, // S_TOXAA
{SPR_TOXA, 0, 175, {NULL}, 0, 0, S_NULL}, // S_TOXAA_DEAD
{SPR_TOXA, 1, -1, {NULL}, 0, 0, S_TOXAB}, // S_TOXAB
{SPR_TOXB, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 6, 5, S_TOXBA}, // S_TOXBA
}; };
mobjinfo_t mobjinfo[NUMMOBJTYPES] = mobjinfo_t mobjinfo[NUMMOBJTYPES] =
@ -22712,6 +22719,84 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
MF_SPECIAL|MF_SCENERY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_PICKUPFROMBELOW|MF_DONTENCOREMAP, // flags MF_SPECIAL|MF_SCENERY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_PICKUPFROMBELOW|MF_DONTENCOREMAP, // flags
S_NULL // raisestate S_NULL // raisestate
}, },
{ // MT_TOXOMISTER_POLE
-1, // doomednum
S_TOXAA, // spawnstate
1, // spawnhealth
S_NULL, // seestate
sfx_tossed, // seesound
8, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
0, // painchance
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_TOXAA_DEAD, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
32*FRACUNIT, // radius
64*FRACUNIT, // height
0, // display offset
0, // mass
0, // damage
sfx_None, // activesound
MF_SHOOTABLE|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_TOXOMISTER_EYE
-1, // doomednum
S_TOXAB, // spawnstate
1000, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
8, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
0, // painchance
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_NULL, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
32*FRACUNIT, // radius
64*FRACUNIT, // height
0, // display offset
0, // mass
0, // damage
sfx_None, // activesound
MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_TOXOMISTER_CLOUD
-1, // doomednum
S_TOXBA, // spawnstate
1000, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
8, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
0, // painchance
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_NULL, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
70*FRACUNIT, // radius
70*FRACUNIT, // height
0, // display offset
0, // mass
0, // damage
sfx_None, // activesound
MF_SPECIAL|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
}; };

View file

@ -1338,6 +1338,8 @@ typedef enum sprite
SPR_STUN, SPR_STUN,
SPR_STON, SPR_STON,
SPR_TOXA,
SPR_TOXB,
// Pulley // Pulley
SPR_HCCH, SPR_HCCH,
@ -4188,6 +4190,11 @@ typedef enum state
S_STON, S_STON,
S_TOXAA,
S_TOXAA_DEAD,
S_TOXAB,
S_TOXBA,
S_FIRSTFREESLOT, S_FIRSTFREESLOT,
S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1,
NUMSTATES NUMSTATES
@ -5119,6 +5126,10 @@ typedef enum mobj_type
MT_STONESHOE, MT_STONESHOE,
MT_STONESHOE_CHAIN, MT_STONESHOE_CHAIN,
MT_TOXOMISTER_POLE,
MT_TOXOMISTER_EYE,
MT_TOXOMISTER_CLOUD,
MT_FIRSTFREESLOT, MT_FIRSTFREESLOT,
MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1,
NUMMOBJTYPES NUMMOBJTYPES

View file

@ -2160,3 +2160,10 @@ void K_UpdateBotGameplayVars(player_t *player)
K_UpdateBotGameplayVarsItemUsage(player); K_UpdateBotGameplayVarsItemUsage(player);
} }
boolean K_BotUnderstandsItem(kartitems_t item)
{
if (item == KITEM_BALLHOG)
return false; // Sorry. MRs welcome!
return true;
}

View file

@ -401,6 +401,7 @@ void K_BotItemUsage(const player_t *player, ticcmd_t *cmd, INT16 turnamt);
void K_BotPickItemPriority(player_t *player); void K_BotPickItemPriority(player_t *player);
boolean K_BotUnderstandsItem(kartitems_t item);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"

View file

@ -172,6 +172,7 @@ static patch_t *kp_droptarget[3];
static patch_t *kp_gardentop[3]; static patch_t *kp_gardentop[3];
static patch_t *kp_gachabom[3]; static patch_t *kp_gachabom[3];
static patch_t *kp_stoneshoe[3]; static patch_t *kp_stoneshoe[3];
static patch_t *kp_toxomister[3];
static patch_t *kp_bar[2]; static patch_t *kp_bar[2];
static patch_t *kp_doublebar[2]; static patch_t *kp_doublebar[2];
static patch_t *kp_triplebar[2]; static patch_t *kp_triplebar[2];
@ -239,8 +240,11 @@ static patch_t *kp_team_you;
static patch_t *kp_duel_foe; static patch_t *kp_duel_foe;
static patch_t *kp_duel_you; static patch_t *kp_duel_you;
static patch_t *kp_duel_sticker; static patch_t *kp_duel_sticker;
static patch_t *kp_duel_4sticker;
static patch_t *kp_duel_under; static patch_t *kp_duel_under;
static patch_t *kp_duel_4under;
static patch_t *kp_duel_over; static patch_t *kp_duel_over;
static patch_t *kp_duel_4over;
static patch_t *kp_duel_margin[24]; static patch_t *kp_duel_margin[24];
patch_t *kp_autoroulette; patch_t *kp_autoroulette;
@ -649,6 +653,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_gardentop[0], "K_ITGTOP"); HU_UpdatePatch(&kp_gardentop[0], "K_ITGTOP");
HU_UpdatePatch(&kp_gachabom[0], "K_ITGBOM"); HU_UpdatePatch(&kp_gachabom[0], "K_ITGBOM");
HU_UpdatePatch(&kp_stoneshoe[0], "K_ITSTON"); HU_UpdatePatch(&kp_stoneshoe[0], "K_ITSTON");
HU_UpdatePatch(&kp_toxomister[0], "K_ITTOX");
HU_UpdatePatch(&kp_bar[0], "K_RBBAR"); HU_UpdatePatch(&kp_bar[0], "K_RBBAR");
HU_UpdatePatch(&kp_doublebar[0], "K_RBBAR2"); HU_UpdatePatch(&kp_doublebar[0], "K_RBBAR2");
HU_UpdatePatch(&kp_triplebar[0], "K_RBBAR3"); HU_UpdatePatch(&kp_triplebar[0], "K_RBBAR3");
@ -710,6 +715,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_gardentop[1], "K_ISGTOP"); HU_UpdatePatch(&kp_gardentop[1], "K_ISGTOP");
HU_UpdatePatch(&kp_gachabom[1], "K_ISGBOM"); HU_UpdatePatch(&kp_gachabom[1], "K_ISGBOM");
HU_UpdatePatch(&kp_stoneshoe[1], "K_ISSTON"); HU_UpdatePatch(&kp_stoneshoe[1], "K_ISSTON");
HU_UpdatePatch(&kp_toxomister[1], "K_ISTOX");
HU_UpdatePatch(&kp_bar[1], "K_SBBAR"); HU_UpdatePatch(&kp_bar[1], "K_SBBAR");
HU_UpdatePatch(&kp_doublebar[1], "K_SBBAR2"); HU_UpdatePatch(&kp_doublebar[1], "K_SBBAR2");
HU_UpdatePatch(&kp_triplebar[1], "K_SBBAR3"); HU_UpdatePatch(&kp_triplebar[1], "K_SBBAR3");
@ -769,6 +775,7 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_gardentop[2], "ISPYGTOP"); HU_UpdatePatch(&kp_gardentop[2], "ISPYGTOP");
HU_UpdatePatch(&kp_gachabom[2], "ISPYGBOM"); HU_UpdatePatch(&kp_gachabom[2], "ISPYGBOM");
HU_UpdatePatch(&kp_stoneshoe[2], "ISPYSTON"); HU_UpdatePatch(&kp_stoneshoe[2], "ISPYSTON");
HU_UpdatePatch(&kp_toxomister[2], "ISPYTOX");
// CHECK indicators // CHECK indicators
sprintf(buffer, "K_CHECKx"); sprintf(buffer, "K_CHECKx");
@ -1083,8 +1090,11 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_duel_foe, "DUEL_FOE"); HU_UpdatePatch(&kp_duel_foe, "DUEL_FOE");
HU_UpdatePatch(&kp_duel_sticker, "DUEL_S"); HU_UpdatePatch(&kp_duel_sticker, "DUEL_S");
HU_UpdatePatch(&kp_duel_4sticker, "DUEL4_S");
HU_UpdatePatch(&kp_duel_under, "DUEL_B"); HU_UpdatePatch(&kp_duel_under, "DUEL_B");
HU_UpdatePatch(&kp_duel_4under, "DUEL4_B");
HU_UpdatePatch(&kp_duel_over, "DUEL_B2"); HU_UpdatePatch(&kp_duel_over, "DUEL_B2");
HU_UpdatePatch(&kp_duel_4over, "DUEL4_B2");
HU_UpdatePatch(&kp_duel_you, "DUEL_YOU"); HU_UpdatePatch(&kp_duel_you, "DUEL_YOU");
sprintf(buffer, "DUELMBxx"); sprintf(buffer, "DUELMBxx");
@ -1190,6 +1200,7 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset)
kp_gardentop, kp_gardentop,
kp_gachabom, kp_gachabom,
kp_stoneshoe, kp_stoneshoe,
kp_toxomister,
}; };
if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS)) if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS))
@ -3306,20 +3317,50 @@ INT32 K_GetTransFlagFromFixed(fixed_t value)
} }
} }
static tic_t duel_lastleveltime = 0; // We want to draw teams and duel HUD in a player context,
static INT32 duel_marginanim = 0; // but also precisely control how often it's drawn, even if
static INT32 duel_lastmargin = 0; // some players have no view.
static INT32 youheight = 0; static UINT8 K_FirstActiveDisplayPlayer(player_t *player)
{
UINT8 i;
for (i = 0; i <= r_splitscreen; i++)
{
player_t *pl = &players[displayplayers[i]];
if (!pl->spectator && !camera[i].freecam)
break;
}
if (player == &players[displayplayers[i]])
return true;
return false;
}
// MAXSPLITSCREENPLAYERS not allowed here, warning for change later
static tic_t duel_lastleveltime[4];
static INT32 duel_marginanim[4];
static INT32 duel_lastmargin[4];
static INT32 youheight[4];
static void K_drawKartDuelScores(void) static void K_drawKartDuelScores(void)
{ {
if (!K_InRaceDuel()) if (!K_InRaceDuel())
return; return;
if (r_splitscreen > 1 && !K_FirstActiveDisplayPlayer(stplyr))
return;
using srb2::Draw; using srb2::Draw;
player_t *foe = K_DuelOpponent(stplyr); player_t *foe = K_DuelOpponent(stplyr);
if (stplyr == foe)
return;
boolean use4p = (r_splitscreen) ? 1 : 0;
UINT8 vn = R_GetViewNumber();
INT32 basex = 0; INT32 basex = 0;
INT32 basey = 48; INT32 basey = 48;
INT32 flags = V_SNAPTOLEFT|V_HUDTRANS|V_SLIDEIN; INT32 flags = V_SNAPTOLEFT|V_HUDTRANS|V_SLIDEIN;
@ -3342,13 +3383,66 @@ static void K_drawKartDuelScores(void)
INT32 youscorex = 16; INT32 youscorex = 16;
INT32 youscorey = 69; INT32 youscorey = 69;
Draw::Font scorefont = Draw::Font::kThinTimer; INT32 margx = 0;
INT32 margy = 0;
boolean redraw = false; // Draw a duplicate?
boolean redrawn = false;
if (use4p)
{
basex = BASEVIDWIDTH/2 - 40;
basey = 0;
flags = V_SNAPTOTOP|V_HUDTRANS|V_SLIDEIN;
redraw = true;
if (r_splitscreen == 1)
{
redraw = false;
flags |= V_SNAPTORIGHT;
if (R_GetViewNumber() == 1)
{
flags |= V_SNAPTOBOTTOM;
flags &= ~V_SNAPTOTOP;
basey = BASEVIDHEIGHT - 40;
}
basex = BASEVIDWIDTH - 80;
}
barx = 40;
bary = 7;
barheight = 35; // MOTHERFUCK FLIPPED IN 4P
barwidth = 4; // DITTO
foex = 6;
foey = 12;
youx = 63;
youy = 12;
foescorex = foex + 6;
foescorey = foey + 12;
youscorex = youx + 6;
youscorey = youy + 12;
margx = 15;
margy = -40;
}
redraw:
Draw::Font scorefont = use4p ? Draw::Font::kZVote : Draw::Font::kThinTimer;
Draw::Align scorealign = use4p ? Draw::Align::kCenter : Draw::Align::kLeft;
UINT8 ri = 6; UINT8 ri = 6;
INT32 youfill = skincolors[stplyr->skincolor].ramp[ri]; INT32 youfill = skincolors[stplyr->skincolor].ramp[ri];
INT32 foefill = skincolors[foe->skincolor].ramp[ri]; INT32 foefill = skincolors[foe->skincolor].ramp[ri];
V_DrawScaledPatch(basex, basey, flags, kp_duel_sticker); if (use4p)
V_DrawScaledPatch(basex, basey, flags, kp_duel_4sticker);
else
V_DrawScaledPatch(basex, basey, flags, kp_duel_sticker);
INT32 scoredelta = stplyr->duelscore - foe->duelscore; INT32 scoredelta = stplyr->duelscore - foe->duelscore;
INT32 clutchscore = DUELWINNINGSCORE - 1; // we want the bar to be full when NEXT checkpoint wins... INT32 clutchscore = DUELWINNINGSCORE - 1; // we want the bar to be full when NEXT checkpoint wins...
@ -3371,27 +3465,43 @@ static void K_drawKartDuelScores(void)
targetyouheight = 2*barheight - savemargin; targetyouheight = 2*barheight - savemargin;
} }
if (leveltime != duel_lastleveltime) if (leveltime != duel_lastleveltime[vn])
{ {
INT32 slide = std::max(1, abs(targetyouheight - youheight)/3); INT32 slide = std::max(1, abs(targetyouheight - youheight[vn])/3);
if (targetyouheight > youheight) if (targetyouheight > youheight[vn])
youheight += slide; youheight[vn] += slide;
else if (targetyouheight < youheight) else if (targetyouheight < youheight[vn])
youheight -= slide; youheight[vn] -= slide;
} }
INT32 foeheight = 2*barheight-youheight; // barheight is a single tied bar, so total height of the full gauge is 2x barheight INT32 foeheight = 2*barheight-youheight[vn]; // 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); if (use4p)
V_DrawFill(basex+barx, basey+bary-barheight+foeheight, barwidth, youheight, youfill|flags); {
V_DrawFill(basex+barx-barheight, basey+bary, foeheight, barwidth, foefill|flags);
V_DrawFill(basex+barx-barheight+foeheight, basey+bary, youheight[vn], barwidth, youfill|flags);
}
else
{
V_DrawFill(basex+barx, basey+bary-barheight, barwidth, foeheight, foefill|flags);
V_DrawFill(basex+barx, basey+bary-barheight+foeheight, barwidth, youheight[vn], youfill|flags);
}
V_DrawScaledPatch(basex, basey, flags, kp_duel_under); V_DrawScaledPatch(basex, basey, flags, use4p ? kp_duel_4under : 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); if (use4p)
Draw younum = Draw(basex+youscorex, basey+youscorey).flags(flags).font(scorefont).align(Draw::Align::kLeft); V_DrawScaledPatch(basex-barheight+foeheight, basey, flags, kp_duel_4over);
else
V_DrawScaledPatch(basex, basey-barheight+foeheight, flags, kp_duel_over);
if (!use4p)
{
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(scorealign);
Draw younum = Draw(basex+youscorex, basey+youscorey).flags(flags).font(scorefont).align(scorealign);
if (abs(scoredelta) == clutchscore && ((leveltime % 2) || cv_reducevfx.value)) if (abs(scoredelta) == clutchscore && ((leveltime % 2) || cv_reducevfx.value))
{ {
@ -3414,8 +3524,8 @@ static void K_drawKartDuelScores(void)
for (UINT8 draw = 0; draw < 2; draw++) for (UINT8 draw = 0; draw < 2; draw++)
{ {
UINT8 drawme = draw ? (stplyr - players) : (foe - players); UINT8 drawme = draw ? (stplyr - players) : (foe - players);
UINT8 drawx = basex + (draw ? youx : foex); UINT16 drawx = basex + (draw ? youx : foex);
UINT8 drawy = basey + (draw ? youy : foey); UINT16 drawy = basey + (draw ? youy : foey);
if (!playeringame[drawme] || players[drawme].spectator) if (!playeringame[drawme] || players[drawme].spectator)
continue; continue;
@ -3450,7 +3560,7 @@ static void K_drawKartDuelScores(void)
else else
colormap = R_GetTranslationColormap(workingskin, static_cast<skincolornum_t>(players[drawme].mo->color), GTC_CACHE); 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); V_DrawMappedPatch(drawx+xoff, drawy+yoff, flags|flipflag, faceprefix[workingskin][use4p ? FACE_MINIMAP : FACE_RANK], colormap);
} }
// Dogshit. Should have just figured out how to do log base 5 in C++. // Dogshit. Should have just figured out how to do log base 5 in C++.
@ -3466,139 +3576,150 @@ static void K_drawKartDuelScores(void)
INT32 boostspersymbol = 3; // How many boosts should it take to see a new symbol? INT32 boostspersymbol = 3; // How many boosts should it take to see a new symbol?
// rawmargin = (leveltime/10)%(3*boostspersymbol); // rawmargin = (leveltime/10)%(3*boostspersymbol);
if (duel_lastleveltime != leveltime) // Trigger the "slide" animation when rawmargin changes. if (duel_lastleveltime[vn] != leveltime) // Trigger the "slide" animation when rawmargin changes.
{ {
duel_marginanim = std::min(duel_marginanim + 1, 100); // not magic just arbitrary duel_marginanim[vn] = std::min(duel_marginanim[vn] + 1, 100); // not magic just arbitrary
if (duel_lastmargin != rawmargin) if (duel_lastmargin[vn] != rawmargin)
{ {
duel_marginanim = 0; duel_marginanim[vn] = 0;
duel_lastmargin = rawmargin; duel_lastmargin[vn] = rawmargin;
} }
} }
duel_lastleveltime = leveltime; duel_lastleveltime[vn] = leveltime;
// CONS_Printf("=== RAWMARGIN %d\n", rawmargin); // CONS_Printf("=== RAWMARGIN %d\n", rawmargin);
if (rawmargin == 0) 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. rawmargin--; // Start at 0, idiot
nummargindigits = 5;
for(UINT8 i = 0; i < nummargindigits; i++) // 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))
{ {
margindigits[i] = MARGINLEVELS-1; // Capped out. Show 5 Chaos.
} nummargindigits = 5;
} for(UINT8 i = 0; i < nummargindigits; i++)
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); margindigits[i] = MARGINLEVELS-1;
// CONS_Printf("testing %d (%d)\n", i, test);
if (margin >= test)
{
significant_margin = i;
break;
}
} }
}
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; INT32 index = significant_margin;
margindigits[nummargindigits] = index + minsymbol - 1; margindigits[nummargindigits] = index + minsymbol - 1;
// CONS_Printf("digit %d %d\n", nummargindigits, margindigits[nummargindigits]); // CONS_Printf("digit %d %d\n", nummargindigits, margindigits[nummargindigits]);
nummargindigits++; nummargindigits++;
// CONS_Printf("margin was %d ", margin); // CONS_Printf("margin was %d ", margin);
margin -= std::pow(symbolsperupgrade, index); margin -= std::pow(symbolsperupgrade, index);
// CONS_Printf("is %d\n", margin); // CONS_Printf("is %d\n", margin);
if (nummargindigits >= 3 + frac) if (nummargindigits >= 3 + frac)
break; break;
}
}
INT32 marginspacing = std::min(6, duel_marginanim[vn]);
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 + margx + marginx, basey + margy, flags, kp_duel_margin[margindigits[i]]);
marginx -= marginspacing;
} }
} }
INT32 marginspacing = std::min(6, duel_marginanim); if (redraw && !redrawn)
INT32 marginx = ((nummargindigits-1) * marginspacing)/2;
for (INT32 i = nummargindigits - 1; i >= 0; i--)
{ {
// CONS_Printf("draw %d - %d\n", i, margindigits[i]); basey = BASEVIDHEIGHT - 40;
V_DrawScaledPatch(basex + marginx, basey, flags, kp_duel_margin[margindigits[i]]); flags &= ~V_SNAPTOTOP;
marginx -= marginspacing; flags |= V_SNAPTOBOTTOM;
redrawn = true;
goto redraw;
} }
} }
static INT32 easedallyscore = 0; // MAXSPLITSCREENPLAYERS not allowed here, warning for changes later
static tic_t scorechangecooldown = 0; static INT32 easedallyscore[4];
static tic_t scorechangecooldown[4];
// Mildly ugly. Don't want to export this to khud when it's so nicely handled here, // 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. // but HUD hooks run at variable timing based on your actual framerate.
static tic_t teams_lastleveltime = 0; static tic_t teams_lastleveltime[4];
void K_drawKartTeamScores(boolean fromintermission, INT32 interoffset) void K_drawKartTeamScores(boolean fromintermission, INT32 interoffset)
{ {
if (G_GametypeHasTeams() == false) if (G_GametypeHasTeams() == false)
{
return; return;
}
if (r_splitscreen > 1 && !K_FirstActiveDisplayPlayer(stplyr))
return;
if (TEAM__MAX != 3) if (TEAM__MAX != 3)
return; // "maybe someday" - the magic conch return; // "maybe someday" - the magic conch
@ -3606,7 +3727,8 @@ void K_drawKartTeamScores(boolean fromintermission, INT32 interoffset)
// I get to write HUD code from scratch, so it's going to be horribly // I get to write HUD code from scratch, so it's going to be horribly
// verbose and obnoxious. // verbose and obnoxious.
UINT8 use4p = (r_splitscreen > 1) ? 1 : 0; UINT8 use4p = !!(r_splitscreen);
UINT8 vn = R_GetViewNumber();
INT32 basex = BASEVIDWIDTH/2 + 20; INT32 basex = BASEVIDWIDTH/2 + 20;
INT32 basey = 0; INT32 basey = 0;
@ -3660,6 +3782,18 @@ void K_drawKartTeamScores(boolean fromintermission, INT32 interoffset)
facex = -2; facex = -2;
facey = -5; facey = -5;
faceoff = 4; faceoff = 4;
if (r_splitscreen == 1 && !fromintermission)
{
basex += 110;
flags |= V_SNAPTORIGHT;
if (R_GetViewNumber() == 1)
{
flags |= V_SNAPTOBOTTOM;
flags &= ~V_SNAPTOTOP;
basey = 170;
}
}
} }
if (fromintermission) if (fromintermission)
@ -3723,47 +3857,47 @@ void K_drawKartTeamScores(boolean fromintermission, INT32 interoffset)
R_GetTranslationColormap(TC_RAINBOW, g_teaminfo[allies].color, GTC_CACHE) : R_GetTranslationColormap(TC_RAINBOW, g_teaminfo[allies].color, GTC_CACHE) :
R_GetTranslationColormap(TC_RAINBOW, g_teaminfo[enemies].color, GTC_CACHE); R_GetTranslationColormap(TC_RAINBOW, g_teaminfo[enemies].color, GTC_CACHE);
if (scorechangecooldown) if (scorechangecooldown[vn])
scorechangecooldown--; scorechangecooldown[vn]--;
// prevent giga flicker on team scoring // prevent giga flicker on team scoring
if (easedallyscore == allyscore) if (easedallyscore[vn] == allyscore)
{ {
// :O // :O
} }
else else
{ {
if (teams_lastleveltime != leveltime) // Timing consistency if (teams_lastleveltime[vn] != leveltime) // Timing consistency
{ {
INT32 delta = abs(easedallyscore - allyscore); // how wrong is display score? INT32 delta = abs(easedallyscore[vn] - allyscore); // how wrong is display score?
if (scorechangecooldown == 0 && delta) if (scorechangecooldown[vn] == 0 && delta)
{ {
if (allyscore > easedallyscore) if (allyscore > easedallyscore[vn])
{ {
easedallyscore++; easedallyscore[vn]++;
if (!cv_reducevfx.value) if (!cv_reducevfx.value)
allycolor = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_WHITE, GTC_CACHE); allycolor = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_WHITE, GTC_CACHE);
} }
else else
{ {
easedallyscore--; easedallyscore[vn]--;
if (!cv_reducevfx.value) if (!cv_reducevfx.value)
enemycolor = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_WHITE, GTC_CACHE); enemycolor = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_WHITE, GTC_CACHE);
} }
scorechangecooldown = TICRATE/delta; scorechangecooldown[vn] = TICRATE/delta;
} }
} }
if (!fromintermission) if (!fromintermission)
{ {
// replace scores with eased scores // replace scores with eased scores
allyscore = easedallyscore; allyscore = easedallyscore[vn];
enemyscore = totalscore - allyscore; enemyscore = totalscore - allyscore;
} }
} }
teams_lastleveltime = leveltime; teams_lastleveltime[vn] = leveltime;
fixed_t enemypercent = FixedDiv(enemyscore*FRACUNIT, totalscore*FRACUNIT); fixed_t enemypercent = FixedDiv(enemyscore*FRACUNIT, totalscore*FRACUNIT);
// fixed_t allypercent = FixedDiv(allyscore*FRACUNIT, totalscore*FRACUNIT); // fixed_t allypercent = FixedDiv(allyscore*FRACUNIT, totalscore*FRACUNIT);
@ -3806,7 +3940,7 @@ void K_drawKartTeamScores(boolean fromintermission, INT32 interoffset)
// Draw at the top and bottom of the screen in 4P. // Draw at the top and bottom of the screen in 4P.
// Draw only at the bottom in intermission. // Draw only at the bottom in intermission.
boolean shouldsecondpass = use4p; boolean shouldsecondpass = (r_splitscreen > 1);
boolean onsecondpass = fromintermission; boolean onsecondpass = fromintermission;
draw: draw:
@ -7746,11 +7880,8 @@ void K_drawKartHUD(void)
K_DrawKartPositionNum(stplyr->position); K_DrawKartPositionNum(stplyr->position);
} }
if (R_GetViewNumber() == 0) K_drawKartTeamScores(false, 0);
{ K_drawKartDuelScores();
K_drawKartTeamScores(false, 0);
K_drawKartDuelScores();
}
} }
// This sucks, but we need to draw rings before EXP because 4P amps // This sucks, but we need to draw rings before EXP because 4P amps

View file

@ -462,7 +462,7 @@ std::optional<TargetTracking::Tooltip> object_tooltip(const mobj_t* mobj)
case MT_PLAYER: case MT_PLAYER:
{ {
if (stplyr->fastfall == 0 && K_CanSuperTransfer(stplyr)) if (mobj->player == stplyr && stplyr->fastfall == 0 && K_CanSuperTransfer(stplyr))
return Tooltip( return Tooltip(
TextElement( TextElement(
TextElement().parse("<c_animated>").font(splitfont)) TextElement().parse("<c_animated>").font(splitfont))

View file

@ -4146,6 +4146,11 @@ fixed_t K_GetNewSpeed(const player_t *player)
p_speed = 15 * p_speed / 10; p_speed = 15 * p_speed / 10;
} }
if (!P_MobjWasRemoved(player->toxomisterCloud))
{
p_speed = FixedMul(p_speed, Obj_GetToxomisterCloudDrag(player->toxomisterCloud));
}
if (K_PlayerUsesBotMovement(player) == true && player->botvars.rubberband > 0) if (K_PlayerUsesBotMovement(player) == true && player->botvars.rubberband > 0)
{ {
// Acceleration is tied to top speed... // Acceleration is tied to top speed...
@ -7079,7 +7084,7 @@ mobj_t *K_ThrowKartItemEx(player_t *player, boolean missile, mobjtype_t mapthing
{ {
mobj_t *lasttrail = K_FindLastTrailMobj(player); mobj_t *lasttrail = K_FindLastTrailMobj(player);
if (mapthing == MT_BUBBLESHIELDTRAP) // Drop directly on top of you. if (mapthing == MT_BUBBLESHIELDTRAP || mapthing == MT_TOXOMISTER_POLE) // Drop directly on top of you.
{ {
newangle = player->mo->angle; newangle = player->mo->angle;
newx = player->mo->x + player->mo->momx; newx = player->mo->x + player->mo->momx;
@ -9582,6 +9587,11 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
player->pflags2 &= ~PF2_SUPERTRANSFERVFX; player->pflags2 &= ~PF2_SUPERTRANSFERVFX;
} }
if (K_PlayerUsesBotMovement(player) && !K_BotUnderstandsItem(player->itemtype) && player->itemamount)
{
K_DropItems(player);
}
if (player->transfer) if (player->transfer)
{ {
if (player->fastfall) if (player->fastfall)
@ -12785,6 +12795,7 @@ static INT32 K_FlameShieldMax(player_t *player)
UINT32 distv = 1024; // Pre no-scams: 2048 UINT32 distv = 1024; // Pre no-scams: 2048
distv = distv * 16 / FLAMESHIELD_MAX; // Old distv was based on a 16-segment bar distv = distv * 16 / FLAMESHIELD_MAX; // Old distv was based on a 16-segment bar
UINT32 scamradius = 1500*4; // How close is close enough that we shouldn't be allowed to scam 1st? UINT32 scamradius = 1500*4; // How close is close enough that we shouldn't be allowed to scam 1st?
// UINT8 i;
disttofinish = K_GetItemRouletteDistance(player, 8); disttofinish = K_GetItemRouletteDistance(player, 8);
@ -14010,23 +14021,35 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
player->instaWhipCharge = 0; player->instaWhipCharge = 0;
} }
if (player->cmd.buttons & BT_BAIL && (player->cmd.buttons & BT_RESPAWNMASK) != BT_RESPAWNMASK)
if ((player->cmd.buttons & BT_BAIL) && (player->cmd.buttons & BT_RESPAWNMASK) != BT_RESPAWNMASK && ((player->itemtype && player->itemamount) || (player->rings > 0) || player->superring > 0 || player->pickuprings > 0 || player->itemRoulette.active))
{ {
boolean grounded = P_IsObjectOnGround(player->mo); if (leveltime < introtime || (gametyperules & GTR_SPHERES))
onground && player->tumbleBounces == 0 ? player->bailcharge += 2 : player->bailcharge++; // charge twice as fast on the ground
if ((P_PlayerInPain(player) && player->bailcharge == 1) || (grounded && P_PlayerInPain(player) && player->bailcharge == 2)) // this is brittle ..
{ {
mobj_t *bail = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_BAILCHARGE); // No bailing in GTR_SPHERES because I cannot be fucked to do manual Last Chance right now.
S_StartSound(bail, sfx_gshb9); // I tried to use info.c, but you can't play sounds on mobjspawn via A_PlaySound // Maybe someday!
S_StartSound(bail, sfx_kc4e); if (!(player->oldcmd.buttons & BT_BAIL))
P_SetTarget(&bail->target, player->mo); if (P_IsDisplayPlayer(player))
bail->renderflags |= RF_FULLBRIGHT; // set fullbright here, were gonna animate frames in the thinker and it saves us from setting FF_FULLBRIGHT every frame S_StartSound(player->mo, sfx_s3k7b);
player->bailcharge = 0;
}
else if ((player->itemtype && player->itemamount) || player->rings > 0 || player->superring > 0 || player->pickuprings > 0 || player->itemRoulette.active)
{
// Set up bail charge, provided we have something to bail with (any rings or item resource).
boolean grounded = P_IsObjectOnGround(player->mo);
onground && player->tumbleBounces == 0 ? player->bailcharge += 2 : player->bailcharge++; // charge twice as fast on the ground
if ((P_PlayerInPain(player) && player->bailcharge == 1) || (grounded && P_PlayerInPain(player) && player->bailcharge == 2)) // this is brittle ..
{
mobj_t *bail = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_BAILCHARGE);
S_StartSound(bail, sfx_gshb9); // I tried to use info.c, but you can't play sounds on mobjspawn via A_PlaySound
S_StartSound(bail, sfx_kc4e);
P_SetTarget(&bail->target, player->mo);
bail->renderflags |= RF_FULLBRIGHT; // set fullbright here, were gonna animate frames in the thinker and it saves us from setting FF_FULLBRIGHT every frame
}
}
else
{
player->bailcharge = 0;
} }
}
else
{
player->bailcharge = 0;
} }
if ((!P_PlayerInPain(player) && player->bailcharge >= 5) || player->bailcharge >= BAIL_MAXCHARGE) if ((!P_PlayerInPain(player) && player->bailcharge >= 5) || player->bailcharge >= BAIL_MAXCHARGE)
@ -14039,7 +14062,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
UINT32 debtrings = 20; UINT32 debtrings = 20;
if (player->rings < 0) if (player->rings < 0)
{ {
debtrings -= player->rings; debtrings += player->rings;
player->rings = 0; player->rings = 0;
} }
@ -14951,8 +14974,15 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{ {
S_StartSound(player->mo, sfx_gsha7); S_StartSound(player->mo, sfx_gsha7);
P_Thrust(player->mo, K_MomentumAngle(player->mo), 25*player->mo->scale); if (P_IsObjectOnGround(player->mo)) // facing angle blends w/ momentum angle for game-feel
P_Thrust(player->mo, player->mo->angle, 25*player->mo->scale); {
P_Thrust(player->mo, player->mo->angle, 25*player->mo->scale);
P_Thrust(player->mo, K_MomentumAngle(player->mo), 25*player->mo->scale);
}
else // air version is momentum angle only, reduces cheese, is twice as strong to compensate
{
P_Thrust(player->mo, K_MomentumAngle(player->mo), 50*player->mo->scale);
}
UINT8 numsparks = 8; UINT8 numsparks = 8;
for (UINT8 i = 0; i < numsparks; i++) for (UINT8 i = 0; i < numsparks; i++)
@ -15059,6 +15089,21 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
player->botvars.itemconfirm = 0; player->botvars.itemconfirm = 0;
} }
break; break;
case KITEM_TOXOMISTER:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
K_SetItemOut(player); // need this to set itemscale
mobj_t *pole = K_ThrowKartItem(player, false, MT_TOXOMISTER_POLE, -1, 0, 0);
Obj_InitToxomisterPole(pole);
K_UnsetItemOut(player);
player->itemamount--;
K_PlayAttackTaunt(player->mo);
player->botvars.itemconfirm = 0;
}
break;
case KITEM_SAD: case KITEM_SAD:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO
&& !player->sadtimer) && !player->sadtimer)
@ -16353,6 +16398,7 @@ boolean K_IsPickMeUpItem(mobjtype_t type)
case MT_SSMINE: case MT_SSMINE:
case MT_SSMINE_SHIELD: case MT_SSMINE_SHIELD:
case MT_FLOATINGITEM: // Stone Shoe case MT_FLOATINGITEM: // Stone Shoe
case MT_TOXOMISTER_POLE:
return true; return true;
default: default:
return false; return false;
@ -16415,6 +16461,9 @@ static boolean K_PickUp(player_t *player, mobj_t *picked)
else else
type = KITEM_SAD; type = KITEM_SAD;
break; break;
case MT_TOXOMISTER_POLE:
type = KITEM_TOXOMISTER;
break;
default: default:
type = KITEM_SAD; type = KITEM_SAD;
break; break;

View file

@ -46,10 +46,10 @@ Make sure this matches the actual number of states
#define BAIL_MAXCHARGE (84) // tics to bail when in painstate nad in air, on ground is half, if you touch this, also update Obj_BailChargeThink synced animation logic #define BAIL_MAXCHARGE (84) // tics to bail when in painstate nad in air, on ground is half, if you touch this, also update Obj_BailChargeThink synced animation logic
#define BAIL_DROP (FRACUNIT) #define BAIL_DROP (FRACUNIT)
#define BAIL_BOOST (FRACUNIT) #define BAIL_BOOST (6*FRACUNIT/5)
#define BAIL_CREDIT_DEBTRINGS (true) #define BAIL_CREDIT_DEBTRINGS (true)
#define BAIL_DROPFREQUENCY (2) #define BAIL_DROPFREQUENCY (2)
#define BAILSTUN (TICRATE*10) #define BAILSTUN (TICRATE*7)
#define MAXCOMBOTHRUST (mapobjectscale*20) #define MAXCOMBOTHRUST (mapobjectscale*20)
#define MAXCOMBOFLOAT (mapobjectscale*10) #define MAXCOMBOFLOAT (mapobjectscale*10)

View file

@ -475,6 +475,16 @@ boolean Obj_TickStoneShoeChain(mobj_t *chain);
player_t *Obj_StoneShoeOwnerPlayer(mobj_t *shoe); player_t *Obj_StoneShoeOwnerPlayer(mobj_t *shoe);
void Obj_CollideStoneShoe(mobj_t *mover, mobj_t *mobj); void Obj_CollideStoneShoe(mobj_t *mover, mobj_t *mobj);
/* Toxomister */
void Obj_InitToxomisterPole(mobj_t *pole);
boolean Obj_TickToxomisterPole(mobj_t *pole);
boolean Obj_TickToxomisterEye(mobj_t *eye);
boolean Obj_TickToxomisterCloud(mobj_t *cloud);
boolean Obj_ToxomisterPoleCollide(mobj_t *pole, mobj_t *toucher);
boolean Obj_ToxomisterCloudCollide(mobj_t *cloud, mobj_t *toucher);
fixed_t Obj_GetToxomisterCloudDrag(mobj_t *cloud);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View file

@ -71,13 +71,14 @@ void M_MPRoomSelectInit(INT32 choice)
if (modifiedgame) if (modifiedgame)
{ {
M_StartMessage("Server Browser & Add-Ons", M_GetText("You have add-ons loaded.\nYou won't be able to join netgames!\n\nTo play online, restart the game\nand don't load any addons.\n\n\"Dr. Robotnik's Ring Racers\" will\nautomatically add everything\nyou need when you join.\n"), NULL, MM_NOTHING, NULL, NULL); M_StartMessage("Server Browser & Add-Ons", M_GetText("You have add-ons loaded.\nYou won't be able to join netgames!\n\nTo play online, restart the game\nand don't load any addons.\n\n\"Dr. Robotnik's Ring Racers\" will\nautomatically add everything\nyou need when you join.\n"), NULL, MM_NOTHING, NULL, NULL);
return;
} }
// The following behaviour is affected by modifiedgame despite the above restriction. // The following behaviour is affected by modifiedgame despite the above restriction.
// It's a sanity check were that to be removed, wheither by us or by a modified client. // It's a sanity check were that to be removed, wheither by us or by a modified client.
// "wheither"? That typo rules, I'm keeping that ~toast 280823 // "wheither"? That typo rules, I'm keeping that ~toast 280823
// thanks toaster - Tyron 2025-07-02
mpmenu.room = (modifiedgame == true) ? 1 : 0; mpmenu.room = (modifiedgame == true) ? 1 : 0;
mpmenu.ticker = 0; mpmenu.ticker = 0;
mpmenu.servernum = 0; mpmenu.servernum = 0;

View file

@ -67,6 +67,7 @@ target_sources(SRB2SDL2 PRIVATE
stone-shoe.cpp stone-shoe.cpp
exp.c exp.c
bail.c bail.c
toxomister.cpp
) )
add_subdirectory(versus) add_subdirectory(versus)

View file

@ -325,10 +325,10 @@ kill_monitor_part (mobj_t *part)
static inline UINT32 static inline UINT32
restore_item_rng (UINT32 seed) restore_item_rng (UINT32 seed)
{ {
const UINT32 oldseed = P_GetRandSeed(PR_ITEM_ROULETTE); const UINT32 oldseed = P_GetRandSeed(PR_ITEM_SPAWNER);
P_SetRandSeedNet(PR_ITEM_ROULETTE, P_SetRandSeedNet(PR_ITEM_SPAWNER,
P_GetInitSeed(PR_ITEM_ROULETTE), seed); P_GetInitSeed(PR_ITEM_SPAWNER), seed);
return oldseed; return oldseed;
} }
@ -478,7 +478,7 @@ Obj_MonitorSpawnParts (mobj_t *monitor)
P_SetScale(monitor, (monitor->destscale *= 2)); P_SetScale(monitor, (monitor->destscale *= 2));
monitor_itemcount(monitor) = 0; monitor_itemcount(monitor) = 0;
monitor_rngseed(monitor) = P_GetRandSeed(PR_ITEM_ROULETTE); monitor_rngseed(monitor) = P_GetRandSeed(PR_ITEM_SPAWNER);
monitor_spawntic(monitor) = leveltime; monitor_spawntic(monitor) = leveltime;
monitor_emerald(monitor) = 0; monitor_emerald(monitor) = 0;

View file

@ -230,7 +230,7 @@ private:
if (P_IsObjectOnGround(this)) if (P_IsObjectOnGround(this))
{ {
momz = 32 * mapobjectscale; momz = flip(32 * mapobjectscale);
bouncing(true); bouncing(true);
voice(sfx_s3k5f); voice(sfx_s3k5f);
P_StartQuakeFromMobj(5, 40 * scale(), 512 * scale(), this); P_StartQuakeFromMobj(5, 40 * scale(), 512 * scale(), this);
@ -271,6 +271,11 @@ private:
follow()->player->stonedrag = dist > minDist(); follow()->player->stonedrag = dist > minDist();
sprzoff(30 * scale()); sprzoff(30 * scale());
if (is_flipped() != follow()->is_flipped())
{
K_FlipFromObject(this, follow());
}
} }
void move_chain() void move_chain()
@ -292,6 +297,7 @@ private:
while (Mobj::valid(node)) while (Mobj::valid(node))
{ {
node->move_origin({p, pz}); node->move_origin({p, pz});
K_FlipFromObject(node, this);
node->sprzoff(sprzoff()); node->sprzoff(sprzoff());
// Let chain flicker like shoe does // Let chain flicker like shoe does

439
src/objects/toxomister.cpp Normal file
View file

@ -0,0 +1,439 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by James Robert Roman
// Copyright (C) 2025 by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include <algorithm>
#include <array>
#include "objects.hpp"
#include "../core/static_vec.hpp"
#include "../d_player.h"
#include "../doomdef.h"
#include "../doomtype.h"
#include "../g_game.h"
#include "../k_hud.h" // transflag
#include "../m_easing.h"
#include "../m_fixed.h"
#include "../m_random.h"
#include "../r_main.h"
#include "../tables.h"
using namespace srb2::objects;
namespace
{
Fixed distance3d(const Mobj* a, const Mobj* b)
{
return FixedHypot(FixedHypot(a->x - b->x, a->y - b->y), a->z - b->z);
}
Vec2<Fixed> angle_vector(angle_t x)
{
return Vec2<Fixed> {FCOS(x), FSIN(x)};
}
// copied from objects/hyudoro.c
static void
sine_bob
( mobj_t * hyu,
INT32 height,
angle_t a,
fixed_t sineofs)
{
hyu->sprzoff = FixedMul(height * hyu->scale,
sineofs + FINESINE(a >> ANGLETOFINESHIFT)) * P_MobjFlip(hyu);
}
static void
bob_in_place
( mobj_t * hyu,
INT32 height,
INT32 bob_speed)
{
sine_bob(hyu,
height,
(leveltime & (bob_speed - 1)) *
(ANGLE_MAX / bob_speed), -(3*FRACUNIT/4));
}
struct Eye;
struct Pole;
struct Cloud;
struct Eye : Mobj
{
static constexpr INT32 kOrbitRadius = 24;
bool valid() const { return Mobj::valid(owner()) && owner()->health > 0; }
bool tick()
{
if (!valid())
{
remove();
return false;
}
return true;
}
};
struct Pole : Mobj
{
static constexpr sfxenum_t kSound = sfx_s3kdal;
void extravalue1() = delete;
tic_t last_touch0() const { return mobj_t::extravalue1; }
void last_touch0(tic_t n) { mobj_t::extravalue1 = n; }
void extravalue2() = delete;
bool clouds_spawned() const { return mobj_t::extravalue2; }
void clouds_spawned(bool n) { mobj_t::extravalue2 = n; }
void reactiontime() = delete;
tic_t sound_started() const { return mobj_t::reactiontime; }
void sound_started(tic_t n) { mobj_t::reactiontime = n; }
void tracer() = delete;
Eye* eye() const { return Mobj::tracer<Eye>(); }
void eye(Eye* n) { Mobj::tracer(n); }
bool valid() const
{
if (!Mobj::valid(eye()))
return false;
return true;
}
void init()
{
Eye* p_eye = spawn_from<Eye>(MT_TOXOMISTER_EYE);
p_eye->owner(this);
p_eye->spriteyoffset(96*FRACUNIT);
last_touch0(leveltime);
clouds_spawned(false);
eye(p_eye);
flags |= MF_SPECIAL;
}
void spawn_clouds_in_orbit();
bool tick()
{
if (!valid())
{
remove();
return false;
}
if (P_IsObjectOnGround(this))
{
if (!clouds_spawned())
{
spawn_clouds_in_orbit();
clouds_spawned(true);
voice(sfx_s3k9e);
}
if (!voice_playing(kSound))
{
voice(kSound);
sound_started(leveltime);
}
if ((leveltime - sound_started()) % 256 == 0)
voice(kSound);
}
else
{
P_SpawnGhostMobj(this);
}
tick_eye();
return true;
}
void tick_eye()
{
Mobj::PosArg p = {pos2d(), z};
p.x += momx;
p.y += momy;
p.z += momz;
Mobj* targ = find_nearest_eyeball_target();
if (targ)
{
INT32 angle_to_targ = angle_to2d(targ);
Vec2<Fixed> v = angle_vector(angle_to_targ) * Fixed {Eye::kOrbitRadius * mapobjectscale};
p.x += v.x;
p.y += v.y;
eye()->angle = angle_to_targ;
}
eye()->move_origin(p);
}
angle_t angle_to2d(Mobj* mobj) const
{
return R_PointToAngle2(x, y, mobj->x, mobj->y);
}
Mobj* find_nearest_eyeball_target() const
{
srb2::StaticVec<Mobj*, MAXPLAYERS> targets;
for (INT32 i = 0; i < MAXPLAYERS; ++i)
{
if (!playeringame[i])
continue;
if (!players[i].mo)
continue;
targets.push_back(static_cast<Mobj*>(players[i].mo));
}
if (targets.empty())
return nullptr;
return *std::min_element(
targets.begin(),
targets.end(),
[this](Mobj* a, Mobj* b) { return distance3d(this, a) < distance3d(this, b); }
);
}
bool touch(Mobj* toucher)
{
if (touch_cooldown(toucher, 0))
return false;
if (K_TryPickMeUp(this, toucher, false))
return false;
// Adapted from P_XYMovement, MT_JAWZ
voice(info->deathsound);
P_KillMobj(this, NULL, NULL, DMG_NORMAL);
P_SetObjectMomZ(this, 24*FRACUNIT, false);
instathrust(R_PointToAngle2(toucher->x, toucher->y, x, y), 32 * mapobjectscale);
flags &= ~MF_NOGRAVITY;
hitlag(toucher, toucher, 8, true);
return false;
}
bool touch_cooldown
( Mobj* toucher,
UINT8 k)
{
tic_t cooldown = leveltime - last_touch0();
if (toucher == target() && cooldown < 10)
{
last_touch0(leveltime);
return true;
}
return false;
}
};
struct Cloud : Mobj
{
static constexpr INT32 kMaxFuse = 5*TICRATE;
void hnext() = delete;
Mobj* follow() const { return Mobj::hnext<Mobj>(); }
void follow(Mobj* n) { Mobj::hnext(n); }
void tracer() = delete;
Pole* pole() const { return Mobj::tracer<Pole>(); }
void pole(Pole* n) { Mobj::tracer(n); }
Fixed fuse_frac() const { return FRACUNIT - fuse * FRACUNIT / kMaxFuse; }
Fixed drag_var() const { return Easing_Linear(fuse_frac(), FRACUNIT/3, FRACUNIT); }
bool tick()
{
if (Mobj::valid(follow()))
return tick_follow();
return tick_patrol();
}
bool tick_follow()
{
if (!Mobj::valid(follow()))
{
remove();
return false;
}
move_origin(follow()->pos());
momx = 0;
momy = 0;
momz = 0;
bob_in_place(this, 8, 64);
voice_loop(sfx_s3kcfl);
if (leveltime % (TICRATE/3) == 0 && follow()->player->rings > -20) // toxomister ring drain
{
follow()->player->rings--;
S_StartSound(follow()->player->mo, sfx_antiri);
}
if (fuse < 3*TICRATE && leveltime % (1 + fuse / TICRATE) == 0)
{
renderflags ^= RF_DONTDRAW;
}
if (fuse < kMaxFuse && (kMaxFuse - fuse) % 20 == 0 && Mobj::valid(target()) && target()->player && follow()->player)
{
K_SpawnAmps(target()->player, K_PvPAmpReward(3, target()->player, follow()->player), this);
}
follow()->player->stunned = fuse; // stunned as long as cloud is here
return true;
}
bool tick_patrol()
{
if (Mobj::valid(pole()) && pole()->health > 0)
{
move_origin(pole()->pos());
instathrust(angle, 64 * mapobjectscale);
}
else
{
if (!fuse)
{
fuse = 3*TICRATE;
instathrust(angle, 2 * mapobjectscale);
}
if (leveltime & 1)
{
renderflags ^= RF_DONTDRAW;
}
}
return true;
}
bool touch(Mobj* toucher)
{
if (toucher == target())
return false;
if (toucher->player)
{
if (this == toucher->player->toxomisterCloud) // already attached
return true;
if (!P_MobjWasRemoved(toucher->player->toxomisterCloud))
{
toucher->player->pflags |= PF_CASTSHADOW;
return true;
}
P_SetTarget(&toucher->player->toxomisterCloud, this);
}
toucher->hitlag(8);
scale_to(destscale);
follow(toucher);
fuse = kMaxFuse;
renderflags &= ~RF_DONTDRAW;
voice(sfx_s3k8a);
return true;
}
};
void Pole::spawn_clouds_in_orbit()
{
constexpr INT32 kNumClouds = 6;
std::array<UINT32, kNumClouds> weights;
std::array<INT32, kNumClouds> order;
angle_t a = 0;
angle_t a_incr = ANGLE_MAX / kNumClouds;
for (INT32 i = 0; i < kNumClouds; ++i)
{
weights[i] = P_Random(PR_TRACKHAZARD);
order[i] = i;
}
std::stable_sort(order.begin(), order.end(), [&](INT32 a, INT32 b) { return weights[a] < weights[b]; });
for (INT32 i : order)
{
Cloud* cloud = spawn_from<Cloud>({}, MT_TOXOMISTER_CLOUD);
cloud->pole(this);
cloud->angle = a;
cloud->target(target());
cloud->spriteyoffset(24*FRACUNIT);
cloud->hitlag(2 + i * 4);
cloud->scale_between(1, cloud->scale(), cloud->scale() / 5);
a += a_incr;
}
}
}; // namespace
void Obj_InitToxomisterPole(mobj_t *pole)
{
static_cast<Pole*>(pole)->init();
}
boolean Obj_TickToxomisterPole(mobj_t *pole)
{
return static_cast<Pole*>(pole)->tick();
}
boolean Obj_TickToxomisterEye(mobj_t *eye)
{
return static_cast<Eye*>(eye)->tick();
}
boolean Obj_TickToxomisterCloud(mobj_t *cloud)
{
return static_cast<Cloud*>(cloud)->tick();
}
boolean Obj_ToxomisterPoleCollide(mobj_t *pole, mobj_t *toucher)
{
return static_cast<Pole*>(pole)->touch(static_cast<Mobj*>(toucher));
}
boolean Obj_ToxomisterCloudCollide(mobj_t *cloud, mobj_t *toucher)
{
return static_cast<Cloud*>(cloud)->touch(static_cast<Mobj*>(toucher));
}
fixed_t Obj_GetToxomisterCloudDrag(mobj_t *cloud)
{
return static_cast<Cloud*>(cloud)->drag_var();
}

View file

@ -1129,6 +1129,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
Obj_CollideStoneShoe(toucher, special); Obj_CollideStoneShoe(toucher, special);
return; return;
case MT_TOXOMISTER_POLE:
Obj_ToxomisterPoleCollide(special, toucher);
return;
case MT_TOXOMISTER_CLOUD:
Obj_ToxomisterCloudCollide(special, toucher);
return;
default: // SOC or script pickup default: // SOC or script pickup
P_SetTarget(&special->target, toucher); P_SetTarget(&special->target, toucher);
break; break;

View file

@ -1025,6 +1025,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
|| g_tm.thing->type == MT_MONITOR || g_tm.thing->type == MT_MONITOR
|| g_tm.thing->type == MT_BATTLECAPSULE || g_tm.thing->type == MT_BATTLECAPSULE
|| g_tm.thing->type == MT_KART_LEFTOVER || g_tm.thing->type == MT_KART_LEFTOVER
|| g_tm.thing->type == MT_TOXOMISTER_POLE
|| (g_tm.thing->type == MT_PLAYER))) || (g_tm.thing->type == MT_PLAYER)))
{ {
// see if it went over / under // see if it went over / under
@ -1043,6 +1044,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
|| thing->type == MT_MONITOR || thing->type == MT_MONITOR
|| thing->type == MT_BATTLECAPSULE || thing->type == MT_BATTLECAPSULE
|| thing->type == MT_KART_LEFTOVER || thing->type == MT_KART_LEFTOVER
|| thing->type == MT_TOXOMISTER_POLE
|| (thing->type == MT_PLAYER))) || (thing->type == MT_PLAYER)))
{ {
// see if it went over / under // see if it went over / under

View file

@ -1095,7 +1095,7 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
wasflip = (mo->eflags & MFE_VERTICALFLIP) != 0; wasflip = (mo->eflags & MFE_VERTICALFLIP) != 0;
if (mo->type != MT_SPINFIRE) if (mo->type != MT_SPINFIRE && mo->type != MT_STONESHOE)
mo->eflags &= ~MFE_VERTICALFLIP; mo->eflags &= ~MFE_VERTICALFLIP;
if (mo->subsector->sector->ffloors) // Check for 3D floor gravity too. if (mo->subsector->sector->ffloors) // Check for 3D floor gravity too.
@ -1248,6 +1248,10 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
case MT_GACHABOM: case MT_GACHABOM:
gravityadd = (5*gravityadd)/2; gravityadd = (5*gravityadd)/2;
break; break;
case MT_TOXOMISTER_POLE:
if (mo->health > 0)
gravityadd = (5*gravityadd)/2;
break;
case MT_BANANA: case MT_BANANA:
case MT_BALLHOG: case MT_BALLHOG:
case MT_BALLHOG_RETICULE_TEST: case MT_BALLHOG_RETICULE_TEST:
@ -1303,7 +1307,7 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
gravityadd *= 2; gravityadd *= 2;
break; break;
case MT_STONESHOE: case MT_STONESHOE:
gravityadd *= 4; gravityadd = -4 * abs(gravityadd) * P_MobjFlip(mo);
break; break;
default: default:
break; break;
@ -2337,6 +2341,7 @@ boolean P_ZMovement(mobj_t *mo)
case MT_BIGTUMBLEWEED: case MT_BIGTUMBLEWEED:
case MT_LITTLETUMBLEWEED: case MT_LITTLETUMBLEWEED:
case MT_EMERALD: case MT_EMERALD:
case MT_TOXOMISTER_POLE:
if (!(mo->flags & MF_NOCLIPHEIGHT) && P_CheckDeathPitCollide(mo)) if (!(mo->flags & MF_NOCLIPHEIGHT) && P_CheckDeathPitCollide(mo))
{ {
P_RemoveMobj(mo); P_RemoveMobj(mo);
@ -5320,6 +5325,7 @@ boolean P_IsKartItem(INT32 type)
case MT_HYUDORO: case MT_HYUDORO:
case MT_SINK: case MT_SINK:
case MT_GACHABOM: case MT_GACHABOM:
case MT_TOXOMISTER_POLE:
return true; return true;
default: default:
@ -5346,6 +5352,7 @@ boolean P_IsKartFieldItem(INT32 type)
case MT_DROPTARGET: case MT_DROPTARGET:
case MT_DUELBOMB: case MT_DUELBOMB:
case MT_GACHABOM: case MT_GACHABOM:
case MT_TOXOMISTER_POLE:
return true; return true;
default: default:
@ -5379,6 +5386,8 @@ boolean P_IsRelinkItem(INT32 type)
case MT_HYUDORO_CENTER: case MT_HYUDORO_CENTER:
case MT_SINK: case MT_SINK:
case MT_GACHABOM: case MT_GACHABOM:
case MT_TOXOMISTER_POLE:
case MT_FLOATINGITEM: // Stone Shoe Trap
return true; return true;
default: default:
@ -6863,6 +6872,12 @@ static boolean P_MobjDeadThink(mobj_t *mobj)
P_SetMobjState(mobj, mobj->info->xdeathstate); P_SetMobjState(mobj, mobj->info->xdeathstate);
/* FALLTHRU */ /* FALLTHRU */
case MT_JAWZ_SHIELD: case MT_JAWZ_SHIELD:
mobj->renderflags ^= RF_DONTDRAW;
break;
case MT_TOXOMISTER_POLE:
if (mobj->momz == 0 && P_IsObjectOnGround(mobj))
P_SetMobjState(mobj, mobj->info->xdeathstate);
mobj->renderflags ^= RF_DONTDRAW; mobj->renderflags ^= RF_DONTDRAW;
break; break;
case MT_SSMINE: case MT_SSMINE:
@ -10295,6 +10310,15 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
case MT_STONESHOE: case MT_STONESHOE:
return Obj_TickStoneShoe(mobj); return Obj_TickStoneShoe(mobj);
case MT_TOXOMISTER_POLE:
return Obj_TickToxomisterPole(mobj);
case MT_TOXOMISTER_EYE:
return Obj_TickToxomisterEye(mobj);
case MT_TOXOMISTER_CLOUD:
return Obj_TickToxomisterCloud(mobj);
default: default:
// check mobj against possible water content, before movement code // check mobj against possible water content, before movement code
P_MobjCheckWater(mobj); P_MobjCheckWater(mobj);
@ -11159,6 +11183,9 @@ static void P_DefaultMobjShadowScale(mobj_t *thing)
case MT_STONESHOE_CHAIN: case MT_STONESHOE_CHAIN:
thing->shadowscale = FRACUNIT/5; thing->shadowscale = FRACUNIT/5;
break; break;
case MT_TOXOMISTER_POLE:
thing->shadowscale = FRACUNIT;
break;
default: default:
if (thing->flags & (MF_ENEMY|MF_BOSS)) if (thing->flags & (MF_ENEMY|MF_BOSS))
thing->shadowscale = FRACUNIT; thing->shadowscale = FRACUNIT;
@ -12978,12 +13005,12 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
// spawn all the duel mode objects itself, which ends up // spawn all the duel mode objects itself, which ends up
// calling this function again. // calling this function again.
// So that's why this check is even here. // So that's why this check is even here.
if (inDuel == false && (grandprixinfo.gp == false || grandprixinfo.eventmode != GPEVENT_BONUS)) if (inDuel == false && (grandprixinfo.gp == false || grandprixinfo.eventmode != GPEVENT_BONUS) && gametype != GT_TUTORIAL)
{ {
if (K_IsDuelItem(i) == true if (K_IsDuelItem(i) == true
&& K_DuelItemAlwaysSpawns(mthing) == false) && K_DuelItemAlwaysSpawns(mthing) == false)
{ {
// Only spawns in Duels or GP bonus rounds. // Only spawns in Duels, GP bonus rounds or Tutorials.
return false; return false;
} }
} }
@ -15487,6 +15514,7 @@ boolean P_MobjCanChangeFlip(mobj_t *mobj)
case MT_SHRINK_CHAIN: case MT_SHRINK_CHAIN:
case MT_SHRINK_LASER: case MT_SHRINK_LASER:
case MT_SHRINK_PARTICLE: case MT_SHRINK_PARTICLE:
case MT_STONESHOE:
return false; return false;
default: default:

View file

@ -93,6 +93,7 @@ typedef enum
BALLHOGRETICULE = 0x8000, BALLHOGRETICULE = 0x8000,
STONESHOE = 0x10000, STONESHOE = 0x10000,
FLYBOT = 0x20000, FLYBOT = 0x20000,
TOXOMISTERCLOUD = 0x40000,
} player_saveflags; } player_saveflags;
static inline void P_ArchivePlayer(savebuffer_t *save) static inline void P_ArchivePlayer(savebuffer_t *save)
@ -368,6 +369,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (players[i].stoneShoe) if (players[i].stoneShoe)
flags |= STONESHOE; flags |= STONESHOE;
if (players[i].toxomisterCloud)
flags |= TOXOMISTERCLOUD;
if (players[i].flybot) if (players[i].flybot)
flags |= FLYBOT; flags |= FLYBOT;
@ -421,6 +425,9 @@ static void P_NetArchivePlayers(savebuffer_t *save)
if (flags & STONESHOE) if (flags & STONESHOE)
WRITEUINT32(save->p, players[i].stoneShoe->mobjnum); WRITEUINT32(save->p, players[i].stoneShoe->mobjnum);
if (flags & TOXOMISTERCLOUD)
WRITEUINT32(save->p, players[i].toxomisterCloud->mobjnum);
if (flags & FLYBOT) if (flags & FLYBOT)
WRITEUINT32(save->p, players[i].flybot->mobjnum); WRITEUINT32(save->p, players[i].flybot->mobjnum);
@ -1082,6 +1089,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
if (flags & STONESHOE) if (flags & STONESHOE)
players[i].stoneShoe = (mobj_t *)(size_t)READUINT32(save->p); players[i].stoneShoe = (mobj_t *)(size_t)READUINT32(save->p);
if (flags & TOXOMISTERCLOUD)
players[i].toxomisterCloud = (mobj_t *)(size_t)READUINT32(save->p);
if (flags & FLYBOT) if (flags & FLYBOT)
players[i].flybot = (mobj_t *)(size_t)READUINT32(save->p); players[i].flybot = (mobj_t *)(size_t)READUINT32(save->p);
@ -6247,6 +6257,11 @@ static void P_RelinkPointers(void)
if (!RelinkMobj(&players[i].stoneShoe)) if (!RelinkMobj(&players[i].stoneShoe))
CONS_Debug(DBG_GAMELOGIC, "stoneShoe not found on player %d\n", i); CONS_Debug(DBG_GAMELOGIC, "stoneShoe not found on player %d\n", i);
} }
if (players[i].toxomisterCloud)
{
if (!RelinkMobj(&players[i].toxomisterCloud))
CONS_Debug(DBG_GAMELOGIC, "toxomisterCloud not found on player %d\n", i);
}
if (players[i].flybot) if (players[i].flybot)
{ {
if (!RelinkMobj(&players[i].flybot)) if (!RelinkMobj(&players[i].flybot))

View file

@ -8434,6 +8434,14 @@ void P_FreeLevelState(void)
HWR_ClearAllTextures(); HWR_ClearAllTextures();
#endif #endif
if (rendermode == render_soft)
{
// Queued draws might reference patches or colormaps about to be freed.
// Flush 2D to make sure no read-after-free occurs.
srb2::rhi::Rhi* rhi = srb2::sys::get_rhi(srb2::sys::g_current_rhi);
srb2::sys::main_hardware_state()->twodee_renderer->flush(*rhi, srb2::g_2d);
}
G_FreeGhosts(); // ghosts are allocated with PU_LEVEL G_FreeGhosts(); // ghosts are allocated with PU_LEVEL
Patch_FreeTag(PU_PATCH_LOWPRIORITY); Patch_FreeTag(PU_PATCH_LOWPRIORITY);
Patch_FreeTag(PU_PATCH_ROTATED); Patch_FreeTag(PU_PATCH_ROTATED);

View file

@ -4259,6 +4259,7 @@ void P_PlayerThink(player_t *player)
PlayerPointerErase(player->ballhogreticule); PlayerPointerErase(player->ballhogreticule);
PlayerPointerErase(player->flickyAttacker); PlayerPointerErase(player->flickyAttacker);
PlayerPointerErase(player->stoneShoe); PlayerPointerErase(player->stoneShoe);
PlayerPointerErase(player->toxomisterCloud);
PlayerPointerErase(player->powerup.flickyController); PlayerPointerErase(player->powerup.flickyController);
PlayerPointerErase(player->powerup.barrier); PlayerPointerErase(player->powerup.barrier);