Merge remote-tracking branch 'origin/master' into wall-transfer-everything

This commit is contained in:
James R 2022-09-23 23:40:37 -07:00
commit fe6bfd1ff2
28 changed files with 1916 additions and 912 deletions

View file

@ -428,6 +428,9 @@ typedef struct player_s
UINT8 driftboost; // (0 to 125) - Boost you get from drifting
UINT8 strongdriftboost; // (0 to 125) - While active, boost from drifting gives a stronger speed increase
UINT16 gateBoost; // Juicebox Manta Ring boosts
UINT8 gateSound; // Sound effect combo
SINT8 aizdriftstrat; // (-1 to 1) - Let go of your drift while boosting? Helper for the SICK STRATZ (sliptiding!) you have just unlocked
INT32 aizdrifttilt;
INT32 aizdriftturn;

View file

@ -322,7 +322,6 @@ actionpointer_t actionpointers[] =
{{A_ItemPop}, "A_ITEMPOP"},
{{A_JawzChase}, "A_JAWZCHASE"},
{{A_JawzExplode}, "A_JAWZEXPLODE"},
{{A_SPBChase}, "A_SPBCHASE"},
{{A_SSMineSearch}, "A_SSMINESEARCH"},
{{A_SSMineExplode}, "A_SSMINEEXPLODE"},
{{A_LandMineExplode}, "A_LANDMINEEXPLODE"},
@ -3652,6 +3651,10 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
"S_SPB20",
"S_SPB_DEAD",
// Juicebox for SPB
"S_MANTA1",
"S_MANTA2",
// Lightning Shield
"S_LIGHTNINGSHIELD1",
"S_LIGHTNINGSHIELD2",
@ -5356,6 +5359,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_SPB", // Self-Propelled Bomb
"MT_SPBEXPLOSION",
"MT_MANTARING", // Juicebox for SPB
"MT_LIGHTNINGSHIELD", // Shields
"MT_BUBBLESHIELD",

View file

@ -707,7 +707,7 @@ extern boolean comeback;
extern SINT8 battlewanted[4];
extern tic_t wantedcalcdelay;
extern tic_t indirectitemcooldown;
extern tic_t itemCooldowns[NUMKARTITEMS - 1];
extern tic_t mapreset;
extern boolean thwompsactive;
extern UINT8 lastLowestLap;

View file

@ -317,7 +317,7 @@ SINT8 pickedvote; // What vote the host rolls
// Server-sided, synched variables
SINT8 battlewanted[4]; // WANTED players in battle, worth x2 points
tic_t wantedcalcdelay; // Time before it recalculates WANTED
tic_t indirectitemcooldown; // Cooldown before any more Shrink, SPB, or any other item that works indirectly is awarded
tic_t itemCooldowns[NUMKARTITEMS - 1]; // Cooldowns to prevent item spawning
tic_t mapreset; // Map reset delay when enough players have joined an empty game
boolean thwompsactive; // Thwomps activate on lap 2
UINT8 lastLowestLap; // Last lowest lap, for activating race lap executors

View file

@ -565,6 +565,8 @@ char sprnames[NUMSPRITES + 1][5] =
"BHOG", // Ballhog
"BHBM", // Ballhog BOOM
"SPBM", // Self-Propelled Bomb
"TRIS", // SPB Manta Ring start
"TRNQ", // SPB Manta Ring loop
"THNS", // Lightning Shield
"BUBS", // Bubble Shield (not Bubs)
"BWVE", // Bubble Shield waves
@ -4199,28 +4201,31 @@ state_t states[NUMSTATES] =
{SPR_BHBM, FF_FULLBRIGHT|14, 1, {NULL}, 0, 0, S_BALLHOGBOOM16}, // S_BALLHOGBOOM15
{SPR_BHBM, FF_FULLBRIGHT|15, 1, {NULL}, 0, 0, S_NULL}, // S_BALLHOGBOOM16
{SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB2}, // S_SPB1
{SPR_SPBM, 1, 1, {A_SPBChase}, 0, 0, S_SPB3}, // S_SPB2
{SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB4}, // S_SPB3
{SPR_SPBM, 2, 1, {A_SPBChase}, 0, 0, S_SPB5}, // S_SPB4
{SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB6}, // S_SPB5
{SPR_SPBM, 3, 1, {A_SPBChase}, 0, 0, S_SPB7}, // S_SPB6
{SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB8}, // S_SPB7
{SPR_SPBM, 4, 1, {A_SPBChase}, 0, 0, S_SPB9}, // S_SPB8
{SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB10}, // S_SPB9
{SPR_SPBM, 5, 1, {A_SPBChase}, 0, 0, S_SPB11}, // S_SPB10
{SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB12}, // S_SPB11
{SPR_SPBM, 6, 1, {A_SPBChase}, 0, 0, S_SPB13}, // S_SPB12
{SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB14}, // S_SPB13
{SPR_SPBM, 7, 1, {A_SPBChase}, 0, 0, S_SPB15}, // S_SPB14
{SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB16}, // S_SPB15
{SPR_SPBM, 8, 1, {A_SPBChase}, 0, 0, S_SPB17}, // S_SPB16
{SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB18}, // S_SPB17
{SPR_SPBM, 8, 1, {A_SPBChase}, 0, 0, S_SPB19}, // S_SPB18
{SPR_SPBM, 0, 1, {A_SPBChase}, 0, 0, S_SPB20}, // S_SPB19
{SPR_SPBM, 8, 1, {A_SPBChase}, 0, 0, S_SPB1}, // S_SPB20
{SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB2}, // S_SPB1
{SPR_SPBM, 1, 1, {NULL}, 0, 0, S_SPB3}, // S_SPB2
{SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB4}, // S_SPB3
{SPR_SPBM, 2, 1, {NULL}, 0, 0, S_SPB5}, // S_SPB4
{SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB6}, // S_SPB5
{SPR_SPBM, 3, 1, {NULL}, 0, 0, S_SPB7}, // S_SPB6
{SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB8}, // S_SPB7
{SPR_SPBM, 4, 1, {NULL}, 0, 0, S_SPB9}, // S_SPB8
{SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB10}, // S_SPB9
{SPR_SPBM, 5, 1, {NULL}, 0, 0, S_SPB11}, // S_SPB10
{SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB12}, // S_SPB11
{SPR_SPBM, 6, 1, {NULL}, 0, 0, S_SPB13}, // S_SPB12
{SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB14}, // S_SPB13
{SPR_SPBM, 7, 1, {NULL}, 0, 0, S_SPB15}, // S_SPB14
{SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB16}, // S_SPB15
{SPR_SPBM, 8, 1, {NULL}, 0, 0, S_SPB17}, // S_SPB16
{SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB18}, // S_SPB17
{SPR_SPBM, 8, 1, {NULL}, 0, 0, S_SPB19}, // S_SPB18
{SPR_SPBM, 0, 1, {NULL}, 0, 0, S_SPB20}, // S_SPB19
{SPR_SPBM, 8, 1, {NULL}, 0, 0, S_SPB1}, // S_SPB20
{SPR_SPBM, 8, 175, {NULL}, 0, 0, S_NULL}, // S_SPB_DEAD
{SPR_TRIS, FF_FULLBRIGHT|FF_ANIMATE|FF_PAPERSPRITE|FF_ADD, 9, {NULL}, 2, 3, S_MANTA2}, // S_MANTA1
{SPR_TRNQ, FF_FULLBRIGHT|FF_ANIMATE|FF_PAPERSPRITE|FF_ADD, -1, {NULL}, 7, 1, S_NULL}, // S_MANTA2
{SPR_THNS, FF_FULLBRIGHT|9, 2, {NULL}, 0, 0, S_LIGHTNINGSHIELD2}, // S_LIGHTNINGSHIELD1
{SPR_THNS, FF_FULLBRIGHT|10, 2, {NULL}, 0, 0, S_LIGHTNINGSHIELD3}, // S_LIGHTNINGSHIELD2
{SPR_THNS, FF_FULLBRIGHT|11, 2, {NULL}, 0, 0, S_LIGHTNINGSHIELD4}, // S_LIGHTNINGSHIELD3
@ -23906,6 +23911,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_NULL // raisestate
},
{ // MT_MANTARING
-1, // doomednum
S_MANTA1, // 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
64*FRACUNIT, // radius
64*FRACUNIT, // height
0, // display offset
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_LIGHTNINGSHIELD
-1, // doomednum
S_LIGHTNINGSHIELD1, // spawnstate
@ -28704,13 +28736,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
16<<FRACBITS, // radius
16<<FRACBITS, // radius
32<<FRACBITS, // height
1, // display offset
100, // mass
DMG_NORMAL, // mass
0, // damage
sfx_None, // activesound
MF_DONTENCOREMAP|MF_NOGRAVITY|MF_PAIN, // flags
MF_NOGRAVITY|MF_PAIN|MF_NOHITLAGFORME|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},

View file

@ -275,7 +275,6 @@ enum actionnum
A_ITEMPOP,
A_JAWZCHASE,
A_JAWZEXPLODE,
A_SPBCHASE,
A_SSMINESEARCH,
A_SSMINEEXPLODE,
A_LANDMINEEXPLODE,
@ -548,7 +547,6 @@ void A_ChangeHeight();
void A_ItemPop();
void A_JawzChase();
void A_JawzExplode();
void A_SPBChase();
void A_SSMineSearch();
void A_SSMineExplode();
void A_LandMineExplode();
@ -1113,6 +1111,8 @@ typedef enum sprite
SPR_BHOG, // Ballhog
SPR_BHBM, // Ballhog BOOM
SPR_SPBM, // Self-Propelled Bomb
SPR_TRIS, // SPB Manta Ring start
SPR_TRNQ, // SPB Manta Ring loop
SPR_THNS, // Thunder Shield
SPR_BUBS, // Bubble Shield (not Bubs)
SPR_BWVE, // Bubble Shield waves
@ -4650,6 +4650,10 @@ typedef enum state
S_SPB20,
S_SPB_DEAD,
// Juicebox for SPB
S_MANTA1,
S_MANTA2,
// Thunder Shield
S_LIGHTNINGSHIELD1,
S_LIGHTNINGSHIELD2,
@ -6390,6 +6394,7 @@ typedef enum mobj_type
MT_SPB, // SPB stuff
MT_SPBEXPLOSION,
MT_MANTARING, // Juicebox for SPB
MT_LIGHTNINGSHIELD, // Shields
MT_BUBBLESHIELD,

View file

