diff --git a/src/d_player.h b/src/d_player.h index 74ac5943f..c31837757 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -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; diff --git a/src/deh_tables.c b/src/deh_tables.c index d0d767412..5f1c88826 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -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", diff --git a/src/doomstat.h b/src/doomstat.h index 95baf7ce4..30578d3e6 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -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; diff --git a/src/g_game.c b/src/g_game.c index f47681e44..8cc5b1f72 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -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 diff --git a/src/info.c b/src/info.c index c14582b98..947b06701 100644 --- a/src/info.c +++ b/src/info.c @@ -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<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) diff --git a/src/k_kart.c b/src/k_kart.c index bc6b49498..5d9faf36f 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -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); // % 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<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<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) { diff --git a/src/k_kart.h b/src/k_kart.h index 98a372188..a3f7dae0c 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -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); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 76a001544..2785f83ac 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -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)); diff --git a/src/k_objects.h b/src/k_objects.h index 775710c1a..d39da4247 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -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*/ diff --git a/src/k_respawn.c b/src/k_respawn.c index 9a8cc38e8..94bb04e18 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -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); diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index ad1669373..85e73a12f 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -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")) diff --git a/src/lua_script.c b/src/lua_script.c index 8a1e307bb..051739976 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -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; diff --git a/src/objects/Sourcefile b/src/objects/Sourcefile index d1e801471..84e263f29 100644 --- a/src/objects/Sourcefile +++ b/src/objects/Sourcefile @@ -1,3 +1,5 @@ hyudoro.c shrink.c item-debris.c +spb.c +manta-ring.c diff --git a/src/objects/manta-ring.c b/src/objects/manta-ring.c new file mode 100644 index 000000000..69f98fc06 --- /dev/null +++ b/src/objects/manta-ring.c @@ -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; +} diff --git a/src/objects/shrink.c b/src/objects/shrink.c index 241164aa7..bd68fa3d4 100644 --- a/src/objects/shrink.c +++ b/src/objects/shrink.c @@ -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) diff --git a/src/objects/spb.c b/src/objects/spb.c new file mode 100644 index 000000000..e939b1b31 --- /dev/null +++ b/src/objects/spb.c @@ -0,0 +1,1024 @@ +// 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 spb.c +/// \brief Self Propelled Bomb item code. + +#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 SPB_SEEKTEST + +#define SPB_SLIPTIDEDELTA (ANG1 * 3) +#define SPB_STEERDELTA (ANGLE_90 - ANG10) +#define SPB_DEFAULTSPEED (FixedMul(mapobjectscale, K_GetKartSpeedFromStat(9) * 2)) +#define SPB_ACTIVEDIST (1024 * FRACUNIT) + +#define SPB_HOTPOTATO (2*TICRATE) +#define SPB_MAXSWAPS (2) +#define SPB_FLASHING (TICRATE) + +#define SPB_CHASETIMESCALE (60*TICRATE) +#define SPB_CHASETIMEMUL (3*FRACUNIT) + +#define SPB_SEEKTURN (FRACUNIT/8) +#define SPB_CHASETURN (FRACUNIT/4) + +#define SPB_MANTA_SPACING (2750 * FRACUNIT) + +#define SPB_MANTA_VSTART (150) +#define SPB_MANTA_VRATE (60) +#define SPB_MANTA_VMAX (100) + +enum +{ + SPB_MODE_SEEK, + SPB_MODE_CHASE, + SPB_MODE_WAIT, +}; + +#define spb_mode(o) ((o)->extravalue1) +#define spb_modetimer(o) ((o)->extravalue2) + +#define spb_nothink(o) ((o)->threshold) +#define spb_intangible(o) ((o)->cvmem) + +#define spb_lastplayer(o) ((o)->lastlook) +#define spb_speed(o) ((o)->movefactor) +#define spb_pitch(o) ((o)->movedir) + +#define spb_chasetime(o) ((o)->watertop) // running out of variables here... +#define spb_swapcount(o) ((o)->health) + +#define spb_curwaypoint(o) ((o)->cusval) + +#define spb_manta_vscale(o) ((o)->movecount) +#define spb_manta_totaldist(o) ((o)->reactiontime) + +#define spb_owner(o) ((o)->target) +#define spb_chase(o) ((o)->tracer) + +static void SPBMantaRings(mobj_t *spb) +{ + fixed_t vScale = INT32_MAX; + fixed_t spacing = INT32_MAX; + fixed_t finalDist = INT32_MAX; + + const fixed_t floatHeight = 24 * spb->scale; + fixed_t floorDist = INT32_MAX; + + if (leveltime % SPB_MANTA_VRATE == 0) + { + spb_manta_vscale(spb) = max(spb_manta_vscale(spb) - 1, SPB_MANTA_VMAX); + } + + spacing = FixedMul(SPB_MANTA_SPACING, spb->scale); + spacing = FixedMul(spacing, K_GetKartGameSpeedScalar(gamespeed)); + + vScale = FixedDiv(spb_manta_vscale(spb) * FRACUNIT, 100 * FRACUNIT); + finalDist = FixedMul(spacing, vScale); + + floorDist = abs(P_GetMobjFeet(spb) - P_GetMobjGround(spb)); + + spb_manta_totaldist(spb) += P_AproxDistance(spb->momx, spb->momy); + + if (spb_manta_totaldist(spb) > finalDist + && floorDist <= floatHeight) + { + spb_manta_totaldist(spb) = 0; + + Obj_MantaRingCreate( + spb, + spb_owner(spb), +#ifdef SPB_SEEKTEST + NULL +#else + spb_chase(spb) +#endif + ); + } +} + +static void SpawnSPBDust(mobj_t *spb) +{ + // 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 = spb->floorz; + angle_t sa = spb->angle - ANG1*60; + INT32 i; + + if (spb->eflags & MFE_VERTICALFLIP) + { + sz = spb->ceilingz; + } + + if ((leveltime & 1) && abs(spb->z - sz) < FRACUNIT*64) // Only every 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 = 96 * FINECOSINE(sa >> ANGLETOFINESHIFT); + sy = 96 * FINESINE(sa >> ANGLETOFINESHIFT); + + dust = P_SpawnMobjFromMobj(spb, sx, sy, 0, MT_SPBDUST); + dust->z = sz; + + dust->momx = spb->momx/2; + dust->momy = spb->momy/2; + dust->momz = spb->momz/2; // Give some of the momentum to the dust + + P_SetScale(dust, spb->scale * 2); + + dust->color = SKINCOLOR_RED; + dust->colorized = true; + + dust->angle = spb->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, spb); + + 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 SpawnSPBSliptide(mobj_t *spb, SINT8 dir) +{ + fixed_t newx; + fixed_t newy; + mobj_t *spark; + angle_t travelangle; + fixed_t sz = spb->floorz; + + if (spb->eflags & MFE_VERTICALFLIP) + { + sz = spb->ceilingz; + } + + travelangle = K_MomentumAngle(spb); + + if ((leveltime & 1) && abs(spb->z - sz) < FRACUNIT*64) + { + newx = P_ReturnThrustX(spb, travelangle - (dir*ANGLE_45), 24*FRACUNIT); + newy = P_ReturnThrustY(spb, travelangle - (dir*ANGLE_45), 24*FRACUNIT); + + spark = P_SpawnMobjFromMobj(spb, newx, newy, 0, MT_SPBDUST); + spark->z = sz; + + P_SetMobjState(spark, S_KARTAIZDRIFTSTRAT); + P_SetTarget(&spark->target, spb); + + spark->colorized = true; + spark->color = SKINCOLOR_RED; + + spark->angle = travelangle + (dir * ANGLE_90); + P_SetScale(spark, (spark->destscale = spb->scale*3/2)); + + spark->momx = (6*spb->momx)/5; + spark->momy = (6*spb->momy)/5; + + K_MatchGenericExtraFlags(spark, spb); + } +} + +// Used for seeking and when SPB is trailing its target from way too close! +static void SpawnSPBSpeedLines(mobj_t *spb) +{ + mobj_t *fast = P_SpawnMobjFromMobj(spb, + P_RandomRange(-24, 24) * FRACUNIT, + P_RandomRange(-24, 24) * FRACUNIT, + (spb->info->height / 2) + (P_RandomRange(-24, 24) * FRACUNIT), + MT_FASTLINE + ); + + P_SetTarget(&fast->target, spb); + fast->angle = K_MomentumAngle(spb); + + fast->color = SKINCOLOR_RED; + fast->colorized = true; + + K_MatchGenericExtraFlags(fast, spb); +} + +static fixed_t SPBDist(mobj_t *a, mobj_t *b) +{ + return P_AproxDistance(P_AproxDistance( + a->x - b->x, + a->y - b->y), + a->z - b->z + ); +} + +static void SPBTurn( + fixed_t destSpeed, angle_t destAngle, + fixed_t *editSpeed, angle_t *editAngle, + fixed_t lerp, SINT8 *returnSliptide) +{ + INT32 delta = AngleDeltaSigned(destAngle, *editAngle); + fixed_t dampen = FRACUNIT; + + // Slow down when turning; it looks better and makes U-turns not unfair + dampen = FixedDiv((180 * FRACUNIT) - AngleFixed(abs(delta)), 180 * FRACUNIT); + *editSpeed = FixedMul(destSpeed, dampen); + + delta = FixedMul(delta, lerp); + + // Calculate sliptide effect during seeking. + if (returnSliptide != NULL) + { + const boolean isSliptiding = (abs(delta) >= SPB_SLIPTIDEDELTA); + SINT8 sliptide = 0; + + if (isSliptiding == true) + { + if (delta < 0) + { + sliptide = -1; + } + else + { + sliptide = 1; + } + } + + *returnSliptide = sliptide; + } + + *editAngle += delta; +} + +static void SetSPBSpeed(mobj_t *spb, fixed_t xySpeed, fixed_t zSpeed) +{ + spb->momx = FixedMul(FixedMul( + xySpeed, + FINECOSINE(spb->angle >> ANGLETOFINESHIFT)), + FINECOSINE(spb_pitch(spb) >> ANGLETOFINESHIFT) + ); + + spb->momy = FixedMul(FixedMul( + xySpeed, + FINESINE(spb->angle >> ANGLETOFINESHIFT)), + FINECOSINE(spb_pitch(spb) >> ANGLETOFINESHIFT) + ); + + spb->momz = FixedMul( + zSpeed, + FINESINE(spb_pitch(spb) >> ANGLETOFINESHIFT) + ); +} + +static boolean SPBSeekSoundPlaying(mobj_t *spb) +{ + return (S_SoundPlaying(spb, sfx_spbska) + || S_SoundPlaying(spb, sfx_spbskb) + || S_SoundPlaying(spb, sfx_spbskc)); +} + +static void SPBSeek(mobj_t *spb, player_t *bestPlayer) +{ + const fixed_t desiredSpeed = SPB_DEFAULTSPEED; + + waypoint_t *curWaypoint = NULL; + waypoint_t *destWaypoint = NULL; + + fixed_t dist = INT32_MAX; + fixed_t activeDist = INT32_MAX; + + fixed_t destX = spb->x; + fixed_t destY = spb->y; + fixed_t destZ = spb->z; + angle_t destAngle = spb->angle; + angle_t destPitch = 0U; + + fixed_t xySpeed = desiredSpeed; + fixed_t zSpeed = desiredSpeed; + SINT8 sliptide = 0; + + fixed_t steerDist = INT32_MAX; + mobj_t *steerMobj = NULL; + + boolean circling = false; + + size_t i; + + spb_lastplayer(spb) = -1; // Just make sure this is reset + + if (bestPlayer == NULL + || bestPlayer->mo == NULL + || P_MobjWasRemoved(bestPlayer->mo) == true + || bestPlayer->mo->health <= 0 + || (bestPlayer->respawn.state != RESPAWNST_NONE)) + { + // No one there? Completely STOP. + spb->momx = spb->momy = spb->momz = 0; + + if (bestPlayer == NULL) + { + spbplace = -1; + } + + return; + } + + // Found someone, now get close enough to initiate the slaughter... + P_SetTarget(&spb_chase(spb), bestPlayer->mo); + spbplace = bestPlayer->position; + + dist = SPBDist(spb, spb_chase(spb)); + activeDist = FixedMul(SPB_ACTIVEDIST, spb_chase(spb)->scale); + + if (spb_swapcount(spb) > SPB_MAXSWAPS + 1) + { + // Too much hot potato. + // Go past our target and explode instead. + if (spb->fuse == 0) + { + spb->fuse = 2*TICRATE; + } + } +#ifndef SPB_SEEKTEST // Easy debug switch + else + { + if (dist <= activeDist) + { + S_StopSound(spb); + S_StartSound(spb, spb->info->attacksound); + + spb_mode(spb) = SPB_MODE_CHASE; // TARGET ACQUIRED + spb_swapcount(spb)++; + + spb_modetimer(spb) = SPB_HOTPOTATO; + spb_intangible(spb) = SPB_FLASHING; + + spb_speed(spb) = desiredSpeed; + return; + } + } +#endif + + if (SPBSeekSoundPlaying(spb) == false) + { + if (dist <= activeDist * 3) + { + S_StartSound(spb, sfx_spbskc); + } + else if (dist <= activeDist * 6) + { + S_StartSound(spb, sfx_spbskb); + } + else + { + S_StartSound(spb, sfx_spbska); + } + } + + // Move along the waypoints until you get close enough + if (spb_curwaypoint(spb) == -1) + { + // Determine first waypoint. + curWaypoint = K_GetBestWaypointForMobj(spb); + spb_curwaypoint(spb) = (INT32)K_GetWaypointHeapIndex(curWaypoint); + } + else + { + curWaypoint = K_GetWaypointFromIndex( (size_t)spb_curwaypoint(spb) ); + } + + destWaypoint = bestPlayer->nextwaypoint; + + if (curWaypoint != NULL) + { + fixed_t waypointDist = INT32_MAX; + fixed_t waypointRad = INT32_MAX; + + destX = curWaypoint->mobj->x; + destY = curWaypoint->mobj->y; + destZ = curWaypoint->mobj->z; + + waypointDist = R_PointToDist2(spb->x, spb->y, destX, destY) / mapobjectscale; + waypointRad = max(curWaypoint->mobj->radius / mapobjectscale, DEFAULT_WAYPOINT_RADIUS); + + if (waypointDist <= waypointRad) + { + boolean pathfindsuccess = false; + + if (destWaypoint != NULL) + { + // Go to next waypoint. + const boolean useshortcuts = K_GetWaypointIsShortcut(destWaypoint); // If the player is on a shortcut, use shortcuts. No escape. + boolean huntbackwards = false; + path_t pathtoplayer = {0}; + + pathfindsuccess = K_PathfindToWaypoint( + curWaypoint, destWaypoint, + &pathtoplayer, + useshortcuts, huntbackwards + ); + + if (pathfindsuccess == true) + { +#ifdef SPB_SEEKTEST + if (pathtoplayer.numnodes > 1) + { + // Go to the next waypoint. + curWaypoint = (waypoint_t *)pathtoplayer.array[1].nodedata; + } + else if (destWaypoint->numnextwaypoints > 0) + { + // Run ahead. + curWaypoint = destWaypoint->nextwaypoints[0]; + } + else + { + // Sort of wait at the player's dest waypoint. + circling = true; + curWaypoint = destWaypoint; + } +#else + path_t reversepath = {0}; + boolean reversesuccess = false; + + huntbackwards = true; + reversesuccess = K_PathfindToWaypoint( + curWaypoint, destWaypoint, + &reversepath, + useshortcuts, huntbackwards + ); + + if (reversesuccess == true + && reversepath.totaldist < pathtoplayer.totaldist) + { + // It's faster to go backwards than to chase forward. + // Keep curWaypoint the same, so the SPB waits around for them. + circling = true; + } + else if (pathtoplayer.numnodes > 1) + { + // Go to the next waypoint. + curWaypoint = (waypoint_t *)pathtoplayer.array[1].nodedata; + } + else if (spb->fuse > 0 && destWaypoint->numnextwaypoints > 0) + { + // Run ahead. + curWaypoint = destWaypoint->nextwaypoints[0]; + } + else + { + // Sort of wait at the player's dest waypoint. + circling = true; + curWaypoint = destWaypoint; + } + + if (reversesuccess == true) + { + Z_Free(reversepath.array); + } +#endif + + Z_Free(pathtoplayer.array); + } + } + + if (pathfindsuccess == true && curWaypoint != NULL) + { + // Update again + spb_curwaypoint(spb) = (INT32)K_GetWaypointHeapIndex(curWaypoint); + destX = curWaypoint->mobj->x; + destY = curWaypoint->mobj->y; + destZ = curWaypoint->mobj->z; + } + else + { + spb_curwaypoint(spb) = -1; + destX = spb_chase(spb)->x; + destY = spb_chase(spb)->y; + destZ = spb_chase(spb)->z; + } + } + } + else + { + spb_curwaypoint(spb) = -1; + destX = spb_chase(spb)->x; + destY = spb_chase(spb)->y; + destZ = spb_chase(spb)->z; + } + + destAngle = R_PointToAngle2(spb->x, spb->y, destX, destY); + destPitch = R_PointToAngle2(0, spb->z, P_AproxDistance(spb->x - destX, spb->y - destY), destZ); + + SPBTurn(desiredSpeed, destAngle, &xySpeed, &spb->angle, SPB_SEEKTURN, &sliptide); + SPBTurn(desiredSpeed, destPitch, &zSpeed, &spb_pitch(spb), SPB_SEEKTURN, NULL); + + SetSPBSpeed(spb, xySpeed, zSpeed); + + // see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh! + steerDist = 1536 * mapobjectscale; + + for (i = 0; i < MAXPLAYERS; i++) + { + fixed_t ourDist = INT32_MAX; + INT32 ourDelta = INT32_MAX; + + if (playeringame[i] == false || players[i].spectator == true) + { + // Not in-game + continue; + } + + if (players[i].mo == NULL || P_MobjWasRemoved(players[i].mo) == true) + { + // Invalid mobj + continue; + } + + ourDelta = AngleDelta(spb->angle, R_PointToAngle2(spb->x, spb->y, players[i].mo->x, players[i].mo->y)); + if (ourDelta > SPB_STEERDELTA) + { + // Check if the angle wouldn't make us LOSE speed. + continue; + } + + ourDist = R_PointToDist2(spb->x, spb->y, players[i].mo->x, players[i].mo->y); + if (ourDist < steerDist) + { + steerDist = ourDist; + steerMobj = players[i].mo; // it doesn't matter if we override this guy now. + } + } + + // different player from our main target, try and ram into em~! + if (steerMobj != NULL && steerMobj != spb_chase(spb)) + { + P_Thrust(spb, R_PointToAngle2(spb->x, spb->y, steerMobj->x, steerMobj->y), spb_speed(spb) / 4); + } + + if (sliptide != 0) + { + // 1 if turning left, -1 if turning right. + // Angles work counterclockwise, remember! + SpawnSPBSliptide(spb, sliptide); + } + else + { + // if we're mostly going straight, then spawn the V dust cone! + SpawnSPBDust(spb); + } + + // Always spawn speed lines while seeking + SpawnSPBSpeedLines(spb); + + // Don't run this while we're circling around one waypoint intentionally. + if (circling == false) + { + // Spawn a trail of rings behind the SPB! + SPBMantaRings(spb); + } +} + +static void SPBChase(mobj_t *spb, player_t *bestPlayer) +{ + fixed_t baseSpeed = 0; + fixed_t maxSpeed = 0; + fixed_t desiredSpeed = 0; + + fixed_t range = INT32_MAX; + fixed_t cx = 0, cy = 0; + + fixed_t dist = INT32_MAX; + angle_t destAngle = spb->angle; + angle_t destPitch = 0U; + fixed_t xySpeed = 0; + fixed_t zSpeed = 0; + + mobj_t *chase = NULL; + player_t *chasePlayer = NULL; + + spb_curwaypoint(spb) = -1; // Reset waypoint + + chase = spb_chase(spb); + + if (chase == NULL || P_MobjWasRemoved(chase) == true || chase->health <= 0) + { + P_SetTarget(&spb_chase(spb), NULL); + spb_mode(spb) = SPB_MODE_WAIT; + spb_modetimer(spb) = 55; // Slightly over the respawn timer length + return; + } + + if (chase->hitlag) + { + // If the player is frozen, the SPB should be too. + spb->hitlag = max(spb->hitlag, chase->hitlag); + return; + } + + // Increment chase time + spb_chasetime(spb)++; + + baseSpeed = SPB_DEFAULTSPEED; + range = (160 * chase->scale); + + // Play the intimidating gurgle + if (S_SoundPlaying(spb, spb->info->activesound) == false) + { + S_StartSound(spb, spb->info->activesound); + } + + // Maybe we want SPB to target an object later? IDK lol + chasePlayer = chase->player; + if (chasePlayer != NULL) + { + UINT8 fracmax = 32; + UINT8 spark = ((10 - chasePlayer->kartspeed) + chasePlayer->kartweight) / 2; + fixed_t easiness = ((chasePlayer->kartspeed + (10 - spark)) << FRACBITS) / 2; + + spb_lastplayer(spb) = chasePlayer - players; // Save the player num for death scumming... + spbplace = chasePlayer->position; + + chasePlayer->pflags |= PF_RINGLOCK; // set ring lock + + if (P_IsObjectOnGround(chase) == false) + { + // In the air you have no control; basically don't hit unless you make a near complete stop + baseSpeed = (7 * chasePlayer->speed) / 8; + } + else + { + // 7/8ths max speed for Knuckles, 3/4ths max speed for min accel, exactly max speed for max accel + baseSpeed = FixedMul( + ((fracmax+1) << FRACBITS) - easiness, + K_GetKartSpeed(chasePlayer, false, false) + ) / fracmax; + } + + if (chasePlayer->carry == CR_SLIDING) + { + baseSpeed = chasePlayer->speed/2; + } + + // Be fairer on conveyors + cx = chasePlayer->cmomx; + cy = chasePlayer->cmomy; + + // Switch targets if you're no longer 1st for long enough + if (bestPlayer != NULL && chasePlayer->position <= bestPlayer->position) + { + spb_modetimer(spb) = SPB_HOTPOTATO; + } + else + { + if (spb_modetimer(spb) > 0) + { + spb_modetimer(spb)--; + } + + if (spb_modetimer(spb) <= 0) + { + spb_mode(spb) = SPB_MODE_SEEK; // back to SEEKING + spb_intangible(spb) = SPB_FLASHING; + } + } + } + + dist = P_AproxDistance(P_AproxDistance(spb->x - chase->x, spb->y - chase->y), spb->z - chase->z); + + desiredSpeed = FixedMul(baseSpeed, FRACUNIT + FixedDiv(dist - range, range)); + + if (desiredSpeed < baseSpeed) + { + desiredSpeed = baseSpeed; + } + + maxSpeed = (baseSpeed * 3) / 2; + if (desiredSpeed > maxSpeed) + { + desiredSpeed = maxSpeed; + } + + if (desiredSpeed < 20 * chase->scale) + { + desiredSpeed = 20 * chase->scale; + } + + if (chasePlayer != NULL && chasePlayer->carry == CR_SLIDING) + { + // Hack for current sections to make them fair. + desiredSpeed = min(desiredSpeed, chasePlayer->speed / 2); + } + + destAngle = R_PointToAngle2(spb->x, spb->y, chase->x, chase->y); + destPitch = R_PointToAngle2(0, spb->z, P_AproxDistance(spb->x - chase->x, spb->y - chase->y), chase->z); + + // Modify stored speed + if (desiredSpeed > spb_speed(spb)) + { + spb_speed(spb) += (desiredSpeed - spb_speed(spb)) / TICRATE; + } + else + { + spb_speed(spb) = desiredSpeed; + } + + SPBTurn(spb_speed(spb), destAngle, &xySpeed, &spb->angle, SPB_CHASETURN, NULL); + SPBTurn(spb_speed(spb), destPitch, &zSpeed, &spb_pitch(spb), SPB_CHASETURN, NULL); + + SetSPBSpeed(spb, xySpeed, zSpeed); + spb->momx += cx; + spb->momy += cy; + + // Spawn a trail of rings behind the SPB! + SPBMantaRings(spb); + + // 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, spb->momx, spb->momy) > (16 * R_PointToDist2(0, 0, chase->momx, chase->momy)) / 15 // Going faster than the target + && xySpeed > 20 * mapobjectscale) // Don't display speedup lines at pitifully low speeds + { + SpawnSPBSpeedLines(spb); + } +} + +static void SPBWait(mobj_t *spb) +{ + player_t *oldPlayer = NULL; + + spb->momx = spb->momy = spb->momz = 0; // Stoooop + spb_curwaypoint(spb) = -1; // Reset waypoint + + if (spb_lastplayer(spb) != -1 + && playeringame[spb_lastplayer(spb)] == true) + { + oldPlayer = &players[spb_lastplayer(spb)]; + } + + if (oldPlayer != NULL + && oldPlayer->spectator == false + && oldPlayer->exiting > 0) + { + spbplace = oldPlayer->position; + oldPlayer->pflags |= PF_RINGLOCK; + } + + if (spb_modetimer(spb) > 0) + { + spb_modetimer(spb)--; + } + + if (spb_modetimer(spb) <= 0) + { + if (oldPlayer != NULL) + { + if (oldPlayer->mo != NULL && P_MobjWasRemoved(oldPlayer->mo) == false) + { + P_SetTarget(&spb_chase(spb), oldPlayer->mo); + spb_mode(spb) = SPB_MODE_CHASE; + spb_modetimer(spb) = SPB_HOTPOTATO; + spb_intangible(spb) = SPB_FLASHING; + spb_speed(spb) = SPB_DEFAULTSPEED; + } + } + else + { + spb_mode(spb) = SPB_MODE_SEEK; + spb_modetimer(spb) = 0; + spb_intangible(spb) = SPB_FLASHING; + spbplace = -1; + } + } +} + +void Obj_SPBThink(mobj_t *spb) +{ + mobj_t *ghost = NULL; + player_t *bestPlayer = NULL; + UINT8 bestRank = UINT8_MAX; + size_t i; + + if (spb->health <= 0) + { + return; + } + + K_SetItemCooldown(KITEM_SPB, 20*TICRATE); + + ghost = P_SpawnGhostMobj(spb); + ghost->fuse = 3; + + if (spb_owner(spb) != NULL && P_MobjWasRemoved(spb_owner(spb)) == false && spb_owner(spb)->player != NULL) + { + ghost->color = spb_owner(spb)->player->skincolor; + ghost->colorized = true; + } + + if (spb_nothink(spb) > 0) + { + // Init values, don't think yet. + spb_lastplayer(spb) = -1; + spb_curwaypoint(spb) = -1; + spb_chasetime(spb) = 0; + spbplace = -1; + + spb_manta_totaldist(spb) = 0; // 30000? + spb_manta_vscale(spb) = SPB_MANTA_VSTART; + + P_InstaThrust(spb, spb->angle, SPB_DEFAULTSPEED); + + spb_nothink(spb)--; + } + else + { + // Find the player with the best rank + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player = NULL; + + if (playeringame[i] == false) + { + // Not valid + continue; + } + + player = &players[i]; + + if (player->spectator == true || player->exiting > 0) + { + // Not playing + continue; + } + + /* + if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) + { + // No mobj + continue; + } + + if (player->mo <= 0) + { + // Dead + continue; + } + + if (player->respawn.state != RESPAWNST_NONE) + { + // Respawning + continue; + } + */ + + if (player->position < bestRank) + { + bestRank = player->position; + bestPlayer = player; + } + } + + switch (spb_mode(spb)) + { + case SPB_MODE_SEEK: + default: + SPBSeek(spb, bestPlayer); + break; + + case SPB_MODE_CHASE: + SPBChase(spb, bestPlayer); + break; + + case SPB_MODE_WAIT: + SPBWait(spb); + break; + } + } + + // Flash on/off when intangible. + if (spb_intangible(spb) > 0) + { + spb_intangible(spb)--; + + if (spb_intangible(spb) & 1) + { + spb->renderflags |= RF_DONTDRAW; + } + else + { + spb->renderflags &= ~RF_DONTDRAW; + } + } + + // Flash white when about to explode! + if (spb->fuse > 0) + { + if (spb->fuse & 1) + { + spb->color = SKINCOLOR_INVINCFLASH; + spb->colorized = true; + } + else + { + spb->color = SKINCOLOR_NONE; + spb->colorized = false; + } + } + + // Clamp within level boundaries. + if (spb->z < spb->floorz) + { + spb->z = spb->floorz; + } + else if (spb->z > spb->ceilingz - spb->height) + { + spb->z = spb->ceilingz - spb->height; + } +} + +void Obj_SPBExplode(mobj_t *spb) +{ + mobj_t *spbExplode = NULL; + + // Don't continue playing the gurgle or the siren + S_StopSound(spb); + + spbExplode = P_SpawnMobjFromMobj(spb, 0, 0, 0, MT_SPBEXPLOSION); + + if (spb_owner(spb) != NULL && P_MobjWasRemoved(spb_owner(spb)) == false) + { + P_SetTarget(&spbExplode->target, spb_owner(spb)); + } + + // Tell the explosion to use alternate knockback. + spbExplode->movefactor = ((SPB_CHASETIMESCALE - spb_chasetime(spb)) * SPB_CHASETIMEMUL) / SPB_CHASETIMESCALE; + + P_RemoveMobj(spb); +} + +void Obj_SPBTouch(mobj_t *spb, mobj_t *toucher) +{ + player_t *player = toucher->player; + + if (spb_intangible(spb) > 0) + { + return; + } + + if ((spb_owner(spb) == toucher || spb_owner(spb) == toucher->target) + && (spb_nothink(spb) > 0)) + { + return; + } + + if (spb->health <= 0 || toucher->health <= 0) + { + return; + } + + if (player->spectator == true) + { + return; + } + + if (player->bubbleblowup > 0) + { + // Stun the SPB, and remove the shield. + K_DropHnextList(player, false); + spb_mode(spb) = SPB_MODE_WAIT; + spb_modetimer(spb) = 55; // Slightly over the respawn timer length + return; + } + + if (spb_chase(spb) != NULL && P_MobjWasRemoved(spb_chase(spb)) == false + && toucher == spb_chase(spb)) + { + // Cause the explosion. + Obj_SPBExplode(spb); + return; + } + else + { + // Regular spinout, please. + P_DamageMobj(toucher, spb, spb_owner(spb), 1, DMG_NORMAL); + } +} diff --git a/src/p_enemy.c b/src/p_enemy.c index 90ab62e29..74170980c 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -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<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)<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<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<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<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<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; diff --git a/src/p_inter.c b/src/p_inter.c index 4b2b8381a..b11c269e0 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -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; diff --git a/src/p_mobj.c b/src/p_mobj.c index 16d07dfa1..034d9c6e0 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -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! diff --git a/src/p_saveg.c b/src/p_saveg.c index f1e51d3fb..a70ac6257 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -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); diff --git a/src/p_setup.c b/src/p_setup.c index 762bf0b1b..f209d14c3 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -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; diff --git a/src/p_spec.c b/src/p_spec.c index 681e5e8e9..3e8edb2b4 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -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; } diff --git a/src/p_tick.c b/src/p_tick.c index 1f396a5fd..708ed6ca8 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -683,8 +683,7 @@ void P_Ticker(boolean run) if (exitcountdown > 1) exitcountdown--; - if (indirectitemcooldown > 0) - indirectitemcooldown--; + K_RunItemCooldowns(); K_BossInfoTicker(); diff --git a/src/p_user.c b/src/p_user.c index 8d3a24bfe..e74a3e986 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -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! diff --git a/src/sounds.c b/src/sounds.c index 1cff40633..5abb9504b 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -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, ""}, diff --git a/src/sounds.h b/src/sounds.h index cb9d3b671..5194d1114 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -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,