@ -4445,7 +4445,6 @@ static void K_drawDistributionDebugger(void)
kp_jawz[1],
kp_mine[1],
kp_landmine[1],
kp_droptarget[1],
kp_ballhog[1],
kp_selfpropelledbomb[1],
kp_grow[1],
@ -4457,6 +4456,7 @@ static void K_drawDistributionDebugger(void)
kp_pogospring[1],
kp_superring[1],
kp_kitchensink[1],
kp_droptarget[1],
kp_sneaker[1],
kp_sneaker[1],
@ -4471,11 +4471,17 @@ static void K_drawDistributionDebugger(void)
UINT32 pdis = 0;
INT32 i;
INT32 x = -9, y = -9;
boolean spbrush = false;
if (stplyr != &players[displayplayers[0]]) // only for p1
return;
if (K_ForcedSPB(stplyr) == true)
{
V_DrawScaledPatch(x, y, V_SNAPTOTOP, items[KITEM_SPB]);
V_DrawThinString(x+11, y+31, V_ALLOWLOWERCASE|V_SNAPTOTOP, "EX");
return;
}
// The only code duplication from the Kart, just to avoid the actual item function from calculating pingame twice
for (i = 0; i < MAXPLAYERS; i++)
{
@ -4498,14 +4504,7 @@ static void K_drawDistributionDebugger(void)
}
}
if (spbplace != -1 && stplyr->position == spbplace+1)
{
// SPB Rush Mode: It's 2nd place's job to catch-up items and make 1st place's job hell
pdis = (3 * pdis) / 2;
spbrush = true;
}
pdis = K_ScaleItemDistance(pdis, pingame, spbrush);
pdis = K_ScaleItemDistance(pdis, pingame);
if (stplyr->bot && stplyr->botvars.rival)
{
@ -4513,7 +4512,7 @@ static void K_drawDistributionDebugger(void)
pdis = (15 * pdis) / 14;
}
useodds = K_FindUseodds(stplyr, 0, pdis, bestbumper, spbrush);
useodds = K_FindUseodds(stplyr, 0, pdis, bestbumper);
for (i = 1; i < NUMKARTRESULTS; i++)
{
@ -4521,38 +4520,21 @@ static void K_drawDistributionDebugger(void)
useodds, i,
stplyr->distancetofinish,
0,
spbrush, stplyr->bot, (stplyr->bot && stplyr->botvars.rival)
stplyr->bot, (stplyr->bot && stplyr->botvars.rival)
);
INT32 amount = 1;
if (itemodds <= 0)
continue;
V_DrawScaledPatch(x, y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP, items[i]);
V_DrawThinString(x+11, y+31, V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP, va("%d", itemodds));
V_DrawScaledPatch(x, y, V_SNAPTOTOP, items[i]);
V_DrawThinString(x+11, y+31, V_SNAPTOTOP, va("%d", itemodds));
// Display amount for multi-items
if (i >= NUMKARTITEMS)
amount = K_ItemResultToAmount(i);
if (amount > 1)
{
INT32 amount;
switch (i)
{
case KRITEM_TENFOLDBANANA:
amount = 10;
break;
case KRITEM_QUADORBINAUT:
amount = 4;
break;
case KRITEM_DUALJAWZ:
amount = 2;
break;
case KRITEM_DUALSNEAKER:
amount = 2;
break;
default:
amount = 3;
break;
}
V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP, va("x%d", amount));
V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("x%d", amount));
}
x += 32;
@ -4563,7 +4545,7 @@ static void K_drawDistributionDebugger(void)
}
}
V_DrawString(0, 0, V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP, va("USEODDS %d", useodds));
V_DrawString(0, 0, V_SNAPTOTOP, va("USEODDS %d", useodds));
}
static void K_drawCheckpointDebugger(void)

View file

@ -47,7 +47,6 @@
// encoremode is Encore Mode (duh), bool
// comeback is Battle Mode's karma comeback, also bool
// battlewanted is an array of the WANTED player nums, -1 for no player in that slot
// indirectitemcooldown is timer before anyone's allowed another Shrink/SPB
// mapreset is set when enough players fill an empty server
void K_TimerReset(void)
@ -326,7 +325,6 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] =
&cv_jawz,
&cv_mine,
&cv_landmine,
&cv_droptarget,
&cv_ballhog,
&cv_selfpropelledbomb,
&cv_grow,
@ -338,6 +336,7 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] =
&cv_pogospring,
&cv_superring,
&cv_kitchensink,
&cv_droptarget,
&cv_dualsneaker,
&cv_triplesneaker,
&cv_triplebanana,
@ -363,7 +362,7 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] =
/*Mine*/ { 0, 3, 3, 1, 0, 0, 0, 0 }, // Mine
/*Land Mine*/ { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine
/*Ballhog*/ { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog
/*Self-Propelled Bomb*/ { 0, 0, 0, 0, 0, 2, 4, 0 }, // Self-Propelled Bomb
/*Self-Propelled Bomb*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Self-Propelled Bomb
/*Grow*/ { 0, 0, 0, 1, 2, 3, 0, 0 }, // Grow
/*Shrink*/ { 0, 0, 0, 0, 0, 1, 3, 2 }, // Shrink
/*Lightning Shield*/ { 1, 0, 0, 0, 0, 0, 0, 0 }, // Lightning Shield
@ -417,8 +416,8 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] =
};
#define DISTVAR (2048) // Magic number distance for use with item roulette tiers
#define SPBSTARTDIST (5*DISTVAR) // Distance when SPB is forced onto 2nd place
#define SPBFORCEDIST (15*DISTVAR) // Distance when SPB is forced onto 2nd place
#define SPBSTARTDIST (6*DISTVAR) // Distance when SPB can start appearing
#define SPBFORCEDIST (12*DISTVAR) // Distance when SPB is forced onto the next person who rolls an item
#define ENDDIST (12*DISTVAR) // Distance when the game stops giving you bananas
// Array of states to pick the starting point of the animation, based on the actual time left for invincibility.
@ -448,6 +447,111 @@ INT32 K_GetShieldFromItem(INT32 item)
}
}
SINT8 K_ItemResultToType(SINT8 getitem)
{
if (getitem <= 0 || getitem >= NUMKARTRESULTS) // Sad (Fallback)
{
if (getitem != 0)
{
CONS_Printf("ERROR: K_GetItemResultToItemType - Item roulette gave bad item (%d) :(\n", getitem);
}
return KITEM_SAD;
}
if (getitem >= NUMKARTITEMS)
{
switch (getitem)
{
case KRITEM_DUALSNEAKER:
case KRITEM_TRIPLESNEAKER:
return KITEM_SNEAKER;
case KRITEM_TRIPLEBANANA:
case KRITEM_TENFOLDBANANA:
return KITEM_BANANA;
case KRITEM_TRIPLEORBINAUT:
case KRITEM_QUADORBINAUT:
return KITEM_ORBINAUT;
case KRITEM_DUALJAWZ:
return KITEM_JAWZ;
default:
I_Error("Bad item cooldown redirect for result %d\n", getitem);
break;
}
}
return getitem;
}
UINT8 K_ItemResultToAmount(SINT8 getitem)
{
switch (getitem)
{
case KRITEM_DUALSNEAKER:
case KRITEM_DUALJAWZ:
return 2;
case KRITEM_TRIPLESNEAKER:
case KRITEM_TRIPLEBANANA:
case KRITEM_TRIPLEORBINAUT:
return 3;
case KRITEM_QUADORBINAUT:
return 4;
case KITEM_BALLHOG: // Not a special result, but has a special amount
return 5;
case KRITEM_TENFOLDBANANA:
return 10;
default:
return 1;
}
}
tic_t K_GetItemCooldown(SINT8 itemResult)
{
SINT8 itemType = K_ItemResultToType(itemResult);
if (itemType < 1 || itemType >= NUMKARTITEMS)
{
return 0;
}
return itemCooldowns[itemType - 1];
}
void K_SetItemCooldown(SINT8 itemResult, tic_t time)
{
SINT8 itemType = K_ItemResultToType(itemResult);
if (itemType < 1 || itemType >= NUMKARTITEMS)
{
return;
}
itemCooldowns[itemType - 1] = max(itemCooldowns[itemType - 1], time);
}
void K_RunItemCooldowns(void)
{
size_t i;
for (i = 0; i < NUMKARTITEMS-1; i++)
{
if (itemCooldowns[i] > 0)
{
itemCooldowns[i]--;
}
}
}
/** \brief Item Roulette for Kart
\param player player
@ -457,70 +561,31 @@ INT32 K_GetShieldFromItem(INT32 item)
*/
static void K_KartGetItemResult(player_t *player, SINT8 getitem)
{
if (getitem == KITEM_SPB || getitem == KITEM_SHRINK) // Indirect items
indirectitemcooldown = 20*TICRATE;
if (getitem == KITEM_SPB || getitem == KITEM_SHRINK)
{
K_SetItemCooldown(getitem, 20*TICRATE);
}
player->botvars.itemdelay = TICRATE;
player->botvars.itemconfirm = 0;
switch (getitem)
{
// Special roulettes first, then the generic ones are handled by default
case KRITEM_DUALSNEAKER: // Sneaker x2
player->itemtype = KITEM_SNEAKER;
player->itemamount = 2;
break;
case KRITEM_TRIPLESNEAKER: // Sneaker x3
player->itemtype = KITEM_SNEAKER;
player->itemamount = 3;
break;
case KRITEM_TRIPLEBANANA: // Banana x3
player->itemtype = KITEM_BANANA;
player->itemamount = 3;
break;
case KRITEM_TENFOLDBANANA: // Banana x10
player->itemtype = KITEM_BANANA;
player->itemamount = 10;
break;
case KRITEM_TRIPLEORBINAUT: // Orbinaut x3
player->itemtype = KITEM_ORBINAUT;
player->itemamount = 3;
break;
case KRITEM_QUADORBINAUT: // Orbinaut x4
player->itemtype = KITEM_ORBINAUT;
player->itemamount = 4;
break;
case KRITEM_DUALJAWZ: // Jawz x2
player->itemtype = KITEM_JAWZ;
player->itemamount = 2;
break;
case KITEM_BALLHOG: // Ballhog x5
player->itemtype = KITEM_BALLHOG;
player->itemamount = 5;
break;
default:
if (getitem <= 0 || getitem >= NUMKARTRESULTS) // Sad (Fallback)
{
if (getitem != 0)
CONS_Printf("ERROR: P_KartGetItemResult - Item roulette gave bad item (%d) :(\n", getitem);
player->itemtype = KITEM_SAD;
}
else
player->itemtype = getitem;
player->itemamount = 1;
break;
}
player->itemtype = K_ItemResultToType(getitem);
player->itemamount = K_ItemResultToAmount(getitem);
}
fixed_t K_ItemOddsScale(UINT8 numPlayers, boolean spbrush)
fixed_t K_ItemOddsScale(UINT8 playerCount)
{
const UINT8 basePlayer = 8; // The player count we design most of the game around.
UINT8 playerCount = (spbrush ? 2 : numPlayers);
fixed_t playerScaling = 0;
if (playerCount < 2)
{
// Cap to 1v1 scaling
playerCount = 2;
}
// Then, it multiplies it further if the player count isn't equal to basePlayer.
// This is done to make low player count races more interesting and high player count rates more fair.
// (If you're in SPB mode and in 2nd place, it acts like it's a 1v1, so the catch-up game is not weakened.)
if (playerCount < basePlayer)
{
// Less than basePlayer: increase odds significantly.
@ -537,12 +602,12 @@ fixed_t K_ItemOddsScale(UINT8 numPlayers, boolean spbrush)
return playerScaling;
}
UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush)
UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers)
{
if (mapobjectscale != FRACUNIT)
{
// Bring back to normal scale.
distance = FixedDiv(distance * FRACUNIT, mapobjectscale) / FRACUNIT;
distance = FixedDiv(distance, mapobjectscale);
}
if (franticitems == true)
@ -551,14 +616,11 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush)
distance = (15 * distance) / 14;
}
if (numPlayers > 0)
{
// Items get crazier with the fewer players that you have.
distance = FixedMul(
distance * FRACUNIT,
FRACUNIT + (K_ItemOddsScale(numPlayers, spbrush) / 2)
) / FRACUNIT;
}
// Items get crazier with the fewer players that you have.
distance = FixedMul(
distance,
FRACUNIT + (K_ItemOddsScale(numPlayers) / 2)
);
return distance;
}
@ -574,20 +636,23 @@ INT32 K_KartGetItemOdds(
UINT8 pos, SINT8 item,
UINT32 ourDist,
fixed_t mashed,
boolean spbrush, boolean bot, boolean rival)
boolean bot, boolean rival)
{
INT32 newodds;
INT32 i;
UINT8 pingame = 0, pexiting = 0;
SINT8 first = -1, second = -1;
player_t *first = NULL;
player_t *second = NULL;
UINT32 firstDist = UINT32_MAX;
UINT32 secondToFirst = UINT32_MAX;
UINT32 secondDist = UINT32_MAX;
UINT32 secondToFirst = 0;
boolean isFirst = false;
boolean powerItem = false;
boolean cooldownOnStart = false;
boolean indirectItem = false;
boolean notNearEnd = false;
INT32 shieldtype = KSHIELD_NONE;
@ -596,7 +661,15 @@ INT32 K_KartGetItemOdds(
I_Assert(KartItemCVars[NUMKARTRESULTS-2] != NULL); // Make sure this exists
if (!KartItemCVars[item-1]->value && !modeattacking)
{
return 0;
}
if (K_GetItemCooldown(item) > 0)
{
// Cooldown is still running, don't give another.
return 0;
}
/*
if (bot)
@ -649,28 +722,32 @@ INT32 K_KartGetItemOdds(
return 0;
}
if (players[i].mo && gametype == GT_RACE)
if (players[i].position == 1)
{
if (players[i].position == 1 && first == -1)
first = i;
if (players[i].position == 2 && second == -1)
second = i;
first = &players[i];
}
if (players[i].position == 2)
{
second = &players[i];
}
}
if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB
if (first != NULL) // calculate 2nd's distance from 1st, for SPB
{
firstDist = players[first].distancetofinish;
firstDist = first->distancetofinish;
isFirst = (ourDist <= firstDist);
}
if (mapobjectscale != FRACUNIT)
{
firstDist = FixedDiv(firstDist * FRACUNIT, mapobjectscale) / FRACUNIT;
}
if (second != NULL)
{
secondDist = second->distancetofinish;
}
secondToFirst = K_ScaleItemDistance(
players[second].distancetofinish - players[first].distancetofinish,
pingame, spbrush
);
if (first != NULL && second != NULL)
{
secondToFirst = secondDist - firstDist;
secondToFirst = K_ScaleItemDistance(secondToFirst, 16 - pingame); // Reversed scaling, so 16P is like 1v1, and 1v1 is like 16P
}
switch (item)
@ -680,6 +757,7 @@ INT32 K_KartGetItemOdds(
case KITEM_SUPERRING:
notNearEnd = true;
break;
case KITEM_ROCKETSNEAKER:
case KITEM_JAWZ:
case KITEM_LANDMINE:
@ -692,11 +770,13 @@ INT32 K_KartGetItemOdds(
case KRITEM_DUALJAWZ:
powerItem = true;
break;
case KRITEM_TRIPLEBANANA:
case KRITEM_TENFOLDBANANA:
powerItem = true;
notNearEnd = true;
break;
case KITEM_INVINCIBILITY:
case KITEM_MINE:
case KITEM_GROW:
@ -705,40 +785,46 @@ INT32 K_KartGetItemOdds(
cooldownOnStart = true;
powerItem = true;
break;
case KITEM_SPB:
cooldownOnStart = true;
indirectItem = true;
notNearEnd = true;
if (firstDist < ENDDIST) // No SPB near the end of the race
if (firstDist < ENDDIST*2 // No SPB when 1st is almost done
|| isFirst == true) // No SPB for 1st ever
{
newodds = 0;
}
else
{
const INT32 distFromStart = max(0, (INT32)secondToFirst - SPBSTARTDIST);
const INT32 distRange = SPBFORCEDIST - SPBSTARTDIST;
const INT32 mulMax = 3;
INT32 multiplier = (distFromStart * mulMax) / distRange;
const UINT32 dist = max(0, ((signed)secondToFirst) - SPBSTARTDIST);
const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST;
const UINT8 maxOdds = 20;
fixed_t multiplier = (dist * FRACUNIT) / distRange;
if (multiplier < 0)
{
multiplier = 0;
if (multiplier > mulMax)
multiplier = mulMax;
}
newodds *= multiplier;
if (multiplier > FRACUNIT)
{
multiplier = FRACUNIT;
}
newodds = FixedMul(maxOdds * 4, multiplier);
}
break;
case KITEM_SHRINK:
cooldownOnStart = true;
powerItem = true;
indirectItem = true;
notNearEnd = true;
if (pingame-1 <= pexiting)
newodds = 0;
break;
case KITEM_LIGHTNINGSHIELD:
cooldownOnStart = true;
powerItem = true;
@ -746,6 +832,7 @@ INT32 K_KartGetItemOdds(
if (spbplace != -1)
newodds = 0;
break;
default:
break;
}
@ -756,12 +843,8 @@ INT32 K_KartGetItemOdds(
return newodds;
}
if ((indirectItem == true) && (indirectitemcooldown > 0))
{
// Too many items that act indirectly in a match can feel kind of bad.
newodds = 0;
}
else if ((cooldownOnStart == true) && (leveltime < (30*TICRATE)+starttime))
if ((cooldownOnStart == true) && (leveltime < (30*TICRATE)+starttime))
{
// This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items)
newodds = 0;
@ -788,7 +871,7 @@ INT32 K_KartGetItemOdds(
fracOdds *= 2;
}
fracOdds = FixedMul(fracOdds, FRACUNIT + K_ItemOddsScale(pingame, spbrush));
fracOdds = FixedMul(fracOdds, FRACUNIT + K_ItemOddsScale(pingame));
if (mashed > 0)
{
@ -804,7 +887,7 @@ INT32 K_KartGetItemOdds(
//{ SRB2kart Roulette Code - Distance Based, yes waypoints
UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush)
UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper)
{
UINT8 i;
UINT8 useodds = 0;
@ -832,7 +915,7 @@ UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbum
i, j,
player->distancetofinish,
mashed,
spbrush, player->bot, (player->bot && player->botvars.rival)
player->bot, (player->bot && player->botvars.rival)
) > 0)
{
available = true;
@ -947,6 +1030,80 @@ INT32 K_GetRollingRouletteItem(player_t *player)
return translation[(player->itemroulette % roulette_size) / 3];
}
boolean K_ForcedSPB(player_t *player)
{
player_t *first = NULL;
player_t *second = NULL;
UINT32 secondToFirst = UINT32_MAX;
UINT8 pingame = 0;
UINT8 i;
if (!cv_selfpropelledbomb.value)
{
return false;
}
if (!(gametyperules & GTR_CIRCUIT))
{
return false;
}
if (player->position <= 1)
{
return false;
}
if (spbplace != -1)
{
return false;
}
if (itemCooldowns[KITEM_SPB - 1] > 0)
{
return false;
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
{
continue;
}
if (players[i].exiting)
{
return false;
}
pingame++;
if (players[i].position == 1)
{
first = &players[i];
}
if (players[i].position == 2)
{
second = &players[i];
}
}
#if 0
if (pingame <= 2)
{
return false;
}
#endif
if (first != NULL && second != NULL)
{
secondToFirst = second->distancetofinish - first->distancetofinish;
secondToFirst = K_ScaleItemDistance(secondToFirst, 16 - pingame);
}
return (secondToFirst >= SPBFORCEDIST);
}
static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
{
INT32 i;
@ -958,8 +1115,6 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
INT32 totalspawnchance = 0;
UINT8 bestbumper = 0;
fixed_t mashed = 0;
boolean dontforcespb = false;
boolean spbrush = false;
// This makes the roulette cycle through items - if this is 0, you shouldn't be here.
if (!player->itemroulette)
@ -971,17 +1126,13 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
{
if (!playeringame[i] || players[i].spectator)
continue;
pingame++;
if (players[i].exiting)
dontforcespb = true;
if (players[i].bumpers > bestbumper)
bestbumper = players[i].bumpers;
}
// No forced SPB in 1v1s, it has to be randomly rolled
if (pingame <= 2)
dontforcespb = true;
// This makes the roulette produce the random noises.
if ((player->itemroulette % 3) == 1 && P_IsDisplayPlayer(player) && !demo.freecam)
{
@ -1029,14 +1180,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
}
}
if (spbplace != -1 && player->position == spbplace+1)
{
// SPB Rush Mode: It's 2nd place's job to catch-up items and make 1st place's job hell
pdis = (3 * pdis) / 2;
spbrush = true;
}
pdis = K_ScaleItemDistance(pdis, pingame, spbrush);
pdis = K_ScaleItemDistance(pdis, pingame);
if (player->bot && player->botvars.rival)
{
@ -1150,10 +1294,8 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
}
// SPECIAL CASE No. 5:
// Force SPB onto 2nd if they get too far behind
if ((gametyperules & GTR_CIRCUIT) && player->position == 2 && pdis > SPBFORCEDIST
&& spbplace == -1 && !indirectitemcooldown && !dontforcespb
&& cv_selfpropelledbomb.value)
// Force SPB if 2nd is way too far behind
if (K_ForcedSPB(player) == true)
{
K_KartGetItemResult(player, KITEM_SPB);
player->karthud[khud_itemblink] = TICRATE;
@ -1171,7 +1313,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
spawnchance[i] = 0;
// Split into another function for a debug function below
useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush);
useodds = K_FindUseodds(player, mashed, pdis, bestbumper);
for (i = 1; i < NUMKARTRESULTS; i++)
{
@ -1179,7 +1321,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
useodds, i,
player->distancetofinish,
mashed,
spbrush, player->bot, (player->bot && player->botvars.rival))
player->bot, (player->bot && player->botvars.rival))
);
}
@ -3230,7 +3372,7 @@ static void K_GetKartBoostPower(player_t *player)
if (player->startboost) // Startup Boost
{
ADDBOOST(FRACUNIT, 4*FRACUNIT, sliptidehandling/2); // + 100% top speed, + 400% acceleration, +25% handling
ADDBOOST(FRACUNIT, 4*FRACUNIT, sliptidehandling); // + 100% top speed, + 400% acceleration, +50% handling
}
if (player->driftboost) // Drift Boost
@ -3258,9 +3400,14 @@ static void K_GetKartBoostPower(player_t *player)
ADDBOOST(player->trickboostpower, 5*FRACUNIT, 0); // <trickboostpower>% speed, 500% accel, 0% handling
}
if (player->gateBoost) // SPB Juicebox boost
{
ADDBOOST(3*FRACUNIT/4, 4*FRACUNIT, sliptidehandling/2); // + 75% top speed, + 400% acceleration, +25% handling
}
if (player->ringboost) // Ring Boost
{
ADDBOOST(FRACUNIT/5, 4*FRACUNIT, 0); // + 20% top speed, + 400% acceleration, +0% handling
ADDBOOST(FRACUNIT/4, 4*FRACUNIT, 0); // + 20% top speed, + 400% acceleration, +0% handling
}
if (player->eggmanexplode) // Ready-to-explode
@ -3858,10 +4005,38 @@ angle_t K_StumbleSlope(angle_t angle, angle_t pitch, angle_t roll)
return slope;
}
static void K_StumblePlayer(player_t *player)
{
P_ResetPlayer(player);
#if 0
// Single, medium bounce
player->tumbleBounces = TUMBLEBOUNCES;
player->tumbleHeight = 30;
#else
// Two small bounces
player->tumbleBounces = TUMBLEBOUNCES-1;
player->tumbleHeight = 20;
#endif
player->pflags &= ~PF_TUMBLESOUND;
S_StartSound(player->mo, sfx_s3k9b);
// and then modulate momz like that...
player->mo->momz = K_TumbleZ(player->mo, player->tumbleHeight * FRACUNIT);
P_SetPlayerMobjState(player->mo, S_KART_SPINOUT);
if (P_IsDisplayPlayer(player))
P_StartQuake(64<<FRACBITS, 10);
// Reset slope.
player->mo->pitch = player->mo->roll = 0;
}
boolean K_CheckStumble(player_t *player, angle_t oldPitch, angle_t oldRoll, boolean fromAir)
{
angle_t steepVal = ANGLE_MAX;
fixed_t gravityadjust;
angle_t oldSlope, newSlope;
angle_t slopeDelta;
@ -3911,36 +4086,7 @@ boolean K_CheckStumble(player_t *player, angle_t oldPitch, angle_t oldRoll, bool
// Oh jeez, you landed on your side.
// You get to tumble.
P_ResetPlayer(player);
#if 0
// Single, medium bounce
player->tumbleBounces = TUMBLEBOUNCES;
player->tumbleHeight = 30;
#else
// Two small bounces
player->tumbleBounces = TUMBLEBOUNCES-1;
player->tumbleHeight = 20;
#endif
player->pflags &= ~PF_TUMBLESOUND;
S_StartSound(player->mo, sfx_s3k9b);
gravityadjust = P_GetMobjGravity(player->mo)/2; // so we'll halve it for our calculations.
if (player->mo->eflags & MFE_UNDERWATER)
gravityadjust /= 2; // halve "gravity" underwater
// and then modulate momz like that...
player->mo->momz = -gravityadjust * player->tumbleHeight;
P_SetPlayerMobjState(player->mo, S_KART_SPINOUT);
if (P_IsDisplayPlayer(player))
P_StartQuake(64<<FRACBITS, 10);
// Reset slope.
player->mo->pitch = player->mo->roll = 0;
K_StumblePlayer(player);
return true;
}
@ -4174,23 +4320,45 @@ void K_ApplyTripWire(player_t *player, tripwirestate_t state)
INT32 K_ExplodePlayer(player_t *player, mobj_t *inflictor, mobj_t *source) // A bit of a hack, we just throw the player up higher here and extend their spinout timer
{
INT32 ringburst = 10;
fixed_t spbMultiplier = FRACUNIT;
(void)source;
K_DirectorFollowAttack(player, inflictor, source);
player->mo->momz = 18*mapobjectscale*P_MobjFlip(player->mo); // please stop forgetting mobjflip checks!!!!
if (inflictor != NULL && P_MobjWasRemoved(inflictor) == false)
{
if (inflictor->type == MT_SPBEXPLOSION && inflictor->movefactor)
{
spbMultiplier = inflictor->movefactor;
if (spbMultiplier <= 0)
{
// Convert into stumble.
K_StumblePlayer(player);
return 0;
}
else if (spbMultiplier < FRACUNIT)
{
spbMultiplier = FRACUNIT;
}
}
}
player->mo->momz = 18 * mapobjectscale * P_MobjFlip(player->mo); // please stop forgetting mobjflip checks!!!!
player->mo->momx = player->mo->momy = 0;
player->spinouttype = KSPIN_EXPLOSION;
player->spinouttimer = (3*TICRATE/2)+2;
if (inflictor && !P_MobjWasRemoved(inflictor))
if (spbMultiplier != FRACUNIT)
{
if (inflictor->type == MT_SPBEXPLOSION && inflictor->extravalue1)
player->mo->momz = FixedMul(player->mo->momz, spbMultiplier);
player->spinouttimer = FixedMul(player->spinouttimer, spbMultiplier + ((spbMultiplier - FRACUNIT) / 2));
ringburst = FixedMul(ringburst * FRACUNIT, spbMultiplier) / FRACUNIT;
if (ringburst > 20)
{
player->spinouttimer = ((5*player->spinouttimer)/2)+1;
player->mo->momz *= 2;
ringburst = 20;
}
}
@ -4760,9 +4928,10 @@ static void K_SpawnDriftElectricity(player_t *player)
}
}
void K_SpawnDriftElectricSparks(player_t *player)
void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave)
{
SINT8 hdir, vdir, i;
int shockscale = shockwave ? 2 : 1;
mobj_t *mo = player->mo;
angle_t momangle = K_MomentumAngle(mo) + ANGLE_180;
@ -4772,12 +4941,7 @@ void K_SpawnDriftElectricSparks(player_t *player)
fixed_t z = FixedDiv(mo->height, 2 * mo->scale); // P_SpawnMobjFromMobj will rescale
fixed_t sparkspeed = mobjinfo[MT_DRIFTELECTRICSPARK].speed;
fixed_t sparkradius = 2 * mobjinfo[MT_DRIFTELECTRICSPARK].radius;
UINT16 color = K_DriftSparkColor(player, player->driftcharge);
// if the sparks are spawned from first blood rather than drift boost, color will be SKINCOLOR_NONE. ew!
if (color == SKINCOLOR_NONE)
color = SKINCOLOR_SILVER;
fixed_t sparkradius = 2 * shockscale * mobjinfo[MT_DRIFTELECTRICSPARK].radius;
for (hdir = -1; hdir <= 1; hdir += 2)
{
@ -4800,6 +4964,11 @@ void K_SpawnDriftElectricSparks(player_t *player)
spark->momx += mo->momx; // copy player speed
spark->momy += mo->momy;
spark->momz += P_GetMobjZMovement(mo);
spark->destscale = shockscale * spark->scale;
P_SetScale(spark, shockscale * spark->scale);
if (shockwave)
spark->frame |= FF_ADD;
sparkangle += ANGLE_90;
}
@ -5904,39 +6073,45 @@ void K_DoSneaker(player_t *player, INT32 type)
static void K_DoShrink(player_t *user)
{
mobj_t *mobj, *next;
S_StartSound(user->mo, sfx_kc46); // Sound the BANG!
Obj_CreateShrinkPohbees(user);
// kill everything in the kitem list while we're at it:
for (mobj = kitemcap; mobj; mobj = next)
#if 0
{
next = mobj->itnext;
mobj_t *mobj, *next;
// check if the item is being held by a player behind us before removing it.
// check if the item is a "shield" first, bc i'm p sure thrown items keep the player that threw em as target anyway
if (mobj->type == MT_BANANA_SHIELD || mobj->type == MT_JAWZ_SHIELD ||
mobj->type == MT_SSMINE_SHIELD || mobj->type == MT_EGGMANITEM_SHIELD ||
mobj->type == MT_SINK_SHIELD || mobj->type == MT_ORBINAUT_SHIELD ||
mobj->type == MT_DROPTARGET_SHIELD)
// kill everything in the kitem list while we're at it:
for (mobj = kitemcap; mobj; mobj = next)
{
if (mobj->target && mobj->target->player)
next = mobj->itnext;
if (mobj->type == MT_SPB)
{
if (mobj->target->player->position > user->position)
continue; // this guy's behind us, don't take his stuff away!
continue;
}
// check if the item is being held by a player behind us before removing it.
// check if the item is a "shield" first, bc i'm p sure thrown items keep the player that threw em as target anyway
if (mobj->type == MT_BANANA_SHIELD || mobj->type == MT_JAWZ_SHIELD ||
mobj->type == MT_SSMINE_SHIELD || mobj->type == MT_EGGMANITEM_SHIELD ||
mobj->type == MT_SINK_SHIELD || mobj->type == MT_ORBINAUT_SHIELD ||
mobj->type == MT_DROPTARGET_SHIELD)
{
if (mobj->target && mobj->target->player)
{
if (mobj->target->player->position > user->position)
continue; // this guy's behind us, don't take his stuff away!
}
}
mobj->destscale = 0;
mobj->flags &= ~(MF_SOLID|MF_SHOOTABLE|MF_SPECIAL);
mobj->flags |= MF_NOCLIPTHING; // Just for safety
}
mobj->destscale = 0;
mobj->flags &= ~(MF_SOLID|MF_SHOOTABLE|MF_SPECIAL);
mobj->flags |= MF_NOCLIPTHING; // Just for safety
if (mobj->type == MT_SPB)
spbplace = -1;
}
#endif
}
void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound)
@ -6354,8 +6529,7 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8
useodds, i,
UINT32_MAX,
0,
false, false, false
)
false, false)
);
}
@ -6371,46 +6545,8 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8
// K_KartGetItemResult requires a player
// but item roulette will need rewritten to change this
switch (i)
{
// Special roulettes first, then the generic ones are handled by default
case KRITEM_DUALSNEAKER: // Sneaker x2
newType = KITEM_SNEAKER;
newAmount = 2;
break;
case KRITEM_TRIPLESNEAKER: // Sneaker x3
newType = KITEM_SNEAKER;
newAmount = 3;
break;
case KRITEM_TRIPLEBANANA: // Banana x3
newType = KITEM_BANANA;
newAmount = 3;
break;
case KRITEM_TENFOLDBANANA: // Banana x10
newType = KITEM_BANANA;
newAmount = 10;
break;
case KRITEM_TRIPLEORBINAUT: // Orbinaut x3
newType = KITEM_ORBINAUT;
newAmount = 3;
break;
case KRITEM_QUADORBINAUT: // Orbinaut x4
newType = KITEM_ORBINAUT;
newAmount = 4;
break;
case KRITEM_DUALJAWZ: // Jawz x2
newType = KITEM_JAWZ;
newAmount = 2;
break;
case KITEM_BALLHOG: // Ballhog x5
newType = KITEM_BALLHOG;
newAmount = 5;
break;
default:
newType = i;
newAmount = 1;
break;
}
newType = K_ItemResultToType(i);
newAmount = K_ItemResultToAmount(i);
if (newAmount > 1)
{
@ -7596,7 +7732,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
// Speed lines
if (player->sneakertimer || player->ringboost
|| player->driftboost || player->startboost
|| player->eggmanexplode || player->trickboost)
|| player->eggmanexplode || player->trickboost
|| player->gateBoost)
{
#if 0
if (player->invincibilitytimer)
@ -7846,6 +7983,9 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
if (player->strongdriftboost)
player->strongdriftboost--;
if (player->gateBoost)
player->gateBoost--;
if (player->startboost > 0 && onground == true)
{
player->startboost--;
@ -8949,7 +9089,7 @@ static void K_KartDrift(player_t *player, boolean onground)
player->strongdriftboost = 85;
K_SpawnDriftBoostExplosion(player, 3);
K_SpawnDriftElectricSparks(player);
K_SpawnDriftElectricSparks(player, K_DriftSparkColor(player, player->driftcharge), false);
}
else if (player->driftcharge >= dsfour)
{
@ -8963,7 +9103,7 @@ static void K_KartDrift(player_t *player, boolean onground)
player->strongdriftboost = 125;
K_SpawnDriftBoostExplosion(player, 4);
K_SpawnDriftElectricSparks(player);
K_SpawnDriftElectricSparks(player, K_DriftSparkColor(player, player->driftcharge), false);
}
}
@ -9320,7 +9460,7 @@ static INT32 K_FlameShieldMax(player_t *player)
}
disttofinish = player->distancetofinish - disttofinish;
distv = FixedMul(distv * FRACUNIT, mapobjectscale) / FRACUNIT;
distv = FixedMul(distv, mapobjectscale);
return min(16, 1 + (disttofinish / distv));
}
@ -10628,9 +10768,10 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
player->pflags &= ~PF_RINGLOCK; // reset ring lock
if (player->itemtype == KITEM_SPB
|| player->itemtype == KITEM_SHRINK
|| player->growshrinktimer < 0)
indirectitemcooldown = 20*TICRATE;
|| player->itemtype == KITEM_SHRINK)
{
K_SetItemCooldown(player->itemtype, 20*TICRATE);
}
if (player->hyudorotimer > 0)
{

View file

@ -44,12 +44,18 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value);
extern consvar_t *KartItemCVars[NUMKARTRESULTS-1];
UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush);
fixed_t K_ItemOddsScale(UINT8 numPlayers, boolean spbrush);
UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush);
INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival);
UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper);
fixed_t K_ItemOddsScale(UINT8 numPlayers);
UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers);
INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, fixed_t mashed, boolean bot, boolean rival);
INT32 K_GetRollingRouletteItem(player_t *player);
boolean K_ForcedSPB(player_t *player);
INT32 K_GetShieldFromItem(INT32 item);
SINT8 K_ItemResultToType(SINT8 getitem);
UINT8 K_ItemResultToAmount(SINT8 getitem);
tic_t K_GetItemCooldown(SINT8 itemResult);
void K_SetItemCooldown(SINT8 itemResult, tic_t time);
void K_RunItemCooldowns(void);
fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against);
boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2);
boolean K_KartSolidBounce(mobj_t *bounceMobj, mobj_t *solidMobj);
@ -119,7 +125,7 @@ INT32 K_GetKartDriftSparkValue(player_t *player);
INT32 K_StairJankFlip(INT32 value);
INT32 K_GetKartDriftSparkValueForStage(player_t *player, UINT8 stage);
void K_SpawnDriftBoostExplosion(player_t *player, int stage);
void K_SpawnDriftElectricSparks(player_t *player);
void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave);
void K_KartUpdatePosition(player_t *player);
mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount);
void K_DropItems(player_t *player);

View file

@ -3366,34 +3366,14 @@ void M_DrawItemToggles(void)
cv = KartItemCVars[currentMenu->menuitems[thisitem].mvar1-1];
translucent = (cv->value ? 0 : V_TRANSLUCENT);
switch (currentMenu->menuitems[thisitem].mvar1)
{
case KRITEM_DUALSNEAKER:
case KRITEM_DUALJAWZ:
drawnum = 2;
break;
case KRITEM_TRIPLESNEAKER:
case KRITEM_TRIPLEBANANA:
case KRITEM_TRIPLEORBINAUT:
drawnum = 3;
break;
case KRITEM_QUADORBINAUT:
drawnum = 4;
break;
case KRITEM_TENFOLDBANANA:
drawnum = 10;
break;
default:
drawnum = 0;
break;
}
drawnum = K_ItemResultToAmount(currentMenu->menuitems[thisitem].mvar1);
if (cv->value)
V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBG", PU_CACHE));
else
V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBGD", PU_CACHE));
if (drawnum != 0)
if (drawnum > 1)
{
V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISMUL", PU_CACHE));
V_DrawScaledPatch(x, y, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[thisitem].mvar1, true), PU_CACHE));
@ -3433,30 +3413,14 @@ void M_DrawItemToggles(void)
cv = KartItemCVars[currentMenu->menuitems[itemOn].mvar1-1];
translucent = (cv->value ? 0 : V_TRANSLUCENT);
switch (currentMenu->menuitems[itemOn].mvar1)
{
case KRITEM_DUALSNEAKER:
case KRITEM_DUALJAWZ:
drawnum = 2;
break;
case KRITEM_TRIPLESNEAKER:
case KRITEM_TRIPLEBANANA:
drawnum = 3;
break;
case KRITEM_TENFOLDBANANA:
drawnum = 10;
break;
default:
drawnum = 0;
break;
}
drawnum = K_ItemResultToAmount(currentMenu->menuitems[itemOn].mvar1);
if (cv->value)
V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE));
else
V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBGD", PU_CACHE));
if (drawnum != 0)
if (drawnum > 1)
{
V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITMUL", PU_CACHE));
V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[itemOn].mvar1, false), PU_CACHE));

View file

@ -20,4 +20,13 @@ void Obj_SpawnItemDebrisEffects(mobj_t *collectible, mobj_t *collector);
void Obj_ItemDebrisThink(mobj_t *debris);
fixed_t Obj_ItemDebrisBounce(mobj_t *debris, fixed_t momz);
/* SPB */
void Obj_SPBThink(mobj_t *spb);
void Obj_SPBExplode(mobj_t *spb);
void Obj_SPBTouch(mobj_t *spb, mobj_t *toucher);
/* SPB Juicebox Rings */
void Obj_MantaRingThink(mobj_t *manta);
mobj_t *Obj_MantaRingCreate(mobj_t *spb, mobj_t *owner, mobj_t *chase);
#endif/*k_objects_H*/

View file

@ -151,6 +151,7 @@ void K_DoIngameRespawn(player_t *player)
player->ringboost = 0;
player->driftboost = player->strongdriftboost = 0;
player->gateBoost = 0;
K_TumbleInterrupt(player);
P_ResetPlayer(player);

View file

@ -252,6 +252,10 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->driftboost);
else if (fastcmp(field,"strongdriftboost"))
lua_pushinteger(L, plr->strongdriftboost);
else if (fastcmp(field,"gateBoost"))
lua_pushinteger(L, plr->gateBoost);
else if (fastcmp(field,"gateSound"))
lua_pushinteger(L, plr->gateSound);
else if (fastcmp(field,"aizdriftstraft"))
lua_pushinteger(L, plr->aizdriftstrat);
else if (fastcmp(field,"aizdrifttilt"))
@ -616,6 +620,12 @@ static int player_set(lua_State *L)
plr->driftcharge = luaL_checkinteger(L, 3);
else if (fastcmp(field,"driftboost"))
plr->driftboost = luaL_checkinteger(L, 3);
else if (fastcmp(field,"strongdriftboost"))
plr->strongdriftboost = luaL_checkinteger(L, 3);
else if (fastcmp(field,"gateBoost"))
plr->gateBoost = luaL_checkinteger(L, 3);
else if (fastcmp(field,"gateSound"))
plr->gateSound = luaL_checkinteger(L, 3);
else if (fastcmp(field,"aizdriftstraft"))
plr->aizdriftstrat = luaL_checkinteger(L, 3);
else if (fastcmp(field,"aizdrifttilt"))

View file

@ -375,9 +375,6 @@ int LUA_PushGlobals(lua_State *L, const char *word)
} else if (fastcmp(word,"wantedcalcdelay")) {
lua_pushinteger(L, wantedcalcdelay);
return 1;
} else if (fastcmp(word,"indirectitemcooldown")) {
lua_pushinteger(L, indirectitemcooldown);
return 1;
} else if (fastcmp(word,"thwompsactive")) {
lua_pushboolean(L, thwompsactive);
return 1;
@ -463,8 +460,6 @@ int LUA_WriteGlobals(lua_State *L, const char *word)
racecountdown = (tic_t)luaL_checkinteger(L, 2);
else if (fastcmp(word,"exitcountdown"))
exitcountdown = (tic_t)luaL_checkinteger(L, 2);
else if (fastcmp(word,"indirectitemcooldown"))
indirectitemcooldown = (tic_t)luaL_checkinteger(L, 2);
else
return 0;

View file

@ -1,3 +1,5 @@
hyudoro.c
shrink.c
item-debris.c
spb.c
manta-ring.c

307
src/objects/manta-ring.c Normal file
View file

@ -0,0 +1,307 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour
// Copyright (C) 2022 by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file manta-ring.c
/// \brief SPB Juicebox rings. See spb.c for their spawning.
#include "../doomdef.h"
#include "../doomstat.h"
#include "../info.h"
#include "../k_kart.h"
#include "../k_objects.h"
#include "../m_random.h"
#include "../p_local.h"
#include "../r_main.h"
#include "../s_sound.h"
#include "../g_game.h"
#include "../z_zone.h"
#include "../k_waypoint.h"
#include "../k_respawn.h"
#define MANTA_RACETIME (90)
#define MANTA_MINTIME (15)
#define MANTA_SPRINTTIME (60)
#define MANTA_ALIVEGATE (0)
#define MANTA_DEADGATE (FF_TRANS80)
#define MANTA_SIZE (2 * FRACUNIT)
#define MANTA_SIZEUP (10)
#define MANTA_SIZESTRENGTH (1500)
#define MANTA_MAXRAMP (80)
#define MANTA_COLLIDE (80 * FRACUNIT)
#define MANTA_TURBO (40)
#define MANTA_FASTRAMP (17)
#define MANTA_MINPWR (10)
#define manta_delay(o) ((o)->fuse)
#define manta_timealive(o) ((o)->movecount)
#define manta_boostval(o) ((o)->extravalue1)
#define manta_laps(o) ((o)->extravalue2)
#define manta_touched(o) ((o)->cusval)
#define manta_owner(o) ((o)->target)
#define manta_chase(o) ((o)->tracer)
static boolean MantaAlreadyTouched(mobj_t *manta, player_t *player)
{
INT32 touchFlag = 0;
if (manta_chase(manta) != NULL && P_MobjWasRemoved(manta_chase(manta)) == false
&& player->mo == manta_chase(manta))
{
return true;
}
#if 0
if (manta_laps(manta) < player->laps)
{
return true;
}
#endif
touchFlag = 1 << (player - players);
return (manta_touched(manta) & touchFlag);
}
static void Obj_MantaCollide(mobj_t *manta, mobj_t *other)
{
// Could hook this into actual mobj collide if desired.
fixed_t distance = INT32_MAX;
fixed_t size = INT32_MAX;
INT32 addBoost = 0;
INT32 touchFlag = 0;
size_t i;
distance = P_AproxDistance(P_AproxDistance(
other->x - manta->x,
other->y - manta->y),
other->z - manta->z) - other->radius - manta->radius;
size = FixedMul(MANTA_COLLIDE, mapobjectscale);
if (distance > size)
{
return;
}
if (other->player != NULL) // Just in case other objects should be added?
{
if (MantaAlreadyTouched(manta, other->player))
{
return;
}
touchFlag = 1 << (other->player - players);
}
addBoost = manta_boostval(manta);
if (manta_timealive(manta) < MANTA_FASTRAMP)
{
// Ramp up to max power.
addBoost = FixedMul(addBoost * FRACUNIT, (manta_timealive(manta) * FRACUNIT) / MANTA_FASTRAMP);
// Convert to integer
addBoost = (addBoost + (FRACUNIT/2)) / FRACUNIT;
// Cap it
addBoost = max(MANTA_MINPWR, addBoost);
}
if (other->player != NULL)
{
UINT8 snd = 0;
if (other->player->speedboost > FRACUNIT/4)
{
snd = other->player->gateSound;
other->player->gateSound++;
if (other->player->gateSound > 4)
{
other->player->gateSound = 4;
}
}
else
{
other->player->gateSound = 0;
}
K_SpawnDriftBoostExplosion(other->player, 3);
K_SpawnDriftElectricSparks(other->player, SKINCOLOR_CRIMSON, true);
for (i = 0; i < 5; i++)
{
S_StopSoundByID(other, sfx_gate01 + i);
}
S_StartSound(other, sfx_gate01 + snd);
other->player->gateBoost += addBoost/2;
if (P_IsDisplayPlayer(other->player) == true)
{
P_StartQuake(12 << FRACBITS, 6);
}
}
if (touchFlag > 0)
{
manta_touched(manta) |= touchFlag;
}
}
static void RunMantaCollide(mobj_t *manta)
{
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
{
player_t *player = NULL;
if (playeringame[i] == false)
{
// Invalid
continue;
}
player = &players[i];
if (player->spectator == true)
{
// Not playing.
continue;
}
if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true)
{
// Invalid object
continue;
}
if (player->mo == manta_chase(manta))
{
// Don't allow the person being chased to touch this.
continue;
}
Obj_MantaCollide(manta, player->mo);
}
}
static void RunMantaVisual(mobj_t *manta)
{
INT32 i;
if (manta->fuse < 5*TICRATE)
{
if (leveltime & 1)
{
manta->renderflags |= RF_DONTDRAW;
}
else
{
manta->renderflags &= ~RF_DONTDRAW;
}
}
for (i = 0; i <= r_splitscreen; i++)
{
const UINT8 pID = displayplayers[i];
player_t *player = &players[pID];
if (MantaAlreadyTouched(manta, player) == false)
{
break;
}
}
if (i > r_splitscreen)
{
manta->frame = (manta->frame & ~FF_TRANSMASK) | MANTA_DEADGATE;
}
else
{
manta->frame = (manta->frame & ~FF_TRANSMASK) | MANTA_ALIVEGATE;
}
}
void Obj_MantaRingThink(mobj_t *manta)
{
RunMantaVisual(manta);
if (manta_delay(manta) % MANTA_SIZEUP == 0)
{
manta->destscale += FixedMul(MANTA_SIZESTRENGTH, mapobjectscale);
manta_boostval(manta) = min(MANTA_MAXRAMP, manta_boostval(manta) + 1);
}
manta_timealive(manta)++;
RunMantaCollide(manta);
}
mobj_t *Obj_MantaRingCreate(mobj_t *spb, mobj_t *owner, mobj_t *chase)
{
mobj_t *manta = NULL;
INT32 delay = 0;
manta = P_SpawnMobjFromMobj(spb, 0, 0, 0, MT_MANTARING);
manta->color = SKINCOLOR_KETCHUP;
manta->destscale = FixedMul(MANTA_SIZE, spb->scale);
P_SetScale(manta, manta->destscale);
manta->angle = R_PointToAngle2(0, 0, spb->momx, spb->momy) + ANGLE_90;
// Set boost value
manta_boostval(manta) = MANTA_TURBO;
// Set despawn delay
delay = max(MANTA_MINTIME, MANTA_RACETIME / mapheaderinfo[gamemap - 1]->numlaps);
if (mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE)
{
delay = MANTA_SPRINTTIME;
}
manta_delay(manta) = delay * TICRATE;
// Default if neither object exists
manta_laps(manta) = INT32_MAX;
// Set owner
if (owner != NULL && P_MobjWasRemoved(owner) == false)
{
P_SetTarget(&manta_owner(manta), owner);
if (owner->player != NULL)
{
// Default if chaser doesn't exist
manta_laps(manta) = owner->player->laps;
}
}
// Set chaser
if (chase != NULL && P_MobjWasRemoved(chase) == false)
{
P_SetTarget(&manta_chase(manta), chase);
if (chase->player != NULL)
{
manta_laps(manta) = chase->player->laps;
}
}
return manta;
}

View file

@ -439,6 +439,8 @@ void Obj_PohbeeThinker(mobj_t *pohbee)
{
mobj_t *gun = NULL;
K_SetItemCooldown(KITEM_SHRINK, 20*TICRATE);
pohbee->momx = pohbee->momy = pohbee->momz = 0;
pohbee->spritexscale = pohbee->spriteyscale = 2*FRACUNIT;
@ -482,6 +484,8 @@ void Obj_PohbeeRemoved(mobj_t *pohbee)
P_RemoveMobj(gun);
gun = nextGun;
}
P_SetTarget(&pohbee_guns(pohbee), NULL);
}
void Obj_ShrinkGunRemoved(mobj_t *gun)
@ -493,6 +497,8 @@ void Obj_ShrinkGunRemoved(mobj_t *gun)
P_RemoveMobj(gun_laser(gun));
}
P_SetTarget(&gun_laser(gun), NULL);
chain = gun_chains(gun);
while (chain != NULL && P_MobjWasRemoved(chain) == false)
{
@ -500,6 +506,8 @@ void Obj_ShrinkGunRemoved(mobj_t *gun)
P_RemoveMobj(chain);
chain = nextChain;
}
P_SetTarget(&gun_chains(gun), NULL);
}
boolean Obj_ShrinkLaserCollide(mobj_t *gun, mobj_t *victim)

1024
src/objects/spb.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -312,7 +312,6 @@ void A_ChangeHeight(mobj_t *actor);
void A_ItemPop(mobj_t *actor);
void A_JawzChase(mobj_t *actor);
void A_JawzExplode(mobj_t *actor);
void A_SPBChase(mobj_t *actor);
void A_SSMineSearch(mobj_t *actor);
void A_SSMineExplode(mobj_t *actor);
void A_LandMineExplode(mobj_t *actor);
@ -13370,516 +13369,6 @@ void A_JawzExplode(mobj_t *actor)
return;
}
static void SpawnSPBTrailRings(mobj_t *actor)
{
I_Assert(actor != NULL);
if (leveltime % 6 == 0)
{
if (leveltime % (actor->extravalue1 == 2 ? 6 : 3) == 0) // Extravalue1 == 2 is seeking mode. Because the SPB is about twice as fast as normal in that mode, also spawn the rings twice as often to make up for it!
{
mobj_t *ring = P_SpawnMobj(actor->x - actor->momx, actor->y - actor->momy,
actor->z - actor->momz + (24*mapobjectscale), MT_RING);
ring->threshold = 10;
ring->fuse = 35*TICRATE;
ring->colorized = true;
ring->color = SKINCOLOR_RED;
}
}
}
// Spawns the V shaped dust. To be used when the SPB is going mostly forward.
static void SpawnSPBDust(mobj_t *mo)
{
// The easiest way to spawn a V shaped cone of dust from the SPB is simply to spawn 2 particles, and to both move them to the sides in opposite direction.
mobj_t *dust;
fixed_t sx;
fixed_t sy;
fixed_t sz = mo->floorz;
angle_t sa = mo->angle - ANG1*60;
INT32 i;
if (mo->eflags & MFE_VERTICALFLIP)
sz = mo->ceilingz;
if (leveltime & 1 && abs(mo->z - sz) < FRACUNIT*64) // Only ever other frame. Also don't spawn it if we're way above the ground.
{
// Determine spawning position next to the SPB:
for (i=0; i < 2; i++)
{
sx = mo->x + FixedMul((mo->scale*96), FINECOSINE((sa)>>ANGLETOFINESHIFT));
sy = mo->y + FixedMul((mo->scale*96), FINESINE((sa)>>ANGLETOFINESHIFT));
dust = P_SpawnMobj(sx, sy, sz, MT_SPBDUST);
dust->momx = mo->momx/2;
dust->momy = mo->momy/2;
dust->momz = mo->momz/2; // Give some of the momentum to the dust
P_SetScale(dust, mo->scale*2);
dust->colorized = true;
dust->color = SKINCOLOR_RED;
P_InitAngle(dust, mo->angle - FixedAngle(FRACUNIT*90 - FRACUNIT*180*i)); // The first one will spawn to the right of the spb, the second one to the left.
P_Thrust(dust, dust->angle, 6*dust->scale);
K_MatchGenericExtraFlags(dust, mo);
sa += ANG1*120; // Add 120 degrees to get to mo->angle + ANG1*60
}
}
}
// Spawns SPB slip tide. To be used when the SPB is turning.
// Modified version of K_SpawnAIZDust. Maybe we could merge those to be cleaner?
// dir should be either 1 or -1 to determine where to spawn the dust.
static void SpawnSPBAIZDust(mobj_t *mo, INT32 dir)
{
fixed_t newx;
fixed_t newy;
mobj_t *spark;
angle_t travelangle;
fixed_t sz = mo->floorz;
if (mo->eflags & MFE_VERTICALFLIP)
sz = mo->ceilingz;
travelangle = K_MomentumAngle(mo);
if (leveltime & 1 && abs(mo->z - sz) < FRACUNIT*64)
{
newx = mo->x + P_ReturnThrustX(mo, travelangle - (dir*ANGLE_45), FixedMul(24*FRACUNIT, mo->scale));
newy = mo->y + P_ReturnThrustY(mo, travelangle - (dir*ANGLE_45), FixedMul(24*FRACUNIT, mo->scale));
spark = P_SpawnMobj(newx, newy, sz, MT_AIZDRIFTSTRAT);
spark->colorized = true;
spark->color = SKINCOLOR_RED;
spark->flags = MF_NOGRAVITY|MF_PAIN;
P_SetTarget(&spark->target, mo);
P_InitAngle(spark, travelangle+(dir*ANGLE_90));
P_SetScale(spark, (spark->destscale = mo->scale*3/2));
spark->momx = (6*mo->momx)/5;
spark->momy = (6*mo->momy)/5;
K_MatchGenericExtraFlags(spark, mo);
}
}
// Used for seeking and when SPB is trailing its target from way too close!
static void SpawnSPBSpeedLines(mobj_t *actor)
{
mobj_t *fast = P_SpawnMobj(actor->x + (P_RandomRange(-24,24) * actor->scale),
actor->y + (P_RandomRange(-24,24) * actor->scale),
actor->z + (actor->height/2) + (P_RandomRange(-24,24) * actor->scale),
MT_FASTLINE);
P_SetTarget(&fast->target, actor);
P_InitAngle(fast, K_MomentumAngle(actor));
fast->color = SKINCOLOR_RED;
fast->colorized = true;
K_MatchGenericExtraFlags(fast, actor);
}
void A_SPBChase(mobj_t *actor)
{
player_t *player = NULL;
player_t *scplayer = NULL; // secondary target for seeking
UINT8 i;
UINT8 bestrank = UINT8_MAX;
fixed_t dist;
angle_t hang, vang;
fixed_t wspeed, xyspeed, zspeed;
fixed_t pdist = 1536<<FRACBITS; // best player distance when seeking
angle_t pangle; // angle between us and the player
if (LUA_CallAction(A_SPBCHASE, actor))
return;
// Default speed
wspeed = FixedMul(mapobjectscale, K_GetKartSpeedFromStat(5)*2); // Go at twice the average speed a player would be going at!
if (actor->threshold) // Just fired, go straight.
{
actor->lastlook = -1;
actor->cusval = -1;
spbplace = -1;
P_InstaThrust(actor, actor->angle, wspeed);
return;
}
// Find the player with the best rank
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator || players[i].exiting)
continue; // not in-game
/*if (!players[i].mo)
continue; // no mobj
if (players[i].mo->health <= 0)
continue; // dead
if (players[i].respawn.state != RESPAWNST_NONE)
continue;*/ // respawning
if (players[i].position < bestrank)
{
bestrank = players[i].position;
player = &players[i];
}
}
// lastlook = last player num targetted
// cvmem = stored speed
// cusval = next waypoint heap index
// extravalue1 = SPB movement mode
// extravalue2 = mode misc option
if (actor->extravalue1 == 1) // MODE: TARGETING
{
actor->cusval = -1; // Reset waypoint
if (actor->tracer && actor->tracer->health)
{
fixed_t defspeed = wspeed;
fixed_t range = (160*actor->tracer->scale);
fixed_t cx = 0, cy =0;
// Play the intimidating gurgle
if (!S_SoundPlaying(actor, actor->info->activesound))
S_StartSound(actor, actor->info->activesound);
// Maybe we want SPB to target an object later? IDK lol
if (actor->tracer->player)
{
UINT8 fracmax = 32;
UINT8 spark = ((10-actor->tracer->player->kartspeed) + actor->tracer->player->kartweight) / 2;
fixed_t easiness = ((actor->tracer->player->kartspeed + (10-spark)) << FRACBITS) / 2;
actor->lastlook = actor->tracer->player-players; // Save the player num for death scumming...
actor->tracer->player->pflags |= PF_RINGLOCK; // set ring lock
if (actor->tracer->hitlag)
{
// If the player is frozen through no fault of their own, the SPB should be too.
actor->hitlag = actor->tracer->hitlag;
}
if (!P_IsObjectOnGround(actor->tracer))
{
// In the air you have no control; basically don't hit unless you make a near complete stop
defspeed = (7 * actor->tracer->player->speed) / 8;
}
else
{
// 7/8ths max speed for Knuckles, 3/4ths max speed for min accel, exactly max speed for max accel
defspeed = FixedMul(((fracmax+1)<<FRACBITS) - easiness, K_GetKartSpeed(actor->tracer->player, false, false)) / fracmax;
}
// Be fairer on conveyors
cx = actor->tracer->player->cmomx;
cy = actor->tracer->player->cmomy;
// Switch targets if you're no longer 1st for long enough
if (actor->tracer->player->position <= bestrank)
actor->extravalue2 = 7*TICRATE;
else if (actor->extravalue2-- <= 0)
actor->extravalue1 = 0; // back to SEEKING
spbplace = actor->tracer->player->position;
}
dist = P_AproxDistance(P_AproxDistance(actor->x-actor->tracer->x, actor->y-actor->tracer->y), actor->z-actor->tracer->z);
wspeed = FixedMul(defspeed, FRACUNIT + FixedDiv(dist-range, range));
if (wspeed < defspeed)
wspeed = defspeed;
if (wspeed > (3*defspeed)/2)
wspeed = (3*defspeed)/2;
if (wspeed < 20*actor->tracer->scale)
wspeed = 20*actor->tracer->scale;
if (actor->tracer->player->carry == CR_SLIDING)
wspeed = actor->tracer->player->speed/2;
// ^^^^ current section: These are annoying, and grand metropolis in particular needs this.
hang = R_PointToAngle2(actor->x, actor->y, actor->tracer->x, actor->tracer->y);
vang = R_PointToAngle2(0, actor->z, dist, actor->tracer->z);
// Modify stored speed
if (wspeed > actor->cvmem)
actor->cvmem += (wspeed - actor->cvmem) / TICRATE;
else
actor->cvmem = wspeed;
{
// Smoothly rotate horz angle
angle_t input = hang - actor->angle;
boolean invert = (input > ANGLE_180);
if (invert)
input = InvAngle(input);
// Slow down when turning; it looks better and makes U-turns not unfair
xyspeed = FixedMul(actor->cvmem, max(0, (((180<<FRACBITS) - AngleFixed(input)) / 90) - FRACUNIT));
input = FixedAngle(AngleFixed(input)/4);
if (invert)
input = InvAngle(input);
actor->angle += input;
// Smoothly rotate vert angle
input = vang - actor->movedir;
invert = (input > ANGLE_180);
if (invert)
input = InvAngle(input);
// Slow down when turning; might as well do it for momz, since we do it above too
zspeed = FixedMul(actor->cvmem, max(0, (((180<<FRACBITS) - AngleFixed(input)) / 90) - FRACUNIT));
input = FixedAngle(AngleFixed(input)/4);
if (invert)
input = InvAngle(input);
actor->movedir += input;
}
actor->momx = cx + FixedMul(FixedMul(xyspeed, FINECOSINE(actor->angle>>ANGLETOFINESHIFT)), FINECOSINE(actor->movedir>>ANGLETOFINESHIFT));
actor->momy = cy + FixedMul(FixedMul(xyspeed, FINESINE(actor->angle>>ANGLETOFINESHIFT)), FINECOSINE(actor->movedir>>ANGLETOFINESHIFT));
actor->momz = FixedMul(zspeed, FINESINE(actor->movedir>>ANGLETOFINESHIFT));
// Spawn a trail of rings behind the SPB!
SpawnSPBTrailRings(actor);
// Red speed lines for when it's gaining on its target. A tell for when you're starting to lose too much speed!
if (R_PointToDist2(0, 0, actor->momx, actor->momy) > (actor->tracer->player ? (16*actor->tracer->player->speed)/15
: (16*R_PointToDist2(0, 0, actor->tracer->momx, actor->tracer->momy))/15) // Going faster than the target
&& xyspeed > K_GetKartSpeed(actor->tracer->player, false, false) / 4) // Don't display speedup lines at pitifully low speeds
SpawnSPBSpeedLines(actor);
return;
}
else // Target's gone, return to SEEKING
{
P_SetTarget(&actor->tracer, NULL);
actor->extravalue1 = 2; // WAIT...
actor->extravalue2 = 52; // Slightly over the respawn timer length
return;
}
}
else if (actor->extravalue1 == 2) // MODE: WAIT...
{
actor->momx = actor->momy = actor->momz = 0; // Stoooop
actor->cusval = -1; // Reset waypoint
if (actor->lastlook != -1
&& playeringame[actor->lastlook]
&& !players[actor->lastlook].spectator
&& !players[actor->lastlook].exiting)
{
spbplace = players[actor->lastlook].position;
players[actor->lastlook].pflags |= PF_RINGLOCK;
if (actor->extravalue2-- <= 0 && players[actor->lastlook].mo)
{
P_SetTarget(&actor->tracer, players[actor->lastlook].mo);
actor->extravalue1 = 1; // TARGET ACQUIRED
actor->extravalue2 = 7*TICRATE;
actor->cvmem = wspeed;
}
}
else
{
actor->extravalue1 = 0; // SEEKING
actor->extravalue2 = 0;
spbplace = -1;
}
}
else // MODE: SEEKING
{
waypoint_t *lastwaypoint = NULL;
waypoint_t *bestwaypoint = NULL;
waypoint_t *nextwaypoint = NULL;
waypoint_t *tempwaypoint = NULL;
actor->lastlook = -1; // Just make sure this is reset
if (!player || !player->mo || player->mo->health <= 0 || (player->respawn.state != RESPAWNST_NONE))
{
// No one there? Completely STOP.
actor->momx = actor->momy = actor->momz = 0;
if (!player)
spbplace = -1;
return;
}
// Found someone, now get close enough to initiate the slaughter...
P_SetTarget(&actor->tracer, player->mo);
spbplace = bestrank;
dist = P_AproxDistance(P_AproxDistance(actor->x-actor->tracer->x, actor->y-actor->tracer->y), actor->z-actor->tracer->z);
/*
K_GetBestWaypointForMobj returns the waypoint closest to the object that isn't its current waypoint. While this is usually good enough,
in cases where the track overlaps, this means that the SPB will sometimes target a waypoint on an earlier/later portion of the track instead of following along.
For this reason, we're going to try and make sure to avoid these situations.
*/
// Move along the waypoints until you get close enough
if (actor->cusval > -1 && actor->extravalue2 > 0)
{
// Previously set nextwaypoint
lastwaypoint = K_GetWaypointFromIndex((size_t)actor->cusval);
tempwaypoint = K_GetBestWaypointForMobj(actor);
// check if the tempwaypoint corresponds to lastwaypoint's next ID at least;
// This is to avoid situations where the SPB decides to suicide jump down a bridge because it found a COMPLETELY unrelated waypoint down there.
if (K_GetWaypointID(tempwaypoint) == K_GetWaypointNextID(lastwaypoint) || K_GetWaypointID(tempwaypoint) == K_GetWaypointID(lastwaypoint))
// either our previous or curr waypoint ID, sure, take it
bestwaypoint = tempwaypoint;
else
bestwaypoint = K_GetWaypointFromIndex((size_t)actor->extravalue2); // keep going from the PREVIOUS wp.
}
else
bestwaypoint = K_GetBestWaypointForMobj(actor);
if (bestwaypoint == NULL && lastwaypoint == NULL)
{
// We have invalid waypoints all around, so use closest to try and make it non-NULL.
bestwaypoint = K_GetClosestWaypointToMobj(actor);
}
if (bestwaypoint != NULL)
{
const boolean huntbackwards = false;
boolean useshortcuts = false;
// If the player is on a shortcut, use shortcuts. No escape.
if (K_GetWaypointIsShortcut(player->nextwaypoint))
{
useshortcuts = true;
}
nextwaypoint = K_GetNextWaypointToDestination(
bestwaypoint, player->nextwaypoint, useshortcuts, huntbackwards);
}
if (nextwaypoint == NULL && lastwaypoint != NULL)
{
// Restore to the last nextwaypoint
nextwaypoint = lastwaypoint;
}
if (nextwaypoint != NULL)
{
const fixed_t xywaypointdist = P_AproxDistance(
actor->x - nextwaypoint->mobj->x, actor->y - nextwaypoint->mobj->y);
hang = R_PointToAngle2(actor->x, actor->y, nextwaypoint->mobj->x, nextwaypoint->mobj->y);
vang = R_PointToAngle2(0, actor->z, xywaypointdist, nextwaypoint->mobj->z);
actor->cusval = (INT32)K_GetWaypointHeapIndex(nextwaypoint);
actor->extravalue2 = (INT32)K_GetWaypointHeapIndex(bestwaypoint); // save our last best, used above.
}
else
{
// continue straight ahead... Shouldn't happen.
hang = actor->angle;
vang = 0U;
}
{
// Smoothly rotate horz angle
angle_t input = hang - actor->angle;
boolean invert = (input > ANGLE_180);
INT32 turnangle;
if (invert)
input = InvAngle(input);
input = FixedAngle(AngleFixed(input)/8);
// Slow down when turning; it looks better and makes U-turns not unfair
xyspeed = FixedMul(wspeed, max(0, (((180<<FRACBITS) - AngleFixed(input)) / 90) - FRACUNIT));
if (invert)
input = InvAngle(input);
actor->angle += input;
// If input is small enough, spawn dust. Otherwise, spawn a slip tide!
turnangle = AngleFixed(input)/FRACUNIT;
// The SPB is really turning if that value is >= 3 and <= 357. This looks pretty bad check-wise so feel free to change it for something that isn't as terrible.
if (turnangle >= 3 && turnangle <= 357)
SpawnSPBAIZDust(actor, turnangle < 180 ? 1 : -1); // 1 if turning left, -1 if turning right. Angles work counterclockwise, remember!
else
SpawnSPBDust(actor); // if we're mostly going straight, then spawn the V dust cone!
SpawnSPBSpeedLines(actor); // Always spawn speed lines while seeking
// Smoothly rotate vert angle
input = vang - actor->movedir;
invert = (input > ANGLE_180);
if (invert)
input = InvAngle(input);
input = FixedAngle(AngleFixed(input)/8);
// Slow down when turning; might as well do it for momz, since we do it above too
zspeed = FixedMul(wspeed, max(0, (((180<<FRACBITS) - AngleFixed(input)) / 90) - FRACUNIT));
if (invert)
input = InvAngle(input);
actor->movedir += input;
}
actor->momx = FixedMul(FixedMul(xyspeed, FINECOSINE(actor->angle>>ANGLETOFINESHIFT)), FINECOSINE(actor->movedir>>ANGLETOFINESHIFT));
actor->momy = FixedMul(FixedMul(xyspeed, FINESINE(actor->angle>>ANGLETOFINESHIFT)), FINECOSINE(actor->movedir>>ANGLETOFINESHIFT));
actor->momz = FixedMul(zspeed, FINESINE(actor->movedir>>ANGLETOFINESHIFT));
// see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh!
for (i=0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator || players[i].exiting)
continue; // not in-game
if (R_PointToDist2(actor->x, actor->y, players[i].mo->x, players[i].mo->y) < pdist)
{
pdist = R_PointToDist2(actor->x, actor->y, players[i].mo->x, players[i].mo->y);
scplayer = &players[i]; // it doesn't matter if we override this guy now.
}
}
// different player from our main target, try and ram into em~!
if (scplayer && scplayer != player)
{
pangle = actor->angle - R_PointToAngle2(actor->x, actor->y,scplayer->mo->x, scplayer->mo->y);
// check if the angle wouldn't make us LOSE speed...
if ((INT32)pangle/ANG1 >= -80 && (INT32)pangle/ANG1 <= 80) // allow for around 80 degrees
{
// Thrust us towards the guy, try to screw em up!
P_Thrust(actor, R_PointToAngle2(actor->x, actor->y, scplayer->mo->x, scplayer->mo->y), actor->movefactor/4); // not too fast though.
}
}
// Spawn a trail of rings behind the SPB!
SpawnSPBTrailRings(actor);
if (dist <= (1024*actor->tracer->scale) && !(actor->flags2 & MF2_AMBUSH)) // Close enough to target? Use Ambush flag to disable targetting so I can have an easier time testing stuff...
{
S_StartSound(actor, actor->info->attacksound);
actor->extravalue1 = 1; // TARGET ACQUIRED
actor->extravalue2 = 7*TICRATE;
actor->cvmem = wspeed;
}
}
// Finally, no matter what, the spb should not be able to be under the ground, or above the ceiling;
if (actor->z < actor->floorz)
actor->z = actor->floorz;
else if (actor->z > actor->ceilingz - actor->height)
actor->z = actor->ceilingz - actor->height;
return;
}
void A_SSMineSearch(mobj_t *actor)
{
fixed_t dis = INT32_MAX;

View file

@ -352,41 +352,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
}
return;
case MT_SPB:
if ((special->target == toucher || special->target == toucher->target) && (special->threshold > 0))
return;
if (special->health <= 0 || toucher->health <= 0)
return;
if (player->spectator)
return;
if (special->tracer && !P_MobjWasRemoved(special->tracer) && toucher == special->tracer)
{
mobj_t *spbexplode;
if (player->bubbleblowup > 0)
{
K_DropHnextList(player, false);
special->extravalue1 = 2; // WAIT...
special->extravalue2 = 52; // Slightly over the respawn timer length
return;
}
S_StopSound(special); // Don't continue playing the gurgle or the siren
spbexplode = P_SpawnMobj(toucher->x, toucher->y, toucher->z, MT_SPBEXPLOSION);
spbexplode->extravalue1 = 1; // Tell K_ExplodePlayer to use extra knockback
if (special->target && !P_MobjWasRemoved(special->target))
P_SetTarget(&spbexplode->target, special->target);
P_RemoveMobj(special);
Obj_SPBTouch(special, toucher);
return;
}
else
{
P_DamageMobj(player->mo, special, special->target, 1, DMG_NORMAL);
}
return;
case MT_EMERALD:
if (!P_CanPickupItem(player, 0))
return;
@ -2060,6 +2029,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
player->sneakertimer = player->numsneakers = 0;
player->driftboost = player->strongdriftboost = 0;
player->gateBoost = 0;
player->ringboost = 0;
player->glanceDir = 0;
player->pflags &= ~PF_GAINAX;

View file

@ -6661,7 +6661,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
break;
case KITEM_SPB:
case KITEM_SHRINK:
indirectitemcooldown = 20*TICRATE;
K_SetItemCooldown(mobj->threshold, 20*TICRATE);
/* FALLTHRU */
default:
mobj->sprite = SPR_ITEM;
@ -6911,8 +6911,15 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
mobj->threshold--;
break;
case MT_SPB:
indirectitemcooldown = 20*TICRATE;
/* FALLTHRU */
{
Obj_SPBThink(mobj);
break;
}
case MT_MANTARING:
{
Obj_MantaRingThink(mobj);
break;
}
case MT_BALLHOG:
{
mobj_t *ghost = P_SpawnGhostMobj(mobj);
@ -9274,6 +9281,11 @@ static boolean P_FuseThink(mobj_t *mobj)
P_RemoveMobj(mobj);
return false;
}
case MT_SPB:
{
Obj_SPBExplode(mobj);
break;
}
case MT_PLAYER:
break; // don't remove
default:
@ -9310,6 +9322,8 @@ void P_MobjThinker(mobj_t *mobj)
P_SetTarget(&mobj->hnext, NULL);
if (mobj->hprev && P_MobjWasRemoved(mobj->hprev))
P_SetTarget(&mobj->hprev, NULL);
if (mobj->itnext && P_MobjWasRemoved(mobj->itnext))
P_SetTarget(&mobj->itnext, NULL);
if (mobj->flags & MF_NOTHINK)
return;
@ -10710,6 +10724,8 @@ void P_RemoveMobj(mobj_t *mobj)
}
}
P_SetTarget(&mobj->itnext, NULL);
// DBG: set everything in mobj_t to 0xFF instead of leaving it. debug memory error.
#ifdef SCRAMBLE_REMOVED
// Invalidate mobj_t data to cause crashes if accessed!

View file

@ -269,6 +269,9 @@ static void P_NetArchivePlayers(void)
WRITEUINT8(save_p, players[i].driftboost);
WRITEUINT8(save_p, players[i].strongdriftboost);
WRITEUINT16(save_p, players[i].gateBoost);
WRITEUINT8(save_p, players[i].gateSound);
WRITESINT8(save_p, players[i].aizdriftstrat);
WRITEINT32(save_p, players[i].aizdrifttilt);
WRITEINT32(save_p, players[i].aizdriftturn);
@ -563,6 +566,9 @@ static void P_NetUnArchivePlayers(void)
players[i].driftboost = READUINT8(save_p);
players[i].strongdriftboost = READUINT8(save_p);
players[i].gateBoost = READUINT16(save_p);
players[i].gateSound = READUINT8(save_p);
players[i].aizdriftstrat = READSINT8(save_p);
players[i].aizdrifttilt = READINT32(save_p);
players[i].aizdriftturn = READINT32(save_p);
@ -4558,7 +4564,8 @@ static void P_NetArchiveMisc(boolean resending)
WRITEFIXED(save_p, battleovertime.z);
WRITEUINT32(save_p, wantedcalcdelay);
WRITEUINT32(save_p, indirectitemcooldown);
for (i = 0; i < NUMKARTITEMS-1; i++)
WRITEUINT32(save_p, itemCooldowns[i]);
WRITEUINT32(save_p, mapreset);
WRITEUINT8(save_p, spectateGriefed);
@ -4721,7 +4728,8 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
battleovertime.z = READFIXED(save_p);
wantedcalcdelay = READUINT32(save_p);
indirectitemcooldown = READUINT32(save_p);
for (i = 0; i < NUMKARTITEMS-1; i++)
itemCooldowns[i] = READUINT32(save_p);
mapreset = READUINT32(save_p);
spectateGriefed = READUINT8(save_p);

View file

@ -4022,6 +4022,8 @@ static void P_InitPlayers(void)
static void P_InitGametype(void)
{
size_t i;
spectateGriefed = 0;
K_CashInPowerLevels(); // Pushes power level changes even if intermission was skipped
@ -4046,7 +4048,10 @@ static void P_InitGametype(void)
}
wantedcalcdelay = wantedfrequency*2;
indirectitemcooldown = 0;
for (i = 0; i < NUMKARTITEMS-1; i++)
itemCooldowns[i] = 0;
mapreset = 0;
thwompsactive = false;

View file

@ -1925,7 +1925,7 @@ static void K_HandleLapIncrement(player_t *player)
player->startboost = 125;
K_SpawnDriftBoostExplosion(player, 4);
K_SpawnDriftElectricSparks(player);
K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false);
rainbowstartavailable = false;
}

View file

@ -683,8 +683,7 @@ void P_Ticker(boolean run)
if (exitcountdown > 1)
exitcountdown--;
if (indirectitemcooldown > 0)
indirectitemcooldown--;
K_RunItemCooldowns();
K_BossInfoTicker();

View file

@ -2592,7 +2592,7 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius)
if (mo->type == MT_SPB) // If you destroy a SPB, you don't get the luxury of a cooldown.
{
spbplace = -1;
indirectitemcooldown = 0;
itemCooldowns[KITEM_SPB - 1] = 0;
}
if (mo->flags & MF_BOSS) //don't OHKO bosses nor players!

View file

@ -1118,6 +1118,18 @@ sfxinfo_t S_sfx[NUMSFX] =
// Shrink laser beam
{"beam01", false, 32, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""},
// SPB seeking
{"spbska", false, 32, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"spbskb", false, 32, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"spbskc", false, 32, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""},
// Juicebox for SPB
{"gate01", false, 32, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gate02", false, 32, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gate03", false, 32, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gate04", false, 32, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"gate05", false, 32, 64, -1, NULL, 0, -1, -1, LUMPERROR, ""},
// SRB2Kart - Engine sounds
// Engine class A
{"krta00", false, 48, 65, -1, NULL, 0, -1, -1, LUMPERROR, ""},

View file

@ -1183,6 +1183,18 @@ typedef enum
// Shrink laser
sfx_beam01,
// SPB seeking
sfx_spbska,
sfx_spbskb,
sfx_spbskc,
// Juicebox for SPB
sfx_gate01,
sfx_gate02,
sfx_gate03,
sfx_gate04,
sfx_gate05,
// Next up: UNIQUE ENGINE SOUNDS! Hoooooo boy...
// Engine class A - Low Speed, Low Weight
sfx_krta00,