// SONIC ROBO BLAST 2 KART ~ ZarroTsu //----------------------------------------------------------------------------- /// \file k_kart.c /// \brief SRB2kart general. /// All of the SRB2kart-unique stuff. #include "k_kart.h" #include "d_netcmd.h" #include "d_player.h" #include "doomstat.h" #include "info.h" #include "k_battle.h" #include "k_boss.h" #include "k_pwrlv.h" #include "k_color.h" #include "doomdef.h" #include "hu_stuff.h" #include "g_game.h" #include "m_fixed.h" #include "m_random.h" #include "p_local.h" #include "p_mobj.h" #include "p_slopes.h" #include "p_setup.h" #include "r_defs.h" #include "r_draw.h" #include "r_local.h" #include "s_sound.h" #include "st_stuff.h" #include "typedef.h" #include "v_video.h" #include "z_zone.h" #include "m_misc.h" #include "m_cond.h" #include "f_finale.h" #include "lua_hud.h" // For Lua hud checks #include "lua_hook.h" // For MobjDamage and ShouldDamage #include "m_cheat.h" // objectplacing #include "p_spec.h" #include "k_stats.h" #include "k_waypoint.h" #include "k_bot.h" #include "k_hud.h" #include "k_terrain.h" #include "k_director.h" #include "k_collide.h" #include "k_follower.h" #include "k_grandprix.h" consvar_t cv_kartstacking_colorflame = CVAR_INIT ("kartstacking_colorflame", "On", 0, CV_OnOff, NULL); consvar_t cv_kartstacking_sneakerstacksound = CVAR_INIT ("kartstacking_sneakerstacksound", "On", 0, CV_OnOff, NULL); consvar_t cv_kartchainingsound = CVAR_INIT ("kartchaining_chainsound", "On", 0, CV_OnOff, NULL); consvar_t cv_kartdriftsounds = CVAR_INIT ("kartdriftsounds", "On", 0, CV_OnOff, NULL); consvar_t cv_kartdriftefx = CVAR_INIT ("kartdriftefx", "On", 0, CV_OnOff, NULL); // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: // gamespeed is cc (0 for easy, 1 for normal, 2 for hard) // franticitems is Frantic Mode items, bool // 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_TimerInit(void) { UINT8 i; UINT8 numPlayers = 0;//, numspec = 0; starttime = introtime = 0; if (!bossinfo.boss) { for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) { continue; } if (players[i].spectator == true) { //numspec++; continue; } numPlayers++; } introtime = (108) + 5; // 108 for rotation, + 5 for white fade starttime = 6*TICRATE + (3*TICRATE/4); if (gametyperules & GTR_FREEROAM) { introtime = 0; starttime = 0; } } // NOW you can try to setup Item Breaker, if there's not enough players for a match K_BattleInit(); timelimitintics = extratimeintics = secretextratime = 0; if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss) { if (!K_CanChangeRules()) { if (grandprixinfo.gp) { timelimitintics = (20*TICRATE); } else { timelimitintics = timelimits[gametype] * (60*TICRATE); } } else #ifndef TESTOVERTIMEINFREEPLAY if (!itembreaker) #endif { timelimitintics = cv_timelimit.value * (60*TICRATE); } } } UINT32 K_GetPlayerDontDrawFlag(player_t *player) { UINT32 flag = 0; if (player == &players[displayplayers[0]]) flag = RF_DONTDRAWP1; else if (r_splitscreen >= 1 && player == &players[displayplayers[1]]) flag = RF_DONTDRAWP2; else if (r_splitscreen >= 2 && player == &players[displayplayers[2]]) flag = RF_DONTDRAWP3; else if (r_splitscreen >= 3 && player == &players[displayplayers[3]]) flag = RF_DONTDRAWP4; return flag; } // Angle reflection used by springs & speed pads angle_t K_ReflectAngle(angle_t yourangle, angle_t theirangle, fixed_t yourspeed, fixed_t theirspeed) { INT32 angoffset; boolean subtract = false; angoffset = yourangle - theirangle; if ((angle_t)angoffset > ANGLE_180) { // Flip on wrong side angoffset = InvAngle((angle_t)angoffset); subtract = !subtract; } // Fix going directly against the spring's angle sending you the wrong way if ((angle_t)angoffset > ANGLE_90) { angoffset = ANGLE_180 - angoffset; } // Offset is reduced to cap it (90 / 2 = max of 45 degrees) angoffset /= 2; // Reduce further based on how slow your speed is compared to the spring's speed // (set both to 0 to ignore this) if (theirspeed != 0 && yourspeed != 0) { if (theirspeed > yourspeed) { angoffset = FixedDiv(angoffset, FixedDiv(theirspeed, yourspeed)); } } if (subtract) angoffset = (signed)(theirangle) - angoffset; else angoffset = (signed)(theirangle) + angoffset; return (angle_t)angoffset; } //{ SRB2kart Net Variables void K_RegisterKartStuff(void) { CV_RegisterVar(&cv_sneaker); CV_RegisterVar(&cv_rocketsneaker); CV_RegisterVar(&cv_invincibility); CV_RegisterVar(&cv_banana); CV_RegisterVar(&cv_eggmanmonitor); CV_RegisterVar(&cv_orbinaut); CV_RegisterVar(&cv_jawz); CV_RegisterVar(&cv_mine); CV_RegisterVar(&cv_ballhog); CV_RegisterVar(&cv_selfpropelledbomb); CV_RegisterVar(&cv_grow); CV_RegisterVar(&cv_shrink); CV_RegisterVar(&cv_thundershield); CV_RegisterVar(&cv_hyudoro); CV_RegisterVar(&cv_pogospring); CV_RegisterVar(&cv_kitchensink); // Nu-ITEMS CV_RegisterVar(&cv_superring); CV_RegisterVar(&cv_landmine); CV_RegisterVar(&cv_bubbleshield); CV_RegisterVar(&cv_flameshield); CV_RegisterVar(&cv_droptarget); CV_RegisterVar(&cv_dualsneaker); CV_RegisterVar(&cv_triplesneaker); CV_RegisterVar(&cv_triplebanana); CV_RegisterVar(&cv_decabanana); CV_RegisterVar(&cv_tripleorbinaut); CV_RegisterVar(&cv_quadorbinaut); CV_RegisterVar(&cv_dualjawz); CV_RegisterVar(&cv_kartminimap); CV_RegisterVar(&cv_kartcheck); CV_RegisterVar(&cv_kartinvinsfx); CV_RegisterVar(&cv_kartspeed); CV_RegisterVar(&cv_kartbattlespeed); CV_RegisterVar(&cv_kartbumpers); CV_RegisterVar(&cv_kartfrantic); CV_RegisterVar(&cv_kartcomeback); CV_RegisterVar(&cv_kartencore); CV_RegisterVar(&cv_kartvoterulechanges); CV_RegisterVar(&cv_kartgametypepreference); CV_RegisterVar(&cv_kartspeedometer); CV_RegisterVar(&cv_kartvoices); CV_RegisterVar(&cv_kartbot); CV_RegisterVar(&cv_forcebots); CV_RegisterVar(&cv_botcontrol); CV_RegisterVar(&cv_karteliminatelast); CV_RegisterVar(&cv_kartusepwrlv); CV_RegisterVar(&cv_votetime); CV_RegisterVar(&cv_kartdebugitem); CV_RegisterVar(&cv_kartdebugamount); CV_RegisterVar(&cv_kartallowgiveitem); CV_RegisterVar(&cv_kartdebugdistribution); CV_RegisterVar(&cv_kartdebughuddrop); CV_RegisterVar(&cv_kartdebugwaypoints); CV_RegisterVar(&cv_kartdebuglap); CV_RegisterVar(&cv_kartdebugbotpredict); CV_RegisterVar(&cv_kartdebugcheckpoint); CV_RegisterVar(&cv_kartdebugnodes); CV_RegisterVar(&cv_kartdebugcolorize); CV_RegisterVar(&cv_kartdebugdirector); // HUD cvars K_RegisterKartHUDStuff(); CV_RegisterVar(&cv_stagetitle); CV_RegisterVar(&cv_lessflicker); CV_RegisterVar(&cv_kartrings); // Stacking CV_RegisterVar(&cv_kartstacking); CV_RegisterVar(&cv_kartstacking_calc_arg_offset); CV_RegisterVar(&cv_kartstacking_speedboostdropoff); CV_RegisterVar(&cv_kartstacking_speedboostdropoff_brake); CV_RegisterVar(&cv_kartstacking_colorflame); CV_RegisterVar(&cv_kartstacking_sneakerstacksound); // Vanilla Boosts CV_RegisterVar(&cv_kartstacking_sneaker_easyspeedboost); CV_RegisterVar(&cv_kartstacking_sneaker_normalspeedboost); CV_RegisterVar(&cv_kartstacking_sneaker_hardspeedboost); CV_RegisterVar(&cv_kartstacking_sneaker_accelboost); CV_RegisterVar(&cv_kartstacking_sneaker_maxgrade); CV_RegisterVar(&cv_kartstacking_invincibility_speedboost); CV_RegisterVar(&cv_kartstacking_invincibility_accelboost); CV_RegisterVar(&cv_kartstacking_grow_speedboost); CV_RegisterVar(&cv_kartstacking_grow_accelboost); CV_RegisterVar(&cv_kartstacking_flame_speedval); CV_RegisterVar(&cv_kartstacking_flame_accelboost); CV_RegisterVar(&cv_kartstacking_start_speedboost); CV_RegisterVar(&cv_kartstacking_start_accelboost); CV_RegisterVar(&cv_kartstacking_drift_speedboost); CV_RegisterVar(&cv_kartstacking_drift_accelboost); CV_RegisterVar(&cv_kartstacking_ring_speedboost); CV_RegisterVar(&cv_kartstacking_ring_accelboost); CV_RegisterVar(&cv_kartchaining); CV_RegisterVar(&cv_kartchainingoffroad); CV_RegisterVar(&cv_kartchainingsound); CV_RegisterVar(&cv_kartitembreaker); CV_RegisterVar(&cv_kartwalltransfer); CV_RegisterVar(&cv_kartpurpledrift); CV_RegisterVar(&cv_kartbumpspark); CV_RegisterVar(&cv_kartbumpspring); CV_RegisterVar(&cv_kartslipdash); CV_RegisterVar(&cv_kartdriftsounds); CV_RegisterVar(&cv_kartdriftefx); } //} boolean K_IsPlayerLosing(player_t *player) { INT32 winningpos = 1; UINT8 i, pcount = 0; if (itembreaker && numgotboxes == 0) return true; // Didn't even TRY? if (itembreaker || bossinfo.boss) return (player->bumper <= 0); // anything short of DNF is COOL if (player->position == 1) return false; for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; if (players[i].position > pcount) pcount = players[i].position; } if (pcount <= 1) return false; winningpos = pcount/2; if (pcount % 2) // any remainder? winningpos++; return (player->position > winningpos); } fixed_t K_GetKartGameSpeedScalar(SINT8 value) { // Easy = 81.25% // Normal = 100% // Hard = 118.75% // Nightmare = 137.5% ?!?! return ((13 + (3*value)) << FRACBITS) / 16; } //{ SRB2kart Roulette Code - Position Based consvar_t *KartItemCVars[NUMKARTRESULTS-1] = { &cv_sneaker, &cv_rocketsneaker, &cv_invincibility, &cv_banana, &cv_eggmanmonitor, &cv_orbinaut, &cv_jawz, &cv_mine, &cv_ballhog, &cv_selfpropelledbomb, &cv_grow, &cv_shrink, &cv_thundershield, &cv_hyudoro, &cv_pogospring, &cv_kitchensink, &cv_superring, &cv_landmine, &cv_bubbleshield, &cv_flameshield, &cv_droptarget, &cv_dualsneaker, &cv_triplesneaker, &cv_triplebanana, &cv_decabanana, &cv_tripleorbinaut, &cv_quadorbinaut, &cv_dualjawz }; #define NUMKARTODDS 80 // Less ugly 2D arrays static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = { //B C D E F G H I { 0, 0, 4, 6, 7, 0, 0, 0 }, // Sneaker { 0, 0, 0, 0, 1, 4, 6, 3 }, // Rocket Sneaker { 0, 0, 0, 0, 1, 4, 6,10 }, // Invincibility { 9, 4, 2, 1, 0, 0, 0, 0 }, // Banana { 3, 2, 1, 0, 0, 0, 0, 0 }, // Eggman Monitor { 5, 6, 4, 2, 0, 0, 0, 0 }, // Orbinaut { 0, 3, 2, 1, 1, 0, 0, 0 }, // Jawz { 0, 2, 2, 1, 0, 0, 0, 0 }, // Mine { 0, 0, 2, 1, 0, 0, 0, 0 }, // Ballhog { 0, 1, 2, 3, 4, 2, 2, 0 }, // Self-Propelled Bomb { 0, 0, 0, 0, 0, 2, 5, 7 }, // Grow { 0, 0, 0, 0, 0, 0, 1, 0 }, // Shrink { 1, 2, 0, 0, 0, 0, 0, 0 }, // Thunder Shield { 0, 0, 0, 1, 2, 1, 0, 0 }, // Hyudoro { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink { 1, 2, 2, 0, 0, 0, 0, 0 }, // Super Ring { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield { 0, 0, 0, 0, 0, 2, 4, 5 }, // Flame Shield { 1, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target { 0, 0, 2, 2, 2, 0, 0, 0 }, // Sneaker x2 { 0, 0, 0, 1, 6,10, 5, 0 }, // Sneaker x3 { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 { 0, 0, 0, 1, 0, 0, 0, 0 }, // Banana x10 { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 { 0, 0, 0, 1, 1, 0, 0, 0 }, // Orbinaut x4 { 0, 0, 1, 2, 0, 0, 0, 0 } // Jawz x2 }; static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = { //K L { 2, 1 }, // Sneaker { 0, 0 }, // Rocket Sneaker { 4, 1 }, // Invincibility { 0, 0 }, // Banana { 1, 0 }, // Eggman Monitor { 8, 0 }, // Orbinaut { 8, 1 }, // Jawz { 6, 1 }, // Mine { 2, 1 }, // Ballhog { 0, 0 }, // Self-Propelled Bomb { 2, 1 }, // Grow { 0, 0 }, // Shrink { 4, 0 }, // Thunder Shield { 2, 0 }, // Hyudoro { 3, 0 }, // Pogo Spring { 0, 0 }, // Kitchen Sink { 0, 0 }, // Super Ring { 2, 0 }, // Land Mine { 1, 0 }, // Bubble Shield { 1, 0 }, // Flame Shield { 2, 0 }, // Drop Target { 0, 0 }, // Sneaker x2 { 0, 1 }, // Sneaker x3 { 0, 0 }, // Banana x3 { 1, 1 }, // Banana x10 { 2, 0 }, // Orbinaut x3 { 1, 1 }, // Orbinaut x4 { 5, 1 } // Jawz x2 }; #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 ENDDIST (12*DISTVAR) // Distance when the game stops giving you bananas INT32 K_GetShieldFromPlayer(player_t *player) { if (player->flametimer > 0) { return KSHIELD_FLAME; } switch (player->itemtype) { case KITEM_THUNDERSHIELD: return KSHIELD_THUNDER; case KITEM_BUBBLESHIELD: return KSHIELD_BUBBLE; //case KITEM_FLAMESHIELD: return KSHIELD_FLAME; not active until flametimer is active. default: return KSHIELD_NONE; } } INT32 K_GetShieldFromItem(INT32 item) { switch (item) { case KITEM_THUNDERSHIELD: return KSHIELD_THUNDER; case KITEM_BUBBLESHIELD: return KSHIELD_BUBBLE; case KITEM_FLAMESHIELD: return KSHIELD_FLAME; default: return KSHIELD_NONE; } } /** \brief Item Roulette for Kart \param player player \param getitem what item we're looking for \return void */ static void K_KartGetItemResult(player_t *player, SINT8 getitem) { if (getitem == KITEM_SPB || getitem == KITEM_SHRINK) // Indirect items indirectitemcooldown = 20*TICRATE; if (getitem == KITEM_HYUDORO) // Hyudoro cooldown hyubgone = 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; 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; } } fixed_t K_ItemOddsScale(UINT8 numPlayers, boolean spbrush) { const UINT8 basePlayer = 8; // The player count we design most of the game around. UINT8 playerCount = (spbrush ? 2 : numPlayers); fixed_t playerScaling = 0; // 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. // 2P: x2.5 playerScaling = (basePlayer - playerCount) * (FRACUNIT / 4); } else if (playerCount > basePlayer) { // More than basePlayer: reduce odds slightly. // 16P: x0.75 playerScaling = (basePlayer - playerCount) * (FRACUNIT / 32); } return playerScaling; } UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush) { if (mapobjectscale != FRACUNIT) { // Bring back to normal scale. distance = FixedDiv(distance * FRACUNIT, mapobjectscale) / FRACUNIT; } if (franticitems == true) { // Frantic items pretends everyone's farther apart, for crazier items. 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; } return distance; } /** \brief Item Roulette for Kart \param player player object passed from P_KartPlayerThink \return void */ INT32 K_KartGetItemOdds( UINT8 pos, SINT8 item, UINT32 ourDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival) { INT32 newodds; INT32 i; UINT8 pingame = 0, pexiting = 0; SINT8 first = -1, second = -1; UINT32 firstDist = UINT32_MAX; UINT32 secondToFirst = UINT32_MAX; boolean powerItem = false; boolean cooldownOnStart = false; boolean indirectItem = false; boolean notNearEnd = false; INT32 shieldtype = KSHIELD_NONE; I_Assert(item > KITEM_NONE); // too many off by one scenarioes. I_Assert(KartItemCVars[NUMKARTRESULTS-2] != NULL); // Make sure this exists if (!KartItemCVars[item-1]->value && !modeattacking) return 0; /* if (bot) { // TODO: Item use on bots should all be passed-in functions. // Instead of manually inserting these, it should return 0 // for any items without an item use function supplied switch (item) { case KITEM_SNEAKER: break; default: return 0; } } */ (void)bot; if (gametyperules & GTR_BATTLEODDS) { I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table newodds = K_KartItemOddsBattle[item-1][pos]; } else if (gametyperules & GTR_RACEODDS) { I_Assert(pos < 8); // Ditto newodds = K_KartItemOddsRace[item-1][pos]; } else { newodds = 0; } // Base multiplication to ALL item odds to simulate fractional precision newodds *= 4; shieldtype = K_GetShieldFromItem(item); for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; if (!(gametyperules & GTR_BUMPERS) || players[i].bumper) pingame++; if (players[i].exiting) pexiting++; if (shieldtype != KSHIELD_NONE && shieldtype == K_GetShieldFromPlayer(&players[i])) { // Don't allow more than one of each shield type at a time return 0; } if (players[i].mo && gametype == GT_RACE) { if (players[i].position == 1 && first == -1) first = i; if (players[i].position == 2 && second == -1) second = i; } } if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB { firstDist = players[first].distancetofinish; if (mapobjectscale != FRACUNIT) { firstDist = FixedDiv(firstDist * FRACUNIT, mapobjectscale) / FRACUNIT; } secondToFirst = K_ScaleItemDistance( players[second].distancetofinish - players[first].distancetofinish, pingame, spbrush ); } switch (item) { case KITEM_BANANA: case KITEM_EGGMAN: notNearEnd = true; break; case KITEM_SUPERRING: notNearEnd = true; if ((K_RingsActive() == false)) // No rings rolled if rings are turned off. { newodds = 0; } break; case KITEM_ROCKETSNEAKER: case KITEM_JAWZ: case KITEM_LANDMINE: case KITEM_DROPTARGET: case KITEM_BALLHOG: case KRITEM_TRIPLESNEAKER: case KRITEM_TRIPLEORBINAUT: case KRITEM_QUADORBINAUT: 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: case KITEM_BUBBLESHIELD: case KITEM_FLAMESHIELD: 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 { 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; if (multiplier < 0) multiplier = 0; if (multiplier > mulMax) multiplier = mulMax; newodds *= multiplier; } break; case KITEM_SHRINK: cooldownOnStart = true; powerItem = true; indirectItem = true; notNearEnd = true; if (pingame-1 <= pexiting) newodds = 0; break; case KITEM_THUNDERSHIELD: cooldownOnStart = true; powerItem = true; if (spbplace != -1) newodds = 0; break; case KITEM_HYUDORO: cooldownOnStart = true; if (hyubgone > 0) newodds = 0; break; default: break; } if (newodds == 0) { // Nothing else we want to do with odds matters at this point :p 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)) { // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) newodds = 0; } else if ((notNearEnd == true) && (ourDist < ENDDIST)) { // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) newodds = 0; } else if (powerItem == true) { // This item is a "power item". This activates "frantic item" toggle related functionality. fixed_t fracOdds = newodds * FRACUNIT; if (franticitems == true) { // First, power items multiply their odds by 2 if frantic items are on; easy-peasy. fracOdds *= 2; } if (rival == true) { // The Rival bot gets frantic-like items, also :p fracOdds *= 2; } fracOdds = FixedMul(fracOdds, FRACUNIT + K_ItemOddsScale(pingame, spbrush)); if (mashed > 0) { // Lastly, it *divides* it based on your mashed value, so that power items are less likely when you mash. fracOdds = FixedDiv(fracOdds, FRACUNIT + mashed); } newodds = fracOdds / FRACUNIT; } return newodds; } INT32 K_KartGetLegacyItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush) { INT32 newodds; INT32 i; UINT8 pingame = 0, pexiting = 0; SINT8 first = -1, second = -1; UINT32 secondToFirst = 0; INT32 shieldtype = KSHIELD_NONE; boolean powerItem = false; boolean cooldownOnStart = false; boolean indirectItem = false; I_Assert(item > KITEM_NONE); // too many off by one scenarioes. if (!KartItemCVars[item-1]->value && !modeattacking) return 0; if (gametyperules & GTR_BATTLEODDS) newodds = K_KartItemOddsBattle[item-1][pos]; else if (gametyperules & GTR_RACEODDS) newodds = K_KartItemOddsRace[item-1][pos]; else newodds = 0; // Base multiplication to ALL item odds to simulate fractional precision newodds *= 4; shieldtype = K_GetShieldFromItem(item); for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; if (!(gametyperules & GTR_BUMPERS) || players[i].bumper) pingame++; if (players[i].exiting) pexiting++; if (shieldtype != KSHIELD_NONE && shieldtype == K_GetShieldFromItem(players[i].itemtype)) { // Don't allow more than one of each shield type at a time return 0; } if (players[i].mo && gametype == GT_RACE) { if (players[i].position == 1 && first == -1) first = i; if (players[i].position == 2 && second == -1) second = i; } } if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB { secondToFirst = P_AproxDistance(P_AproxDistance(players[first].mo->x - players[second].mo->x, players[first].mo->y - players[second].mo->y), players[first].mo->z - players[second].mo->z) / FRACUNIT; secondToFirst = K_ScaleItemDistance(secondToFirst, pingame, spbrush); } switch (item) { case KITEM_ROCKETSNEAKER: case KITEM_JAWZ: case KITEM_BALLHOG: case KITEM_LANDMINE: case KITEM_DROPTARGET: case KRITEM_TRIPLESNEAKER: case KRITEM_TRIPLEBANANA: case KRITEM_TENFOLDBANANA: case KRITEM_TRIPLEORBINAUT: case KRITEM_QUADORBINAUT: case KRITEM_DUALJAWZ: powerItem = true; break; case KITEM_INVINCIBILITY: case KITEM_MINE: case KITEM_GROW: case KITEM_BUBBLESHIELD: case KITEM_FLAMESHIELD: cooldownOnStart = true; powerItem = true; break; case KITEM_SPB: cooldownOnStart = true; indirectItem = true; //powerItem = true; if (pexiting > 0) { newodds = 0; } else if (pos != 9) // Force SPB { const INT32 distFromStart = max(0, (INT32)secondToFirst - SPBSTARTDIST); const INT32 distRange = SPBFORCEDIST - SPBSTARTDIST; const fixed_t mulMax = 3*FRACUNIT; fixed_t multiplier = (distFromStart * mulMax) / distRange; if (multiplier < 0) multiplier = 0; if (multiplier > mulMax) multiplier = mulMax; newodds = FixedMul(newodds * FRACUNIT, multiplier) / FRACUNIT; } break; case KITEM_SHRINK: cooldownOnStart = true; powerItem = true; indirectItem = true; if (pingame-1 <= pexiting) newodds = 0; break; case KITEM_THUNDERSHIELD: cooldownOnStart = true; powerItem = true; break; case KITEM_HYUDORO: cooldownOnStart = true; if (hyubgone > 0) newodds = 0; break; default: break; } if (newodds == 0) { // Nothing else we want to do with odds matters at this point :p 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)) { // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) newodds = 0; } else if (powerItem == true) { // This item is a "power item". This activates "frantic item" toggle related functionality. fixed_t fracOdds = newodds * FRACUNIT; if (franticitems == true) { // First, power items multiply their odds by 2 if frantic items are on; easy-peasy. fracOdds *= 2; } fracOdds = FixedMul(fracOdds, FRACUNIT + K_ItemOddsScale(pingame, spbrush)); if (mashed > 0) { // Lastly, it *divides* it based on your mashed value, so that power items are less likely when you mash. fracOdds = FixedDiv(fracOdds, FRACUNIT + mashed); } newodds = fracOdds / FRACUNIT; } return newodds; } //{ SRB2kart Roulette Code - Distance Based, yes waypoints UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush) { UINT8 i; UINT8 useodds = 0; UINT8 disttable[14]; UINT8 distlen = 0; boolean oddsvalid[8]; // Unused now, oops :V (void)bestbumper; for (i = 0; i < 8; i++) { UINT8 j; boolean available = false; if ((gametyperules & GTR_BATTLEODDS) && i > 1) { oddsvalid[i] = false; break; } for (j = 1; j < NUMKARTRESULTS; j++) { if (K_KartGetItemOdds( i, j, player->distancetofinish, mashed, spbrush, player->bot, (player->bot && player->botvars.rival) ) > 0) { available = true; break; } } oddsvalid[i] = available; } #define SETUPDISTTABLE(odds, num) \ if (oddsvalid[odds]) \ for (i = num; i; --i) \ disttable[distlen++] = odds; if (gametyperules & GTR_BATTLEODDS) // Battle Mode { if (player->roulettetype == 1 && oddsvalid[1] == true) { // 1 is the extreme odds of player-controlled "Karma" items useodds = 1; } else { useodds = 0; if (oddsvalid[0] == false && oddsvalid[1] == true) { // try to use karma odds as a fallback useodds = 1; } } } else if (gametyperules & GTR_RACEODDS) { SETUPDISTTABLE(0,1); SETUPDISTTABLE(1,1); SETUPDISTTABLE(2,1); SETUPDISTTABLE(3,2); SETUPDISTTABLE(4,2); SETUPDISTTABLE(5,3); SETUPDISTTABLE(6,3); SETUPDISTTABLE(7,1); if (pdis == 0) useodds = disttable[0]; else if (pdis > DISTVAR * ((12 * distlen) / 14)) useodds = disttable[distlen-1]; else { for (i = 1; i < 13; i++) { if (pdis <= DISTVAR * ((i * distlen) / 14)) { useodds = disttable[((i * distlen) / 14)]; break; } } } } #undef SETUPDISTTABLE return useodds; } INT32 K_FindLegacyUseodds(player_t *player, fixed_t mashed, INT32 pingame, INT32 bestbumper, boolean spbrush, boolean dontforcespb) { SINT8 sortedPlayers[MAXPLAYERS]; UINT8 sortLength = 0; UINT32 pdis = 0; UINT8 disttable[14]; UINT8 distlen = 0; boolean oddsvalid[8]; INT32 useodds = 0; INT32 i; // Unused now, oops :V (void)bestbumper; for (i = 0; i < 8; i++) { INT32 j; boolean available = false; if ((gametyperules & GTR_BATTLEODDS) && i > 1) { oddsvalid[i] = false; break; } for (j = 1; j < NUMKARTRESULTS; j++) { if (K_KartGetLegacyItemOdds(i, j, mashed, spbrush) > 0) { available = true; break; } } oddsvalid[i] = available; } memset(sortedPlayers, -1, sizeof(sortedPlayers)); if (player->mo != NULL && P_MobjWasRemoved(player->mo) == false) { // Sort all of the players ahead of you. // Then tally up their distances in a conga line. // This will create a much more consistent item // distance algorithm than the "spider web" thing // that it was doing before. // Add yourself to the list. // You'll always be the end of the list, // so we can also calculate the length here. sortedPlayers[ player->position - 1 ] = player - players; sortLength = player->position; // Will only need to do this if there's goint to be // more than yourself in the list. if (sortLength > 1) { SINT8 firstIndex = -1; SINT8 secondIndex = -1; INT32 startFrom = INT32_MAX; // Add all of the other players. for (i = 0; i < MAXPLAYERS; i++) { INT32 pos = INT32_MAX; if (!playeringame[i] || players[i].spectator) { continue; } if (players[i].mo == NULL || P_MobjWasRemoved(players[i].mo) == true) { continue; } pos = players[i].position; if (pos <= 0 || pos > MAXPLAYERS) { // Invalid position. continue; } if (pos >= player->position) { // Tied / behind us. // Also handles ourselves, obviously. continue; } // Ties are done with port priority, if there are any. if (sortedPlayers[ pos - 1 ] == -1) { sortedPlayers[ pos - 1 ] = i; } } // The chance of this list having gaps is improbable, // but not impossible. So we need to spend some extra time // to prevent the gaps from mattering. for (i = 0; i < sortLength-1; i++) { if (sortedPlayers[i] >= 0 && sortedPlayers[i] < MAXPLAYERS) { // First valid index in the list found. firstIndex = sortedPlayers[i]; // Start the next loop after this player. startFrom = i + 1; break; } } if (firstIndex >= 0 && firstIndex < MAXPLAYERS && startFrom < sortLength) { // First index is valid, so we can // start comparing the players. player_t *firstPlayer = NULL; player_t *secondPlayer = NULL; for (i = startFrom; i < sortLength; i++) { if (sortedPlayers[i] >= 0 && sortedPlayers[i] < MAXPLAYERS) { secondIndex = sortedPlayers[i]; firstPlayer = &players[firstIndex]; secondPlayer = &players[secondIndex]; // Add the distance to the player behind you. pdis += P_AproxDistance(P_AproxDistance( firstPlayer->mo->x - secondPlayer->mo->x, firstPlayer->mo->y - secondPlayer->mo->y), firstPlayer->mo->z - secondPlayer->mo->z) / FRACUNIT; // Advance to next index. firstIndex = secondIndex; } } } } } #define SETUPDISTTABLE(odds, num) \ if (oddsvalid[odds]) \ for (i = num; i; --i) \ disttable[distlen++] = odds; if (gametyperules & GTR_BATTLEODDS) // Battle Mode { if (player->roulettetype == 1 && oddsvalid[1] == true) { // 1 is the extreme odds of player-controlled "Karma" items useodds = 1; } else { useodds = 0; if (oddsvalid[0] == false && oddsvalid[1] == true) { // try to use karma odds as a fallback useodds = 1; } } } else { SETUPDISTTABLE(0,1); SETUPDISTTABLE(1,1); SETUPDISTTABLE(2,1); SETUPDISTTABLE(3,2); SETUPDISTTABLE(4,2); SETUPDISTTABLE(5,3); SETUPDISTTABLE(6,3); SETUPDISTTABLE(7,1); pdis = K_ScaleItemDistance(pdis, pingame, spbrush); if (pingame == 1 && oddsvalid[0]) { // Record Attack / FREE PLAY useodds = 0; } else if (pdis <= 0) { // 1st place useodds = disttable[0]; } else if (player->position == 2 && pdis > SPBFORCEDIST && spbplace == -1 && !indirectitemcooldown && !dontforcespb) { // Force SPB in 2nd useodds = 69; } else if (pdis > DISTVAR * ((12 * distlen) / 14)) { // Back of the pack useodds = disttable[distlen-1]; } else { for (i = 1; i < 13; i++) { if (pdis <= (unsigned)(DISTVAR * ((i * distlen) / 14))) { useodds = disttable[((i * distlen) / 14)]; break; } } } } #undef SETUPDISTTABLE //CONS_Printf("Got useodds %d. (position: %d, distance: %d)\n", useodds, player->position, pdis); return useodds; } INT32 K_GetRollingRouletteItem(player_t *player) { static UINT8 translation[NUMKARTITEMS-1]; static UINT16 roulette_size; static INT16 odds_cached = -1; // Race odds have more columns than Battle const UINT8 EMPTYODDS[sizeof K_KartItemOddsRace[0]] = {0}; if (odds_cached != gametype) { UINT8 *odds_row; size_t odds_row_size; UINT8 i; roulette_size = 0; if (gametyperules & GTR_BATTLEODDS) { odds_row = K_KartItemOddsBattle[0]; odds_row_size = sizeof K_KartItemOddsBattle[0]; } else { odds_row = K_KartItemOddsRace[0]; odds_row_size = sizeof K_KartItemOddsRace[0]; } for (i = 1; i < NUMKARTITEMS; ++i) { if (memcmp(odds_row, EMPTYODDS, odds_row_size)) { translation[roulette_size] = i; roulette_size++; } odds_row += odds_row_size; } roulette_size *= 3; odds_cached = gametype; } return translation[(player->itemroulette % roulette_size) / 3]; } static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) { INT32 i; UINT8 pingame = 0; UINT8 roulettestop; UINT32 pdis = 0; UINT8 useodds = 0; INT32 spawnchance[NUMKARTRESULTS]; 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) return; player->itemroulette++; // Gotta check how many players are active at this moment. for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; pingame++; if (players[i].exiting) dontforcespb = true; if (players[i].bumper > bestbumper) bestbumper = players[i].bumper; } // 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) { #define PLAYROULETTESND S_StartSound(NULL, sfx_itrol1 + ((player->itemroulette / 3) % 8)) for (i = 0; i <= r_splitscreen; i++) { if (player == &players[displayplayers[i]] && players[displayplayers[i]].itemroulette) PLAYROULETTESND; } #undef PLAYROULETTESND } roulettestop = TICRATE + (3*(pingame - player->position)); // If the roulette finishes or the player presses BT_ATTACK, stop the roulette and calculate the item. // I'm returning via the exact opposite, however, to forgo having another bracket embed. Same result either way, I think. // Finally, if you get past this check, now you can actually start calculating what item you get. if ((cmd->buttons & BT_ATTACK) && (player->itemroulette >= roulettestop) && !(player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT|IF_USERINGS))) { // Mashing reduces your chances for the good items mashed = FixedDiv((player->itemroulette)*FRACUNIT, ((TICRATE*3)+roulettestop)*FRACUNIT) - FRACUNIT; } else if (!(player->itemroulette >= (TICRATE*3))) return; if (!(K_UsingLegacyCheckpoints())) { for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && !players[i].spectator && players[i].position == 1) { // This player is first! Yay! if (player->distancetofinish <= players[i].distancetofinish) { // Guess you're in first / tied for first? pdis = 0; } else { // Subtract 1st's distance from your distance, to get your distance from 1st! pdis = player->distancetofinish - players[i].distancetofinish; } break; } } } 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 if (!(K_UsingLegacyCheckpoints())) pdis = (3 * pdis) / 2; spbrush = true; } if (!(K_UsingLegacyCheckpoints())) { pdis = K_ScaleItemDistance(pdis, pingame, spbrush); if (player->bot && player->botvars.rival) { // Rival has better odds :) pdis = (15 * pdis) / 14; } } // SPECIAL CASE No. 1: // Fake Eggman items if (player->roulettetype == 2) { player->eggmanexplode = 4*TICRATE; //player->itemblink = TICRATE; //player->itemblinkmode = 1; player->itemroulette = 0; player->roulettetype = 0; if (P_IsDisplayPlayer(player) && !demo.freecam) S_StartSound(NULL, sfx_itrole); return; } // SPECIAL CASE No. 2: // Give a debug item instead if specified if (cv_kartdebugitem.value != 0 && !modeattacking) { K_KartGetItemResult(player, cv_kartdebugitem.value); player->itemamount = cv_kartdebugamount.value; player->itemblink = TICRATE; player->itemblinkmode = 2; player->itemroulette = 0; player->roulettetype = 0; if (P_IsDisplayPlayer(player) && !demo.freecam) S_StartSound(NULL, sfx_dbgsal); return; } // SPECIAL CASE No. 3: // This Gametype never specified an odds type. Roll something random please! if (!(gametyperules & GTR_RACEODDS) && !(gametyperules & GTR_BATTLEODDS)) { SINT8 itemroll = P_RandomRange(KITEM_SNEAKER, NUMKARTITEMS - 1); K_KartGetItemResult(player, itemroll); player->itemblink = TICRATE; player->itemblinkmode = 0; player->itemroulette = 0; player->roulettetype = 0; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolf); return; } // SPECIAL CASE No. 4: // Record Attack / alone mashing behavior if ((modeattacking || pingame == 1) && ((gametyperules & GTR_RACEODDS) || (gametyperules & GTR_BATTLEODDS))) { if ((gametyperules & GTR_RACEODDS)) { if (mashed && ((K_RingsActive() == true) && (modeattacking || cv_superring.value))) // ANY mashed value? You get rings. { K_KartGetItemResult(player, KITEM_SUPERRING); player->itemblinkmode = 1; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolm); } else { if (modeattacking || cv_sneaker.value) // Waited patiently? You get a sneaker! K_KartGetItemResult(player, KITEM_SNEAKER); else // Default to sad if nothing's enabled... K_KartGetItemResult(player, KITEM_SAD); player->itemblinkmode = 0; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolf); } } else if (gametyperules & GTR_BATTLEODDS) { if (mashed && (bossinfo.boss || cv_banana.value) && !itembreaker) // ANY mashed value? You get a banana. { K_KartGetItemResult(player, KITEM_BANANA); player->itemblinkmode = 1; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolm); } else if (bossinfo.boss) { K_KartGetItemResult(player, KITEM_ORBINAUT); player->itemblinkmode = 0; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolf); } } player->itemblink = TICRATE; player->itemroulette = 0; player->roulettetype = 0; return; } // SPECIAL CASE No. 5: // Being in ring debt occasionally forces Super Ring on you if you mashed if ((K_RingsActive() == true) && mashed && player->rings < 0 && cv_superring.value) { INT32 debtamount = min(20, abs(player->rings)); if (P_RandomChance((debtamount*FRACUNIT)/20)) { K_KartGetItemResult(player, KITEM_SUPERRING); player->itemblink = TICRATE; player->itemblinkmode = 1; player->itemroulette = 0; player->roulettetype = 0; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolm); return; } } if (!(K_UsingLegacyCheckpoints())) { // SPECIAL CASE No. 6: // 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) { K_KartGetItemResult(player, KITEM_SPB); player->itemblink = TICRATE; player->itemblinkmode = 2; player->itemroulette = 0; player->roulettetype = 0; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolk); return; } } // NOW that we're done with all of those specialized cases, we can move onto the REAL item roulette tables. // Initializes existing spawnchance values for (i = 0; i < NUMKARTRESULTS; i++) spawnchance[i] = 0; // Split into another function for a debug function below // Use a legacy version for maps not using waypoints. if (K_UsingLegacyCheckpoints()) useodds = K_FindLegacyUseodds(player, mashed, pingame, bestbumper, spbrush, dontforcespb); else useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); if (useodds == 69) { K_KartGetItemResult(player, KITEM_SPB); player->itemblink = TICRATE; player->itemblinkmode = 2; player->itemroulette = 0; player->roulettetype = 0; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolk); return; } for (i = 1; i < NUMKARTRESULTS; i++) { if (K_UsingLegacyCheckpoints()) { spawnchance[i] = (totalspawnchance += K_KartGetLegacyItemOdds(useodds, i, mashed, spbrush)); } else { spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( useodds, i, player->distancetofinish, mashed, spbrush, player->bot, (player->bot && player->botvars.rival)) ); } } // Award the player whatever power is rolled if (totalspawnchance > 0) { totalspawnchance = P_RandomKey(totalspawnchance); for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); K_KartGetItemResult(player, i); } else { player->itemtype = KITEM_SAD; player->itemamount = 1; } if (P_IsDisplayPlayer(player) && !demo.freecam) S_StartSound(NULL, ((player->roulettetype == 1) ? sfx_itrolk : (mashed ? sfx_itrolm : sfx_itrolf))); player->itemblink = TICRATE; player->itemblinkmode = ((player->roulettetype == 1) ? 2 : (mashed ? 1 : 0)); player->itemroulette = 0; // Since we're done, clear the roulette number player->roulettetype = 0; // This too } //} //{ SRB2kart p_user.c Stuff static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against) { fixed_t weight = 5*FRACUNIT; if (!mobj->player) return weight; if (against && !P_MobjWasRemoved(against) && against->player && ((!P_PlayerInPain(against->player) && P_PlayerInPain(mobj->player)) // You're hurt || (against->player->itemtype == KITEM_BUBBLESHIELD && mobj->player->itemtype != KITEM_BUBBLESHIELD))) // They have a Bubble Shield { weight = 0; // This player does not cause any bump action } else { // Applies rubberbanding, to prevent rubberbanding bots // from causing super crazy bumps. fixed_t spd = K_GetKartSpeed(mobj->player, false, true); weight = (mobj->player->kartweight) * FRACUNIT; if (mobj->player->itemtype == KITEM_BUBBLESHIELD) weight += 9*FRACUNIT; if (mobj->player->speed > spd) weight += (mobj->player->speed - spd)/8; } return weight; } fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against) { fixed_t weight = 5*FRACUNIT; switch (mobj->type) { case MT_PLAYER: if (!mobj->player) break; weight = K_PlayerWeight(mobj, against); break; case MT_KART_LEFTOVER: weight = 5*FRACUNIT/2; if (mobj->extravalue1 > 0) { weight = mobj->extravalue1 * (FRACUNIT >> 1); } break; case MT_BUBBLESHIELD: weight = K_PlayerWeight(mobj->target, against); break; case MT_FALLINGROCK: if (against->player) { if (against->player->invincibilitytimer || against->player->growshrinktimer > 0) weight = 0; else weight = K_PlayerWeight(against, NULL); } break; case MT_ORBINAUT: case MT_ORBINAUT_SHIELD: if (against->player) weight = K_PlayerWeight(against, NULL); break; case MT_JAWZ: case MT_JAWZ_DUD: case MT_JAWZ_SHIELD: if (against->player) weight = K_PlayerWeight(against, NULL) + (3*FRACUNIT); else weight += 3*FRACUNIT; break; case MT_DROPTARGET: case MT_DROPTARGET_SHIELD: if (against->player) weight = K_PlayerWeight(against, NULL); default: break; } return weight; } boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) { mobj_t *fx; fixed_t momdifx, momdify; fixed_t distx, disty; fixed_t dot, force; fixed_t mass1, mass2; if (!mobj1 || !mobj2) return false; // Don't bump when you're being reborn if ((mobj1->player && mobj1->player->playerstate != PST_LIVE) || (mobj2->player && mobj2->player->playerstate != PST_LIVE)) return false; if ((mobj1->player && mobj1->player->respawn) || (mobj2->player && mobj2->player->respawn)) return false; if (mobj1->type != MT_DROPTARGET && mobj1->type != MT_DROPTARGET_SHIELD) { // Don't bump if you're flashing INT32 flash; flash = K_GetKartFlashing(mobj1->player); if (mobj1->player && mobj1->player->flashing > 0 && mobj1->player->flashing < flash) { if (mobj1->player->flashing < flash-1) mobj1->player->flashing++; return false; } flash = K_GetKartFlashing(mobj2->player); if (mobj2->player && mobj2->player->flashing > 0 && mobj2->player->flashing < flash) { if (mobj2->player->flashing < flash-1) mobj2->player->flashing++; return false; } } if ((mobj1->player && mobj1->player->squishedtimer > 0) || (mobj2->player && mobj2->player->squishedtimer > 0)) return false; // Don't bump if you've recently bumped if (mobj1->player && mobj1->player->justbumped) { mobj1->player->justbumped = bumptime; return false; } if (mobj2->player && mobj2->player->justbumped) { mobj2->player->justbumped = bumptime; return false; } mass1 = K_GetMobjWeight(mobj1, mobj2); if (solid == true && mass1 > 0) mass2 = mass1; else mass2 = K_GetMobjWeight(mobj2, mobj1); momdifx = mobj1->momx - mobj2->momx; momdify = mobj1->momy - mobj2->momy; // Adds the OTHER player's momentum times a bunch, for the best chance of getting the correct direction distx = (mobj1->x + mobj2->momx*3) - (mobj2->x + mobj1->momx*3); disty = (mobj1->y + mobj2->momy*3) - (mobj2->y + mobj1->momy*3); if (distx == 0 && disty == 0) { // if there's no distance between the 2, they're directly on top of each other, don't run this return false; } { // Normalize distance to the sum of the two objects' radii, since in a perfect world that would be the distance at the point of collision... fixed_t dist = P_AproxDistance(distx, disty); fixed_t nx, ny; dist = dist ? dist : 1; nx = FixedDiv(distx, dist); ny = FixedDiv(disty, dist); distx = FixedMul(mobj1->radius+mobj2->radius, nx); disty = FixedMul(mobj1->radius+mobj2->radius, ny); if (momdifx == 0 && momdify == 0) { // If there's no momentum difference, they're moving at exactly the same rate. Pretend they moved into each other. momdifx = -nx; momdify = -ny; } } // if the speed difference is less than this let's assume they're going proportionately faster from each other if (P_AproxDistance(momdifx, momdify) < (25*mapobjectscale)) { fixed_t momdiflength = P_AproxDistance(momdifx, momdify); fixed_t normalisedx = FixedDiv(momdifx, momdiflength); fixed_t normalisedy = FixedDiv(momdify, momdiflength); momdifx = FixedMul((25*mapobjectscale), normalisedx); momdify = FixedMul((25*mapobjectscale), normalisedy); } dot = FixedMul(momdifx, distx) + FixedMul(momdify, disty); if (dot >= 0) { // They're moving away from each other return false; } force = FixedDiv(dot, FixedMul(distx, distx)+FixedMul(disty, disty)); if (bounce == true && mass2 > 0) // Perform a Goomba Bounce. mobj1->momz = -mobj1->momz; else { fixed_t newz = mobj1->momz; if (mass2 > 0) mobj1->momz = mobj2->momz; if (mass1 > 0 && solid == false) mobj2->momz = newz; } if (mass2 > 0) { mobj1->momx = mobj1->momx - FixedMul(FixedMul(FixedDiv(2*mass2, mass1 + mass2), force), distx); mobj1->momy = mobj1->momy - FixedMul(FixedMul(FixedDiv(2*mass2, mass1 + mass2), force), disty); } if (mass1 > 0 && solid == false) { mobj2->momx = mobj2->momx - FixedMul(FixedMul(FixedDiv(2*mass1, mass1 + mass2), force), -distx); mobj2->momy = mobj2->momy - FixedMul(FixedMul(FixedDiv(2*mass1, mass1 + mass2), force), -disty); } // Do the bump fx when we've CONFIRMED we can bump. if ((mobj1->player && mobj1->player->itemtype == KITEM_BUBBLESHIELD) || (mobj2->player && mobj2->player->itemtype == KITEM_BUBBLESHIELD)) S_StartSound(mobj1, sfx_s3k44); else S_StartSound(mobj1, sfx_s3k49); fx = P_SpawnMobj(mobj1->x/2 + mobj2->x/2, mobj1->y/2 + mobj2->y/2, mobj1->z/2 + mobj2->z/2, MT_BUMP); if (mobj1->eflags & MFE_VERTICALFLIP) fx->eflags |= MFE_VERTICALFLIP; else fx->eflags &= ~MFE_VERTICALFLIP; P_SetScale(fx, mobj1->scale); // Because this is done during collision now, rmomx and rmomy need to be recalculated // so that friction doesn't immediately decide to stop the player if they're at a standstill // Also set justbumped here if (mobj1->player) { mobj1->player->rmomx = mobj1->momx - mobj1->player->cmomx; mobj1->player->rmomy = mobj1->momy - mobj1->player->cmomy; mobj1->player->justbumped = bumptime; if (mobj1->player->spinouttimer) { mobj1->player->wipeoutslow = wipeoutslowtime+1; mobj1->player->spinouttimer = max(wipeoutslowtime+1, mobj1->player->spinouttimer); //mobj1->player->spinouttype = KSPIN_WIPEOUT; // Enforce type } } if (mobj2->player) { mobj2->player->rmomx = mobj2->momx - mobj2->player->cmomx; mobj2->player->rmomy = mobj2->momy - mobj2->player->cmomy; mobj2->player->justbumped = bumptime; if (mobj2->player->spinouttimer) { mobj2->player->wipeoutslow = wipeoutslowtime+1; mobj2->player->spinouttimer = max(wipeoutslowtime+1, mobj2->player->spinouttimer); //mobj2->player->spinouttype = KSPIN_WIPEOUT; // Enforce type } } return true; } /** \brief Checks that the player is on an offroad subsector for realsies. Also accounts for line riding to prevent cheese. \param mo player mobj object \return boolean */ static fixed_t K_CheckOffroadCollide(mobj_t *mo) { // Check for sectors in touching_sectorlist msecnode_t *node; // touching_sectorlist iter sector_t *s; // main sector shortcut sector_t *s2; // FOF sector shortcut ffloor_t *rover; // FOF fixed_t flr; fixed_t cel; // floor & ceiling for height checks to make sure we're touching the offroad sector. I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next) { if (!node->m_sector) break; // shouldn't happen. s = node->m_sector; // 1: Check for the main sector, make sure we're on the floor of that sector and see if we can apply offroad. // Make arbitrary Z checks because we want to check for 1 sector in particular, we don't want to affect the player if the offroad sector is way below them and they're lineriding a normal sector above. flr = P_MobjFloorZ(mo, s, s, mo->x, mo->y, NULL, false, true); cel = P_MobjCeilingZ(mo, s, s, mo->x, mo->y, NULL, true, true); // get Z coords of both floors and ceilings for this sector (this accounts for slopes properly.) // NOTE: we don't use P_GetZAt with our x/y directly because the mobj won't have the same height because of its hitbox on the slope. Complex garbage but tldr it doesn't work. if ( ((s->flags & MSF_FLIPSPECIAL_FLOOR) && mo->z == flr) // floor check || ((mo->eflags & MFE_VERTICALFLIP && (s->flags & MSF_FLIPSPECIAL_CEILING) && (mo->z + mo->height) == cel)) ) // ceiling check. { return s->offroad; } // 2: If we're here, we haven't found anything. So let's try looking for FOFs in the sectors using the same logic. for (rover = s->ffloors; rover; rover = rover->next) { if (!(rover->fofflags & FOF_EXISTS)) // This FOF doesn't exist anymore. continue; s2 = §ors[rover->secnum]; // makes things easier for us flr = P_GetFOFBottomZ(mo, s, rover, mo->x, mo->y, NULL); cel = P_GetFOFTopZ(mo, s, rover, mo->x, mo->y, NULL); // Z coords for fof top/bottom. // we will do essentially the same checks as above instead of bothering with top/bottom height of the FOF. // Reminder that an FOF's floor is its bottom, silly! if ( ((s2->flags & MSF_FLIPSPECIAL_FLOOR) && mo->z == cel) // "floor" check || ((s2->flags & MSF_FLIPSPECIAL_CEILING) && (mo->z + mo->height) == flr) ) // "ceiling" check. { return s2->offroad; } } } return 0; // couldn't find any offroad } /** \brief Updates the Player's offroad value once per frame \param player player object passed from K_KartPlayerThink \return void */ static void K_UpdateOffroad(player_t *player) { terrain_t *terrain = player->mo->terrain; fixed_t offroadstrength = 0; // TODO: Make this use actual special touch code. if (terrain != NULL && terrain->offroad > 0) { offroadstrength = (terrain->offroad << FRACBITS); } else { offroadstrength = K_CheckOffroadCollide(player->mo); } // If you are in offroad, a timer starts. if (offroadstrength) { if (player->offroad == 0) player->offroad = (TICRATE/2); if (player->offroad > 0) { fixed_t offroad; offroad = offroadstrength / (TICRATE/2); player->offroad += offroad; } if (player->offroad > offroadstrength) player->offroad = offroadstrength; } else player->offroad = 0; } void K_KartPainEnergyFling(player_t *player) { static const UINT8 numfling = 5; INT32 i; mobj_t *mo; angle_t fa; fixed_t ns; fixed_t z; // Better safe than sorry. if (!player) return; // P_PlayerRingBurst: "There's no ring spilling in kart, so I'm hijacking this for the same thing as TD" // :oh: for (i = 0; i < numfling; i++) { INT32 objType = mobjinfo[MT_FLINGENERGY].reactiontime; fixed_t momxy, momz; // base horizonal/vertical thrusts z = player->mo->z; if (player->mo->eflags & MFE_VERTICALFLIP) z += player->mo->height - mobjinfo[objType].height; mo = P_SpawnMobj(player->mo->x, player->mo->y, z, objType); mo->fuse = 8*TICRATE; P_SetTarget(&mo->target, player->mo); mo->destscale = player->mo->scale; P_SetScale(mo, player->mo->scale); // Angle offset by player angle, then slightly offset by amount of fling fa = ((i*FINEANGLES/16) + (player->mo->angle>>ANGLETOFINESHIFT) - ((numfling-1)*FINEANGLES/32)) & FINEMASK; if (i > 15) { momxy = 3*FRACUNIT; momz = 4*FRACUNIT; } else { momxy = 28*FRACUNIT; momz = 3*FRACUNIT; } ns = FixedMul(momxy, mo->scale); mo->momx = FixedMul(FINECOSINE(fa),ns); ns = momz; P_SetObjectMomZ(mo, ns, false); if (i & 1) P_SetObjectMomZ(mo, ns, true); if (player->mo->eflags & MFE_VERTICALFLIP) mo->momz *= -1; } } // Adds gravity flipping to an object relative to its master and shifts the z coordinate accordingly. void K_FlipFromObject(mobj_t *mo, mobj_t *master) { mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP)|(master->eflags & MFE_VERTICALFLIP); mo->flags2 = (mo->flags2 & ~MF2_OBJECTFLIP)|(master->flags2 & MF2_OBJECTFLIP); if (mo->eflags & MFE_VERTICALFLIP) mo->z += master->height - FixedMul(master->scale, mo->height); } void K_MatchGenericExtraFlags(mobj_t *mo, mobj_t *master) { // flipping // handle z shifting from there too. This is here since there's no reason not to flip us if needed when we do this anyway; K_FlipFromObject(mo, master); // visibility (usually for hyudoro) mo->renderflags = (mo->renderflags & ~RF_DONTDRAW) | (master->renderflags & RF_DONTDRAW); } // same as above, but does not adjust Z height when flipping void K_GenericExtraFlagsNoZAdjust(mobj_t *mo, mobj_t *master) { // flipping mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP)|(master->eflags & MFE_VERTICALFLIP); mo->flags2 = (mo->flags2 & ~MF2_OBJECTFLIP)|(master->flags2 & MF2_OBJECTFLIP); // visibility (usually for hyudoro) mo->renderflags = (mo->renderflags & ~RF_DONTDRAW) | (master->renderflags & RF_DONTDRAW); } void K_SpawnDashDustRelease(player_t *player) { fixed_t newx; fixed_t newy; mobj_t *dust; angle_t travelangle; INT32 i; I_Assert(player != NULL); I_Assert(player->mo != NULL); I_Assert(!P_MobjWasRemoved(player->mo)); if (!P_IsObjectOnGround(player->mo)) return; if (!player->speed && !player->startboost) return; travelangle = player->mo->angle; if (player->drift || (player->pflags & PF_DRIFTEND)) travelangle -= (ANGLE_45/5)*player->drift; for (i = 0; i < 2; i++) { newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_90, FixedMul(48*FRACUNIT, player->mo->scale)); newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_90, FixedMul(48*FRACUNIT, player->mo->scale)); dust = P_SpawnMobj(newx, newy, player->mo->z, MT_FASTDUST); P_SetTarget(&dust->target, player->mo); dust->angle = travelangle - (((i&1) ? -1 : 1) * ANGLE_45); dust->destscale = player->mo->scale; P_SetScale(dust, player->mo->scale); dust->momx = 3*player->mo->momx/5; dust->momy = 3*player->mo->momy/5; dust->momz = 3*P_GetMobjZMovement(player->mo)/5; K_MatchGenericExtraFlags(dust, player->mo); } } static fixed_t K_GetBrakeFXScale(player_t *player, fixed_t maxScale) { fixed_t s = FixedDiv(player->speed, K_GetKartSpeed(player, false, false)); s = max(s, FRACUNIT); s = min(s, maxScale); return s; } static void K_SpawnBrakeDriftSparks(player_t *player) // Be sure to update the mobj thinker case too! { mobj_t *sparks; I_Assert(player != NULL); I_Assert(player->mo != NULL); I_Assert(!P_MobjWasRemoved(player->mo)); // Position & etc are handled in its thinker, and its spawned invisible. // This avoids needing to dupe code if we don't need it. sparks = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BRAKEDRIFT); P_SetTarget(&sparks->target, player->mo); P_SetScale(sparks, (sparks->destscale = FixedMul(K_GetBrakeFXScale(player, 3*FRACUNIT), player->mo->scale))); K_MatchGenericExtraFlags(sparks, player->mo); sparks->renderflags |= RF_DONTDRAW; } void K_SpawnNormalSpeedLines(player_t *player) { mobj_t *fast = P_SpawnMobj(player->mo->x + (P_RandomRange(-36,36) * player->mo->scale), player->mo->y + (P_RandomRange(-36,36) * player->mo->scale), player->mo->z + (player->mo->height/2) + (P_RandomRange(-20,20) * player->mo->scale), MT_FASTLINE); P_SetTarget(&fast->target, player->mo); fast->angle = K_MomentumAngle(player->mo); fast->momx = 3*player->mo->momx/4; fast->momy = 3*player->mo->momy/4; fast->momz = 3*P_GetMobjZMovement(player->mo)/4; K_MatchGenericExtraFlags(fast, player->mo); if (player->tripwireLeniency) { fast->destscale = fast->destscale * 2; P_SetScale(fast, 3*fast->scale/2); } if (player->eggmanexplode) { // Make it red when you have the eggman speed boost fast->color = SKINCOLOR_RED; fast->colorized = true; } else if (player->invincibilitytimer) { const tic_t defaultTime = itemtime+(2*TICRATE); if (player->invincibilitytimer > defaultTime) { fast->color = player->mo->color; } else { fast->color = SKINCOLOR_INVINCFLASH; } fast->colorized = true; } else if (player->tripwireLeniency) { // Make it pink+blue+big when you can go through tripwire fast->color = (leveltime & 1) ? SKINCOLOR_LILAC : SKINCOLOR_JAWZ; fast->colorized = true; fast->renderflags |= RF_ADD; } else if (player->pflags & PF_AIRFAILSAFE) { fast->color = SKINCOLOR_WHITE; fast->colorized = true; } } void K_SpawnInvincibilitySpeedLines(mobj_t *mo) { mobj_t *fast = P_SpawnMobjFromMobj(mo, P_RandomRange(-48, 48) * FRACUNIT, P_RandomRange(-48, 48) * FRACUNIT, P_RandomRange(0, 64) * FRACUNIT, MT_FASTLINE); P_SetMobjState(fast, S_KARTINVLINES1); P_SetTarget(&fast->target, mo); fast->angle = K_MomentumAngle(mo); fast->momx = 3*mo->momx/4; fast->momy = 3*mo->momy/4; fast->momz = 3*P_GetMobjZMovement(mo)/4; K_MatchGenericExtraFlags(fast, mo); fast->color = mo->color; fast->colorized = true; if (mo->player->invincibilitytimer < 10*TICRATE) fast->destscale = 6*((mo->player->invincibilitytimer/TICRATE)*FRACUNIT)/8; } static void K_SpawnStackingEffect(player_t *player) { // Thanks to 1ndev for code used for booststack->scale and boosttack->frame! (taken and modified from BoostStack) mobj_t *booststack = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BOOSTSTACK); P_SetTarget(&booststack->target, player->mo); P_SetScale(booststack, FixedMul(FRACUNIT + FixedMul(2*FRACUNIT - FRACUNIT, FixedDiv(min(player->numboosts,4)*FRACUNIT, 4*FRACUNIT)), mapobjectscale)); booststack->angle = player->mo->angle + ANGLE_90; booststack->color = player->mo->color; } void K_SpawnBumpEffect(mobj_t *mo) { mobj_t *fx = P_SpawnMobj(mo->x, mo->y, mo->z, MT_BUMP); if (mo->eflags & MFE_VERTICALFLIP) fx->eflags |= MFE_VERTICALFLIP; else fx->eflags &= ~MFE_VERTICALFLIP; fx->scale = mo->scale; S_StartSound(mo, sfx_s3k49); } static SINT8 K_GlanceAtPlayers(player_t *glancePlayer) { const fixed_t maxdistance = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); const angle_t blindSpotSize = ANG10; // ANG5 UINT8 i; SINT8 glanceDir = 0; SINT8 lastValidGlance = 0; // See if there's any players coming up behind us. // If so, your character will glance at 'em. for (i = 0; i < MAXPLAYERS; i++) { player_t *p; angle_t back; angle_t diff; fixed_t distance; SINT8 dir = -1; if (!playeringame[i]) { // Invalid player continue; } p = &players[i]; if (p == glancePlayer) { // FOOL! Don't glance at yerself! continue; } if (!p->mo || P_MobjWasRemoved(p->mo)) { // Invalid mobj continue; } if (p->spectator || p->hyudorotimer > 0) { // Not playing / invisible continue; } distance = R_PointToDist2(glancePlayer->mo->x, glancePlayer->mo->y, p->mo->x, p->mo->y); if (distance > maxdistance) { continue; } back = glancePlayer->mo->angle + ANGLE_180; diff = R_PointToAngle2(glancePlayer->mo->x, glancePlayer->mo->y, p->mo->x, p->mo->y) - back; if (diff > ANGLE_180) { diff = InvAngle(diff); dir = -dir; } if (diff > ANGLE_90) { // Not behind the player continue; } if (diff < blindSpotSize) { // Small blindspot directly behind your back, gives the impression of smoothly turning. continue; } if (P_CheckSight(glancePlayer->mo, p->mo) == true) { // Not blocked by a wall, we can glance at 'em! // Adds, so that if there's more targets on one of your sides, it'll glance on that side. glanceDir += dir; // That poses a limitation if there's an equal number of targets on both sides... // In that case, we'll pick the last chosen glance direction. lastValidGlance = dir; } } if (glanceDir > 0) { return 1; } else if (glanceDir < 0) { return -1; } return lastValidGlance; } /** \brief Calculates the respawn timer and drop-boosting \param player player object passed from K_KartPlayerThink \return void */ static void K_RespawnChecker(player_t *player) { UINT16 buttons = K_GetKartButtons(player); if (player->spectator) return; if (player->respawn > 1) { player->respawn--; player->mo->momz = 0; player->flashing = 2; player->nocontrol = 2; if (leveltime % 8 == 0) { INT32 i; if (!mapreset) S_StartSound(player->mo, sfx_s3kcas); for (i = 0; i < 8; i++) { mobj_t *mo; angle_t newangle; fixed_t newx, newy, newz; newangle = FixedAngle(((360/8)*i)*FRACUNIT); newx = player->mo->x + P_ReturnThrustX(player->mo, newangle, 31<mo->y + P_ReturnThrustY(player->mo, newangle, 31<mo->eflags & MFE_VERTICALFLIP) newz = player->mo->z + player->mo->height; else newz = player->mo->z; mo = P_SpawnMobj(newx, newy, newz, MT_DEZLASER); if (mo) { if (player->mo->eflags & MFE_VERTICALFLIP) mo->eflags |= MFE_VERTICALFLIP; P_SetTarget(&mo->target, player->mo); mo->angle = newangle+ANGLE_90; mo->momz = (8<mo); P_SetScale(mo, (mo->destscale = FRACUNIT)); } } } } else if (player->respawn == 1) { if (player->growshrinktimer < 0) { player->mo->scalespeed = mapobjectscale/TICRATE; player->mo->destscale = (6*mapobjectscale)/8; if (K_PlayerShrinkCheat(player) && !modeattacking && !player->bot) player->mo->destscale = (6*player->mo->destscale)/8; } if (!P_IsObjectOnGround(player->mo) && !mapreset) { player->flashing = K_GetKartFlashing(player); // Sal: The old behavior was stupid and prone to accidental usage. // Let's rip off Mania instead, and turn this into a Drop Dash! if (buttons & BT_ACCELERATE) player->dropdash++; else player->dropdash = 0; if (player->dropdash == TICRATE/4) S_StartSound(player->mo, sfx_ddash); if ((player->dropdash >= TICRATE/4) && (player->dropdash & 1)) player->mo->colorized = true; else player->mo->colorized = false; } else { if ((buttons & BT_ACCELERATE) && (player->dropdash >= TICRATE/4)) { S_StartSound(player->mo, sfx_s23c); player->startboost = 50; K_SpawnDashDustRelease(player); } player->mo->colorized = false; player->dropdash = 0; player->respawn = 0; } } } /** \brief Handles the state changing for moving players, moved here to eliminate duplicate code \param player player data \return void */ void K_KartMoveAnimation(player_t *player) { const fixed_t fastspeed = (K_GetKartSpeed(player, false, true) * 17) / 20; // 85% const fixed_t speedthreshold = player->mo->scale / 8; const boolean onground = P_IsObjectOnGround(player->mo); UINT16 buttons = K_GetKartButtons(player); const boolean spinningwheels = (player->speed > 0); const boolean lookback = ((buttons & BT_LOOKBACK) == BT_LOOKBACK); SINT8 turndir = 0; SINT8 destGlanceDir = 0; SINT8 drift = player->drift; if (!lookback) { player->pflags &= ~PF_GAINAX; if (player->cmd.turning < 0) { turndir = -1; } else if (player->cmd.turning > 0) { turndir = 1; } } else if (drift == 0) { // Prioritize looking back frames over turning turndir = 0; } // Sliptides: drift -> lookback frames if (abs(player->aizdriftturn) >= ANGLE_90 && !wadfiles[((skin_t *)player->mo->skin)->wadnum]->compatmode) { destGlanceDir = -(2*intsign(player->aizdriftturn)); player->glanceDir = destGlanceDir; drift = turndir = 0; player->pflags &= ~PF_GAINAX; } else if (player->aizdriftturn) { drift = intsign(player->aizdriftturn); turndir = 0; } else if (turndir == 0 && drift == 0) { // Only try glancing if you're driving straight. // This avoids all-players loops when we don't need it. destGlanceDir = K_GlanceAtPlayers(player); if (lookback == true) { statenum_t gainaxstate = S_GAINAX_TINY; if (destGlanceDir == 0) { if (player->glanceDir != 0) { // Keep to the side you were already on. if (player->glanceDir < 0) { destGlanceDir = -1; } else { destGlanceDir = 1; } } else { // Look to your right by default destGlanceDir = -1; } } else { // Looking back AND glancing? Amplify the look! destGlanceDir *= 2; if (player->itemamount && player->itemtype) gainaxstate = S_GAINAX_HUGE; else gainaxstate = S_GAINAX_MID1; } if (destGlanceDir && !(player->pflags & PF_GAINAX)) { mobj_t *gainax = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_GAINAX); gainax->movedir = (destGlanceDir < 0) ? (ANGLE_270-ANG10) : (ANGLE_90+ANG10); P_SetTarget(&gainax->target, player->mo); P_SetMobjState(gainax, gainaxstate); gainax->flags2 |= MF2_AMBUSH; player->pflags |= PF_GAINAX; } } else if (player->cmd.forwardmove < 0 && destGlanceDir == 0) { // Reversing -- like looking back, but doesn't stack on the other glances. if (player->glanceDir != 0) { // Keep to the side you were already on. if (player->glanceDir < 0) { destGlanceDir = -1; } else { destGlanceDir = 1; } } else { // Look to your right by default destGlanceDir = -1; } } } else { // Not glancing destGlanceDir = 0; player->glanceDir = 0; } #define SetState(sn) \ if (player->mo->state != &states[sn]) \ P_SetPlayerMobjState(player->mo, sn) if (onground == false) { // Only use certain frames in the air, to make it look like your tires are spinning fruitlessly! if (drift > 0) { // Neutral drift SetState(S_KART_DRIFT_L); } else if (drift < 0) { // Neutral drift SetState(S_KART_DRIFT_R); } else { if (turndir == -1) { SetState(S_KART_FAST_R); } else if (turndir == 1) { SetState(S_KART_FAST_L); } else { switch (player->glanceDir) { case -2: SetState(S_KART_FAST_LOOK_R); break; case 2: SetState(S_KART_FAST_LOOK_L); break; case -1: SetState(S_KART_FAST_GLANCE_R); break; case 1: SetState(S_KART_FAST_GLANCE_L); break; default: SetState(S_KART_FAST); break; } } } if (!spinningwheels) { // TODO: The "tires still in the air" states should have it's own SPR2s. // This was a quick hack to get the same functionality with less work, // but it's really dunderheaded & isn't customizable at all. player->mo->frame = (player->mo->frame & ~FF_FRAMEMASK); player->mo->tics++; // Makes it properly use frame 0 } } else { if (drift > 0) { // Drifting LEFT! if (turndir == -1) { // Right -- outwards drift SetState(S_KART_DRIFT_L_OUT); } else if (turndir == 1) { // Left -- inwards drift SetState(S_KART_DRIFT_L_IN); } else { // Neutral drift SetState(S_KART_DRIFT_L); } } else if (drift < 0) { // Drifting RIGHT! if (turndir == -1) { // Right -- inwards drift SetState(S_KART_DRIFT_R_IN); } else if (turndir == 1) { // Left -- outwards drift SetState(S_KART_DRIFT_R_OUT); } else { // Neutral drift SetState(S_KART_DRIFT_R); } } else { if (player->speed >= fastspeed && player->speed >= (player->lastspeed - speedthreshold)) { // Going REAL fast! if (turndir == -1) { SetState(S_KART_FAST_R); } else if (turndir == 1) { SetState(S_KART_FAST_L); } else { switch (player->glanceDir) { case -2: SetState(S_KART_FAST_LOOK_R); break; case 2: SetState(S_KART_FAST_LOOK_L); break; case -1: SetState(S_KART_FAST_GLANCE_R); break; case 1: SetState(S_KART_FAST_GLANCE_L); break; default: SetState(S_KART_FAST); break; } } } else { if (spinningwheels) { // Drivin' slow. if (turndir == -1) { SetState(S_KART_SLOW_R); } else if (turndir == 1) { SetState(S_KART_SLOW_L); } else { switch (player->glanceDir) { case -2: SetState(S_KART_SLOW_LOOK_R); break; case 2: SetState(S_KART_SLOW_LOOK_L); break; case -1: SetState(S_KART_SLOW_GLANCE_R); break; case 1: SetState(S_KART_SLOW_GLANCE_L); break; default: SetState(S_KART_SLOW); break; } } } else { // Completely still. if (turndir == -1) { SetState(S_KART_STILL_R); } else if (turndir == 1) { SetState(S_KART_STILL_L); } else { switch (player->glanceDir) { case -2: SetState(S_KART_STILL_LOOK_R); break; case 2: SetState(S_KART_STILL_LOOK_L); break; case -1: SetState(S_KART_STILL_GLANCE_R); break; case 1: SetState(S_KART_STILL_GLANCE_L); break; default: SetState(S_KART_STILL); break; } } } } } } #undef SetState // Update your glance value to smooth it out. if (player->glanceDir > destGlanceDir) { player->glanceDir--; } else if (player->glanceDir < destGlanceDir) { player->glanceDir++; } if (!player->glanceDir) player->pflags &= ~PF_GAINAX; // Update lastspeed value -- we use to display slow driving frames instead of fast driving when slowing down. player->lastspeed = player->speed; } static void K_TauntVoiceTimers(player_t *player) { if (!player) return; player->karthud[khud_tauntvoices] = 6*TICRATE; player->karthud[khud_voices] = 4*TICRATE; } static void K_RegularVoiceTimers(player_t *player) { if (!player) return; player->karthud[khud_voices] = 4*TICRATE; if (player->karthud[khud_tauntvoices] < 4*TICRATE) player->karthud[khud_tauntvoices] = 4*TICRATE; } void K_PlayAttackTaunt(mobj_t *source) { sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons boolean tasteful = (!source->player || !source->player->karthud[khud_tauntvoices]); if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2)) S_StartSound(source, sfx_kattk1+pick); if (!tasteful) return; K_TauntVoiceTimers(source->player); } void K_PlayBoostTaunt(mobj_t *source) { sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons boolean tasteful = (!source->player || !source->player->karthud[khud_tauntvoices]); if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2)) S_StartSound(source, sfx_kbost1+pick); if (!tasteful) return; K_TauntVoiceTimers(source->player); } void K_PlayOvertakeSound(mobj_t *source) { boolean tasteful = (!source->player || !source->player->karthud[khud_voices]); if (!(gametype == GT_RACE)) // Only in race return; // 4 seconds from before race begins, 10 seconds afterwards if (leveltime < starttime+(10*TICRATE)) return; if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2)) S_StartSound(source, sfx_kslow); if (!tasteful) return; K_RegularVoiceTimers(source->player); } void K_PlayPainSound(mobj_t *source, mobj_t *other) { sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons sfxenum_t sfx_id = ((skin_t *)source->skin)->soundsid[S_sfx[sfx_khurt1 + pick].skinsound]; boolean alwaysHear = false; if (other != NULL && P_MobjWasRemoved(other) == false && other->player != NULL) { alwaysHear = P_IsDisplayPlayer(other->player); } if (cv_kartvoices.value) { S_StartSound(alwaysHear ? NULL : source, sfx_id); } K_RegularVoiceTimers(source->player); } void K_PlayHitEmSound(mobj_t *source, mobj_t *other) { sfxenum_t sfx_id = ((skin_t *)source->skin)->soundsid[S_sfx[sfx_khitem].skinsound]; boolean alwaysHear = false; if (other != NULL && P_MobjWasRemoved(other) == false && other->player != NULL) { alwaysHear = P_IsDisplayPlayer(other->player); } if (cv_kartvoices.value) { S_StartSound(alwaysHear ? NULL : source, sfx_id); } K_RegularVoiceTimers(source->player); } void K_TryHurtSoundExchange(mobj_t *victim, mobj_t *attacker) { if (victim == NULL || P_MobjWasRemoved(victim) == true || victim->player == NULL) { return; } // In a perfect world we could move this here, but there's // a few niche situations where we want a pain sound from // the victim, but no confirm sound from the attacker. //K_PlayPainSound(victim, attacker); if (attacker == NULL || P_MobjWasRemoved(attacker) == true || attacker->player == NULL) { return; } attacker->player->confirmVictim = (victim->player - players); attacker->player->confirmVictimDelay = TICRATE/2; if (attacker->player->follower != NULL) { const follower_t *fl = &followers[attacker->player->followerskin]; attacker->player->follower->movecount = fl->hitconfirmtime; // movecount is used to play the hitconfirm animation for followers. } } void K_PlayPowerGloatSound(mobj_t *source) { if (cv_kartvoices.value) { S_StartSound(source, sfx_kgloat); } K_RegularVoiceTimers(source->player); } static void K_HandleDelayedHitByEm(player_t *player) { if (player->confirmVictimDelay == 0) { return; } player->confirmVictimDelay--; if (player->confirmVictimDelay == 0) { mobj_t *victim = NULL; if (player->confirmVictim < MAXPLAYERS && playeringame[player->confirmVictim]) { player_t *victimPlayer = &players[player->confirmVictim]; if (victimPlayer != NULL && victimPlayer->spectator == false) { victim = victimPlayer->mo; } } K_PlayHitEmSound(player->mo, victim); } } void K_MomentumToFacing(player_t *player) { angle_t dangle = player->mo->angle - K_MomentumAngle(player->mo); if (dangle > ANGLE_180) dangle = InvAngle(dangle); // If you aren't on the ground or are moving in too different of a direction don't do this if (player->mo->eflags & MFE_JUSTHITFLOOR) ; // Just hit floor ALWAYS redirects else if (!P_IsObjectOnGround(player->mo) || dangle > ANGLE_90) return; P_Thrust(player->mo, player->mo->angle, player->speed - FixedMul(player->speed, player->mo->friction)); player->mo->momx = FixedMul(player->mo->momx - player->cmomx, player->mo->friction) + player->cmomx; player->mo->momy = FixedMul(player->mo->momy - player->cmomy, player->mo->friction) + player->cmomy; } boolean K_ApplyOffroad(player_t *player) { boolean sneakertimer = CANTCHAINOFFROAD ? (player->sneakertimer && player->realsneakertimer) : player->sneakertimer; if (player->invincibilitytimer || player->hyudorotimer || sneakertimer) return false; return true; } boolean K_SlopeResistance(player_t *player) { if (player->invincibilitytimer || player->sneakertimer || player->flamestore) return true; return false; } fixed_t K_PlayerTripwireSpeedThreshold(player_t *player) { fixed_t required_speed = 2 * K_GetKartSpeed(player, false, false); // 200% if (player->offroad && K_ApplyOffroad(player)) { // Increase to 300% if you're lawnmowering. required_speed = (required_speed * 3) / 2; } if (player->botvars.rubberband > FRACUNIT && K_PlayerUsesBotMovement(player) == true) { // Make it harder for bots to do this when rubberbanding. // This is actually biased really hard against the bot, // because the bot rubberbanding speed increase is // decreased with other boosts. required_speed = FixedMul(required_speed, player->botvars.rubberband); } return required_speed; } tripwirepass_t K_TripwirePassConditions(player_t *player) { if ( player->invincibilitytimer || (player->sneakertimer && player->realsneakertimer) ) return TRIPWIRE_BLASTER; if ( player->flamestore || ((player->speed > K_PlayerTripwireSpeedThreshold(player)) && player->tripwireReboundDelay == 0) ) return TRIPWIRE_BOOST; if ( player->growshrinktimer > 0 || player->hyudorotimer ) return TRIPWIRE_IGNORE; return TRIPWIRE_NONE; } boolean K_TripwirePass(player_t *player) { return (player->tripwirePass != TRIPWIRE_NONE); } boolean K_ItemMobjAllowedtoWaterRun(mobj_t *item) { switch (item->type) { case MT_ORBINAUT: case MT_JAWZ: case MT_JAWZ_DUD: case MT_DROPTARGET: case MT_BANANA_SHIELD: case MT_DROPTARGET_SHIELD: case MT_SSMINE_SHIELD: return true; break; default: return false; break; } return false; } boolean K_WaterRun(mobj_t *mobj) { // Let dragged items waterrun with the player for free. switch (mobj->type) { case MT_BANANA_SHIELD: case MT_DROPTARGET_SHIELD: case MT_SSMINE_SHIELD: if (mobj->target && (mobj->target->flags2 & MF2_WATERRUN)) return true; break; default: break; } if (mobj->flags2 & MF2_WATERRUN) { return true; } return false; } void K_SpawnWaterTrail(mobj_t *mobj) { fixed_t topspeed = mobj->player ? K_GetKartSpeed(mobj->player, false, false) : K_GetKartSpeedFromStat(5); fixed_t runspd = 14*mobj->scale; //srb2kart runspd = FixedMul(runspd, mobj->movefactor); fixed_t trailScale; if (topspeed > runspd) { if (mobj->player) { trailScale = FixedMul(FixedDiv(mobj->player->speed - runspd, topspeed - runspd), mapobjectscale); } else { trailScale = FixedMul(FixedDiv(R_PointToDist2(0, 0, mobj->momx, mobj->momy) - runspd, topspeed - runspd), mapobjectscale); } } else trailScale = mapobjectscale; // Scaling is based off difference between runspeed and top speed if (trailScale > 0) { const angle_t forwardangle = K_MomentumAngle(mobj); const fixed_t visualradius = mobj->radius + (8 * mobj->scale); const SINT8 numFrames = 5; const INT32 curFrame = (leveltime % numFrames)|FF_PAPERSPRITE; fixed_t x1, x2, y1, y2; mobj_t *water; x1 = mobj->x + mobj->momx + P_ReturnThrustX(mobj, forwardangle + ANGLE_90, visualradius); y1 = mobj->y + mobj->momy + P_ReturnThrustY(mobj, forwardangle + ANGLE_90, visualradius); x1 = x1 + P_ReturnThrustX(mobj, forwardangle, visualradius); y1 = y1 + P_ReturnThrustY(mobj, forwardangle, visualradius); x2 = mobj->x + mobj->momx + P_ReturnThrustX(mobj, forwardangle - ANGLE_90, visualradius); y2 = mobj->y + mobj->momy + P_ReturnThrustY(mobj, forwardangle - ANGLE_90, visualradius); x2 = x2 + P_ReturnThrustX(mobj, forwardangle, visualradius); y2 = y2 + P_ReturnThrustY(mobj, forwardangle, visualradius); // Left // underlay water = P_SpawnMobj(x1, y1, ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->waterbottom - FixedMul(mobjinfo[MT_WATERTRAILUNDERLAY].height, mobj->scale) : mobj->watertop), MT_WATERTRAILUNDERLAY); water->angle = forwardangle - ANGLE_180 - ANGLE_22h; water->destscale = trailScale; water->momx = mobj->momx; water->momy = mobj->momy; water->momz = mobj->momz; P_SetScale(water, trailScale); P_SetMobjState(water, S_WATERTRAILSUNDERLAY); water->frame = curFrame|FF_ADD|FF_TRANS40; // overlay water = P_SpawnMobj(x1, y1, ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->waterbottom - FixedMul(mobjinfo[MT_WATERTRAIL].height, mobj->scale) : mobj->watertop), MT_WATERTRAIL); water->angle = forwardangle - ANGLE_180 - ANGLE_22h; water->destscale = trailScale; water->momx = mobj->momx; water->momy = mobj->momy; water->momz = mobj->momz; P_SetScale(water, trailScale); P_SetMobjState(water, S_WATERTRAILS); water->frame = curFrame; // Right // Underlay water = P_SpawnMobj(x2, y2, ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->waterbottom - FixedMul(mobjinfo[MT_WATERTRAILUNDERLAY].height, mobj->scale) : mobj->watertop), MT_WATERTRAILUNDERLAY); water->angle = forwardangle - ANGLE_180 + ANGLE_22h; water->destscale = trailScale; water->momx = mobj->momx; water->momy = mobj->momy; water->momz = mobj->momz; P_SetScale(water, trailScale); P_SetMobjState(water, S_WATERTRAILSUNDERLAY); water->frame = curFrame|FF_ADD|FF_TRANS40; // Overlay water = P_SpawnMobj(x2, y2, ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->waterbottom - FixedMul(mobjinfo[MT_WATERTRAIL].height, mobj->scale) : mobj->watertop), MT_WATERTRAIL); water->angle = forwardangle - ANGLE_180 + ANGLE_22h; water->destscale = trailScale; water->momx = mobj->momx; water->momy = mobj->momy; water->momz = mobj->momz; P_SetScale(water, trailScale); P_SetMobjState(water, S_WATERTRAILS); water->frame = curFrame; if (!S_SoundPlaying(mobj, sfx_s3kdbs)) { const INT32 volume = (min(trailScale, FRACUNIT) * 255) / FRACUNIT; S_StartSoundAtVolume(mobj, sfx_s3kdbs, volume); } } } static fixed_t K_FlameShieldDashVar(INT32 val) { // 1 second = 15% + ????% top speed return (FRACUNIT/6) + (((val * (FRACUNIT)) / TICRATE) / 2); } static inline fixed_t K_GetProjectileSpeed(void) { switch (gamespeed) { case 0: return 68*mapobjectscale; // Avg Speed is 34 break; case 2: return 96*mapobjectscale; // Avg Speed is 48 break; default: return 82*mapobjectscale; // Avg Speed is 41 break; } } static inline fixed_t K_GetSneakerBoostSpeed(void) { switch (gamespeed) { case 0: return EASYSNEAKERSPEEDBOOST; break; case 2: return HARDSNEAKERSPEEDBOOST; break; default: return NORMALSNEAKERSPEEDBOOST; break; } } static fixed_t diminish(fixed_t speedboost) { return FixedSqrt(speedboost + CALC_ARG_OFFSET) - CALC_RET_OFFSET; } void K_DoBoost(player_t *player, fixed_t speedboost, fixed_t accelboost, boolean stack, boolean visible) { if (stack && stackingactive) { player->boostinfo.stackspeedboost += speedboost; } player->boostinfo.nonstackspeedboost = max(player->boostinfo.nonstackspeedboost, speedboost); player->boostinfo.accelboost = max(player->boostinfo.accelboost, accelboost); if (visible) { player->boostinfo.grade = CLAMP(player->boostinfo.grade+1, 0, stackingactive ? UINT8_MAX : 1); } } void K_ClearBoost(player_t *player) { player->boostinfo.stackspeedboost = 0; player->boostinfo.nonstackspeedboost = 0; player->boostinfo.accelboost = 0; player->boostinfo.grade = 0; } // sets boostpower, speedboost and accelboost to whatever we need it to be static void K_GetKartBoostPower(player_t *player) { fixed_t boostpower = FRACUNIT; fixed_t finalspeedboost = 0; fixed_t finalaccelboost = 0; UINT8 finalgrade = 0; fixed_t prevspeedboost = player->speedboost; if (player->spinouttimer && player->wipeoutslow == 1) // Slow down after you've been bumped { player->boostpower = player->speedboost = player->accelboost = 0; return; } // Offroad is separate, it's difficult to factor it in with a variable value anyway. if (K_ApplyOffroad(player) && player->offroad >= 0) boostpower = FixedDiv(boostpower, player->offroad + FRACUNIT); if (player->bananadrag > TICRATE) boostpower = (4*boostpower)/5; // Banana drag/offroad dust if (boostpower < FRACUNIT && player->mo && P_IsObjectOnGround(player->mo) && player->speed > 0 && !player->spectator) { K_SpawnWipeoutTrail(player->mo, true); if (leveltime % 6 == 0) S_StartSound(player->mo, sfx_cdfm70); } if (player->sneakertimer) // Sneaker { UINT8 i; fixed_t sneakerspeedboost = K_GetSneakerBoostSpeed(); UINT8 numsneakers = player->numsneakers ? player->numsneakers : 1; for (i = 0; i < numsneakers; i++) { K_DoBoost(player, sneakerspeedboost, SNEAKERACCELBOOST, true, true); // + ???% top speed, + 800% acceleration } } if (player->invincibilitytimer) // Invincibility { K_DoBoost(player, INVINSPEEDBOOST, INVINACCELBOOST, false, false); // + 37.5% top speed, + 300% acceleration } if (player->growshrinktimer > 0) // Grow { K_DoBoost(player, GROWSPEEDBOOST, GROWACCELBOOST, false, false); // + 20% top speed, + 0% acceleration } if (player->flamestore) // Flame Shield dash { fixed_t dash = K_FlameShieldDashVar(player->flamedash); fixed_t intermediate = 0; fixed_t boost = 0; fixed_t val = FLAMESPEEDVAL; // Rim idea: diminish starts around 1.2x sneaker speed and plateaus around 1.4-1.5x intermediate = FixedDiv(FixedMul(val, FRACUNIT*-1/2) - FRACUNIT/4,-val+FRACUNIT/2); boost = FixedMul(val,(FRACUNIT-FixedDiv(FRACUNIT,(dash+intermediate)))); K_DoBoost(player, boost, FLAMEACCELBOOST, false, false); } if (player->startboost) // Startup Boost { K_DoBoost(player, STARTSPEEDBOOST, STARTACCELBOOST, true, true); // + 25% top speed, + 600% acceleration } if (player->driftboost) // Drift Boost { K_DoBoost(player, DRIFTSPEEDBOOST, DRIFTACCELBOOST, true, true); // + 25% top speed, + 400% acceleration } if (player->ringboost) // Ring Boost { K_DoBoost(player, RINGSPEEDBOOST, RINGACCELBOOST, true, true); // + 20% top speed, + 400% acceleration } // This should always remain the last boost if (player->botvars.rubberband > FRACUNIT && K_PlayerUsesBotMovement(player) == true) { K_DoBoost(player, player->botvars.rubberband - FRACUNIT, 0, false, false); } player->boostpower = boostpower; // Combine nonstack and stacking boosts here. finalspeedboost = max(player->boostinfo.nonstackspeedboost, diminish(player->boostinfo.stackspeedboost)); finalaccelboost = player->boostinfo.accelboost; finalgrade = player->boostinfo.grade; // value smoothing if (stackingactive) { if (player->offroad && K_ApplyOffroad(player)) { player->speedboost = max(finalspeedboost, player->speedboost)/2; player->accelboost = max(finalaccelboost, player->accelboost)/2; } else if (finalspeedboost >= prevspeedboost) { player->speedboost = max(player->speedboost, finalspeedboost); player->accelboost = max(finalaccelboost, player->accelboost); } else if ((player->aizdriftstrat && abs((player->drift) < 5)) || (K_GetKartButtons(player) & BT_BRAKE)) { player->speedboost = max(prevspeedboost - SPEEDBOOSTDROPOFF_BRAKE, min(player->speedboost, MAXVANILLABOOST)); player->accelboost = max(finalaccelboost, player->accelboost); } else { player->speedboost = prevspeedboost - SPEEDBOOSTDROPOFF; player->accelboost = max(finalaccelboost, player->accelboost); } } else { if (finalspeedboost > player->speedboost) { player->speedboost = finalspeedboost; } else { player->speedboost += (finalspeedboost - player->speedboost) / (TICRATE/2); } } player->accelboost = finalaccelboost; player->numboosts = finalgrade; K_ClearBoost(player); } // Returns value based on being Grown or Shrunk otherwise returns FRACUNIT static fixed_t K_GrowShrinkOrNormal(const player_t *player) { fixed_t scaleDiff = player->mo->scale - mapobjectscale; fixed_t playerScale = FixedDiv(player->mo->scale, mapobjectscale); fixed_t speedMul = FRACUNIT; if (scaleDiff > 0 || scaleDiff < 0) { // Grown or Shrunk speedMul = playerScale; } return speedMul; } // Returns kart speed from a stat. Boost power and scale are NOT taken into account, no player or object is necessary. fixed_t K_GetKartSpeedFromStat(UINT8 kartspeed) { const fixed_t xspd = 3072; fixed_t g_cc = FRACUNIT; fixed_t k_speed = 150; fixed_t finalspeed; switch (gamespeed) { case 0: g_cc = 53248 + xspd; // 50cc = 81.25 + 4.69 = 85.94% break; case 2: g_cc = 77824 + xspd; // 150cc = 118.75 + 4.69 = 123.44% break; default: g_cc = 65536 + xspd; // 100cc = 100.00 + 4.69 = 104.69% break; } k_speed += kartspeed*3; // 153 - 177 finalspeed = FixedMul(k_speed<<14, g_cc); return finalspeed; } fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower, boolean dorubberband) { fixed_t k_speed = 150; fixed_t g_cc = FRACUNIT; fixed_t xspd = 3072; // 4.6875 aka 3/64 UINT8 kartspeed = player->kartspeed; fixed_t finalspeed; if (doboostpower && !player->pogospring && !P_IsObjectOnGround(player->mo)) return (75*mapobjectscale); // air speed cap switch (gamespeed) { case 0: g_cc = 53248 + xspd; // 50cc = 81.25 + 4.69 = 85.94% break; case 2: g_cc = 77824 + xspd; // 150cc = 118.75 + 4.69 = 123.44% break; default: g_cc = 65536 + xspd; // 100cc = 100.00 + 4.69 = 104.69% break; } if ((gametyperules & GTR_KARMA) && (player->bumper <= 0)) kartspeed = 1; k_speed += kartspeed*3; // 153 - 177 finalspeed = FixedMul(FixedMul(k_speed<<14, g_cc), player->mo->scale); if (K_PlayerUsesBotMovement(player)) { // Increase bot speed by 1-10% depending on difficulty const fixed_t modifier = K_BotMapModifier(); fixed_t add = ((player->botvars.difficulty-1) * FixedMul(FRACUNIT / 10, modifier)) / (DIFFICULTBOT-1); finalspeed = FixedMul(finalspeed, FRACUNIT + add); if (player->bot && player->botvars.rival) { // +10% top speed for the rival finalspeed = FixedMul(finalspeed, 11*FRACUNIT/10); } } if (dorubberband == true && player->botvars.rubberband < FRACUNIT && K_PlayerUsesBotMovement(player) == true) { finalspeed = FixedMul(finalspeed, player->botvars.rubberband); } if (doboostpower) finalspeed = FixedMul(finalspeed, player->boostpower+player->speedboost); if (player->outrun != 0) { // Milky Way's roads finalspeed += FixedMul(player->outrun, K_GrowShrinkOrNormal(player)); } return finalspeed; } fixed_t K_GetKartAccel(player_t *player) { fixed_t k_accel = 32; // 36; UINT8 kartspeed = player->kartspeed; if ((gametyperules & GTR_KARMA) && player->bumper <= 0) kartspeed = 1; k_accel += 4 * (9 - kartspeed); // 32 - 64 return FixedMul(k_accel, FRACUNIT+player->accelboost); } UINT16 K_GetKartFlashing(player_t *player) { UINT16 tics = flashingtics; if (player == NULL) { return tics; } tics += (tics/8) * (player->kartspeed); return tics; } boolean K_PlayerShrinkCheat(player_t *player) { return ( (player->pflags & PF_SHRINKACTIVE) && (player->bot == false) && (modeattacking == false) // Anyone want to make another record attack category? ); } void K_UpdateShrinkCheat(player_t *player) { const boolean mobjValid = (player->mo != NULL && P_MobjWasRemoved(player->mo) == false); if (player->pflags & PF_SHRINKME) { player->pflags |= PF_SHRINKACTIVE; } else { player->pflags &= ~PF_SHRINKACTIVE; } if (mobjValid == true && K_PlayerShrinkCheat(player) == true) { player->mo->destscale = FixedMul(mapobjectscale, SHRINK_SCALE); } } boolean K_KartKickstart(player_t *player) { return ((player->pflags & PF_KICKSTARTACCEL) && (!K_PlayerUsesBotMovement(player)) && (player->kickstartaccel >= ACCEL_KICKSTART)); } UINT16 K_GetKartButtons(player_t *player) { return (player->cmd.buttons | (K_KartKickstart(player) ? BT_ACCELERATE : 0)); } SINT8 K_GetForwardMove(player_t *player) { SINT8 forwardmove = player->cmd.forwardmove; if (K_KartKickstart(player)) // unlike the brute forward of sneakers, allow for backwards easing here { forwardmove = MAXPLMOVE; } if ((player->exiting || mapreset) || player->pflags & PF_STASIS || player->spinouttimer) // pw_introcam? { forwardmove = 0; if (player->sneakertimer) forwardmove = MAXPLMOVE; } return forwardmove; } fixed_t K_GetNewSpeed(player_t *player) { const fixed_t accelmax = 4000; const fixed_t p_speed = K_GetKartSpeed(player, true, true); fixed_t p_accel = K_GetKartAccel(player); fixed_t newspeed, oldspeed, finalspeed; if (K_PlayerUsesBotMovement(player) == true && player->botvars.rubberband > 0) { // Acceleration is tied to top speed... // so if we want JUST a top speed boost, we have to do this... p_accel = FixedDiv(p_accel, player->botvars.rubberband); } oldspeed = R_PointToDist2(0, 0, player->rmomx, player->rmomy); newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), ORIG_FRICTION); if (player->pogospring) // Pogo Spring minimum/maximum thrust { const fixed_t hscale = mapobjectscale; fixed_t minspeed = 24*hscale; fixed_t maxspeed = 28*hscale; if (player->mo->terrain) { minspeed = player->mo->terrain->pogoSpringMin*hscale; maxspeed = player->mo->terrain->pogoSpringMax*hscale; } if (newspeed > maxspeed && player->pogospring == 2) newspeed = maxspeed; if (newspeed < minspeed) newspeed = minspeed; } finalspeed = newspeed - oldspeed; return finalspeed; } fixed_t K_3dKartMovement(player_t *player, boolean onground) { fixed_t finalspeed = K_GetNewSpeed(player); SINT8 forwardmove = K_GetForwardMove(player); if (!onground) return 0; // If the player isn't on the ground, there is no change in speed // forwardmove is: // 50 while accelerating, // 25 while clutching, // 0 with no gas, and // -25 when only braking. finalspeed *= forwardmove/25; finalspeed /= 2; if (forwardmove < 0 && finalspeed > mapobjectscale*2) return finalspeed/2; else if (forwardmove < 0) return -mapobjectscale/2; if (finalspeed < 0) finalspeed = 0; return finalspeed; } angle_t K_MomentumAngle(mobj_t *mo) { if (FixedHypot(mo->momx, mo->momy) >= mo->scale) { return R_PointToAngle2(0, 0, mo->momx, mo->momy); } else { return mo->angle; // default to facing angle, rather than 0 } } void K_AwardPlayerRings(player_t *player, UINT16 rings, boolean overload) { UINT16 superring; if (!overload) { INT32 totalrings = RINGTOTAL(player) + (player->superring); /* capped at 20 rings */ if ((totalrings + rings) > 20) { if (totalrings >= 20) return; // woah dont let that go negative buster rings = (20 - totalrings); } } superring = player->superring + rings; /* check if not overflow */ if (superring > player->superring) player->superring = superring; } void K_DoInstashield(player_t *player) { mobj_t *layera; mobj_t *layerb; if (player->instashield > 0) return; player->instashield = 15; // length of instashield animation S_StartSound(player->mo, sfx_cdpcm9); layera = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INSTASHIELDA); layera->old_x = player->mo->old_x; layera->old_y = player->mo->old_y; layera->old_z = player->mo->old_z; P_SetTarget(&layera->target, player->mo); layerb = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INSTASHIELDB); layerb->old_x = player->mo->old_x; layerb->old_y = player->mo->old_y; layerb->old_z = player->mo->old_z; P_SetTarget(&layerb->target, player->mo); } void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UINT8 bumpersRemoved) { UINT8 points = 1; boolean trapItem = false; if (player == NULL || victim == NULL) { // Invalid player or victim return; } if (player == victim) { // You cannot give yourself points return; } if ((inflictor && !P_MobjWasRemoved(inflictor)) && (inflictor->type == MT_BANANA && inflictor->health > 1)) { trapItem = true; } // Only apply score bonuses to non-bananas if (trapItem == false) { if (K_IsPlayerWanted(victim)) { // +3 points for hitting a wanted player points = 3; } else if (gametyperules & GTR_BUMPERS) { if ((victim->bumper > 0) && (victim->bumper <= bumpersRemoved)) { // +2 points for finishing off a player points = 2; } } } if (gametyperules & GTR_POINTLIMIT) { P_AddPlayerScore(player, points); K_SpawnBattlePoints(player, victim, points); } } void K_SpinPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 type) { (void)inflictor; (void)source; K_DirectorFollowAttack(player, inflictor, source); player->spinouttype = type; if (( player->spinouttype & KSPIN_THRUST )) { // At spinout, player speed is increased to 1/4 their regular speed, moving them forward fixed_t spd = K_GetKartSpeed(player, true, true) / 4; if (player->speed < spd) P_InstaThrust(player->mo, player->mo->angle, FixedMul(spd, player->mo->scale)); S_StartSound(player->mo, sfx_slip); } K_StatPlayerHit(player, source ? source->player : NULL); player->spinouttimer = (3*TICRATE/2)+2; P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); } static void K_RemoveGrowShrink(player_t *player) { if (player->mo && !P_MobjWasRemoved(player->mo)) { if (player->growshrinktimer > 0) // Play Shrink noise S_StartSound(player->mo, sfx_kc59); else if (player->growshrinktimer < 0) // Play Grow noise S_StartSound(player->mo, sfx_kc5a); K_KartResetPlayerColor(player); player->mo->scalespeed = mapobjectscale/TICRATE; player->mo->destscale = mapobjectscale; if (K_PlayerShrinkCheat(player) == true) { player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE); } } player->growshrinktimer = 0; player->growcancel = -1; P_RestoreMusic(player); } void K_SquishPlayer(player_t *player, mobj_t *inflictor, mobj_t *source) { (void)inflictor; (void)source; player->squishedtimer = TICRATE; // Reduce Shrink timer if (player->growshrinktimer < 0) { player->growshrinktimer += TICRATE; if (player->growshrinktimer >= 0) K_RemoveGrowShrink(player); } player->mo->flags |= MF_NOCLIP; player->instashield = 15; } void K_ApplyTripWire(player_t *player, tripwirestate_t state) { // We are either softlocked or wildly misbehaving. Stop that! if (state == TRIPSTATE_BLOCKED && player->tripwireReboundDelay && (player->speed > 5 * K_GetKartSpeed(player, false, false))) P_DamageMobj(player->mo, NULL, NULL, 1, DMG_NORMAL); if (state == TRIPSTATE_PASSED) { S_StartSound(player->mo, sfx_ssa015); player->tripwireLeniency += TICRATE/2; } else if (state == TRIPSTATE_BLOCKED) { S_StartSound(player->mo, sfx_kc40); player->tripwireReboundDelay = 60; } player->tripwireState = 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 = 5; (void)source; K_DirectorFollowAttack(player, inflictor, source); player->mo->momz = 18*mapobjectscale*P_MobjFlip(player->mo); // please stop forgetting mobjflip checks!!!! if (player->mo->eflags & MFE_UNDERWATER) player->mo->momz = (117 * player->mo->momz) / 200; player->mo->momx = player->mo->momy = 0; player->spinouttype = KSPIN_EXPLOSION; player->spinouttimer = (3*TICRATE/2)+2; if (inflictor && !P_MobjWasRemoved(inflictor)) { if (inflictor->type == MT_SPBEXPLOSION && inflictor->extravalue1) { player->spinouttimer = ((5*player->spinouttimer)/2)+1; player->mo->momz *= 2; ringburst = 10; } } P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); if (P_IsDisplayPlayer(player)) P_StartQuake(64<bumper > 0 && prevBumpers == 0) { if (netgame) { CONS_Printf(M_GetText("%s is back in the game!\n"), player_names[player-players]); } } else if (player->bumper == 0 && prevBumpers > 0) { mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); P_SetTarget(&karmahitbox->target, player->mo); karmahitbox->destscale = player->mo->destscale; P_SetScale(karmahitbox, player->mo->scale); player->karmadelay = comebacktime; if (bossinfo.boss) { P_DoTimeOver(player); } else if (netgame) { CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]); } } if (K_IsPlayerWanted(player)) K_CalculateBattleWanted(); K_CheckBumpers(); } void K_DestroyBumpers(player_t *player, UINT8 amount) { UINT8 oldBumpers = player->bumper; if (!(gametyperules & GTR_BUMPERS)) { return; } amount = min(amount, player->bumper); if (amount == 0) { return; } player->bumper -= amount; K_HandleBumperChanges(player, oldBumpers); } void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount) { UINT8 oldPlayerBumpers = player->bumper; UINT8 oldVictimBumpers = victim->bumper; UINT8 tookBumpers = 0; if (!(gametyperules & GTR_BUMPERS)) { return; } amount = min(amount, victim->bumper); if (amount == 0) { return; } while ((tookBumpers < amount) && (victim->bumper > 0)) { UINT8 newbumper = player->bumper; angle_t newangle, diff; fixed_t newx, newy; mobj_t *newmo; if (newbumper <= 1) { diff = 0; } else { diff = FixedAngle(360*FRACUNIT/newbumper); } newangle = player->mo->angle; newx = player->mo->x + P_ReturnThrustX(player->mo, newangle + ANGLE_180, 64*FRACUNIT); newy = player->mo->y + P_ReturnThrustY(player->mo, newangle + ANGLE_180, 64*FRACUNIT); newmo = P_SpawnMobj(newx, newy, player->mo->z, MT_BATTLEBUMPER); newmo->threshold = newbumper; P_SetTarget(&newmo->tracer, victim->mo); P_SetTarget(&newmo->target, player->mo); newmo->angle = (diff * (newbumper-1)); newmo->color = victim->skincolor; if (newbumper+1 < 2) { P_SetMobjState(newmo, S_BATTLEBUMPER3); } else if (newbumper+1 < 3) { P_SetMobjState(newmo, S_BATTLEBUMPER2); } else { P_SetMobjState(newmo, S_BATTLEBUMPER1); } player->bumper++; player->karmapoints = 0; victim->bumper--; tookBumpers++; } if (tookBumpers == 0) { // No change occured. return; } // Play steal sound S_StartSound(player->mo, sfx_3db06); K_HandleBumperChanges(player, oldPlayerBumpers); K_HandleBumperChanges(victim, oldVictimBumpers); } // Spawns the purely visual explosion void K_SpawnMineExplosion(mobj_t *source, UINT8 color) { INT32 i, radius, height; mobj_t *smoldering = P_SpawnMobj(source->x, source->y, source->z, MT_SMOLDERING); mobj_t *dust; mobj_t *truc; INT32 speed, speed2; K_MatchGenericExtraFlags(smoldering, source); smoldering->tics = TICRATE*3; radius = source->radius>>FRACBITS; height = source->height>>FRACBITS; S_StartSound(smoldering, sfx_s3k4e); if (!color) color = SKINCOLOR_KETCHUP; for (i = 0; i < 32; i++) { dust = P_SpawnMobj(source->x, source->y, source->z, MT_SMOKE); P_SetMobjState(dust, S_OPAQUESMOKE1); dust->angle = (ANGLE_180/16) * i; P_SetScale(dust, source->scale); dust->destscale = source->scale*10; dust->scalespeed = source->scale/12; P_InstaThrust(dust, dust->angle, FixedMul(20*FRACUNIT, source->scale)); truc = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT, source->y + P_RandomRange(-radius, radius)*FRACUNIT, source->z + P_RandomRange(0, height)*FRACUNIT, MT_BOOMEXPLODE); K_MatchGenericExtraFlags(truc, source); P_SetScale(truc, source->scale); truc->destscale = source->scale*6; truc->scalespeed = source->scale/12; speed = FixedMul(10*FRACUNIT, source->scale)>>FRACBITS; truc->momx = P_RandomRange(-speed, speed)*FRACUNIT; truc->momy = P_RandomRange(-speed, speed)*FRACUNIT; speed = FixedMul(20*FRACUNIT, source->scale)>>FRACBITS; truc->momz = P_RandomRange(-speed, speed)*FRACUNIT*P_MobjFlip(truc); if (truc->eflags & MFE_UNDERWATER) truc->momz = (117 * truc->momz) / 200; truc->color = color; } for (i = 0; i < 16; i++) { dust = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT, source->y + P_RandomRange(-radius, radius)*FRACUNIT, source->z + P_RandomRange(0, height)*FRACUNIT, MT_SMOKE); P_SetMobjState(dust, S_OPAQUESMOKE1); P_SetScale(dust, source->scale); dust->destscale = source->scale*10; dust->scalespeed = source->scale/12; dust->tics = 30; dust->momz = P_RandomRange(FixedMul(3*FRACUNIT, source->scale)>>FRACBITS, FixedMul(7*FRACUNIT, source->scale)>>FRACBITS)*FRACUNIT; truc = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT, source->y + P_RandomRange(-radius, radius)*FRACUNIT, source->z + P_RandomRange(0, height)*FRACUNIT, MT_BOOMPARTICLE); K_MatchGenericExtraFlags(truc, source); P_SetScale(truc, source->scale); truc->destscale = source->scale*5; truc->scalespeed = source->scale/12; speed = FixedMul(20*FRACUNIT, source->scale)>>FRACBITS; truc->momx = P_RandomRange(-speed, speed)*FRACUNIT; truc->momy = P_RandomRange(-speed, speed)*FRACUNIT; speed = FixedMul(15*FRACUNIT, source->scale)>>FRACBITS; speed2 = FixedMul(45*FRACUNIT, source->scale)>>FRACBITS; truc->momz = P_RandomRange(speed, speed2)*FRACUNIT*P_MobjFlip(truc); if (P_RandomChance(FRACUNIT/2)) truc->momz = -truc->momz; if (truc->eflags & MFE_UNDERWATER) truc->momz = (117 * truc->momz) / 200; truc->tics = TICRATE*2; truc->color = color; } } static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, INT32 flags2, fixed_t speed) { mobj_t *th; fixed_t x, y, z; fixed_t finalspeed = speed; mobj_t *throwmo; if (source->player && source->player->speed > K_GetKartSpeed(source->player, false, false)) { angle_t input = source->angle - an; boolean invert = (input > ANGLE_180); if (invert) input = InvAngle(input); finalspeed = max(speed, FixedMul(speed, FixedMul( FixedDiv(source->player->speed, K_GetKartSpeed(source->player, false, false)), // Multiply speed to be proportional to your own, boosted maxspeed. (((180<x + source->momx + FixedMul(finalspeed, FINECOSINE(an>>ANGLETOFINESHIFT)); y = source->y + source->momy + FixedMul(finalspeed, FINESINE(an>>ANGLETOFINESHIFT)); z = source->z; // spawn on the ground please if (P_MobjFlip(source) < 0) { z = source->z+source->height - mobjinfo[type].height; } th = P_SpawnMobj(x, y, z, type); th->flags2 |= flags2; if (source->flags2 & MF2_WATERRUN) { // Allow certain items to run on water as well! if (K_ItemMobjAllowedtoWaterRun(th)) { th->flags2 |= MF2_WATERRUN; } } th->threshold = 10; if (th->info->seesound) S_StartSound(source, th->info->seesound); P_SetTarget(&th->target, source); if (P_IsObjectOnGround(source)) { // floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn // This should set it for FOFs P_SetOrigin(th, th->x, th->y, th->z); // spawn on the ground if the player is on the ground if (P_MobjFlip(source) < 0) { th->z = th->ceilingz - th->height; th->eflags |= MFE_VERTICALFLIP; } else th->z = th->floorz; } th->angle = an; th->momx = FixedMul(finalspeed, FINECOSINE(an>>ANGLETOFINESHIFT)); th->momy = FixedMul(finalspeed, FINESINE(an>>ANGLETOFINESHIFT)); switch (type) { case MT_ORBINAUT: if (source && source->player) th->color = source->player->skincolor; else th->color = SKINCOLOR_GREY; th->movefactor = finalspeed; break; case MT_JAWZ: if (source && source->player) { INT32 lasttarg = source->player->lastjawztarget; th->cvmem = source->player->skincolor; if ((lasttarg >= 0 && lasttarg < MAXPLAYERS) && playeringame[lasttarg] && !players[lasttarg].spectator && players[lasttarg].mo) { P_SetTarget(&th->tracer, players[lasttarg].mo); } } else th->cvmem = SKINCOLOR_KETCHUP; /* FALLTHRU */ case MT_JAWZ_DUD: S_StartSound(th, th->info->activesound); /* FALLTHRU */ case MT_SPB: th->movefactor = finalspeed; break; case MT_BUBBLESHIELDTRAP: P_SetScale(th, ((5*th->destscale)>>2)*4); th->destscale = (5*th->destscale)>>2; S_StartSound(th, sfx_s3kbfl); S_StartSound(th, sfx_cdfm35); break; default: break; } if (type != MT_BUBBLESHIELDTRAP) { x = x + P_ReturnThrustX(source, an, source->radius + th->radius); y = y + P_ReturnThrustY(source, an, source->radius + th->radius); throwmo = P_SpawnMobj(x, y, z, MT_FIREDITEM); throwmo->movecount = 1; throwmo->movedir = source->angle - an; P_SetTarget(&throwmo->target, source); } return NULL; } UINT16 K_DriftSparkColor(player_t *player, INT32 charge) { INT32 ds = K_GetKartDriftSparkValue(player); UINT16 color = SKINCOLOR_NONE; if (charge < 0) { // Stage 0: GREY color = SKINCOLOR_GREY; } else if (charge >= ds*4) { // Stage 4: Rainbow if (charge <= (ds*4)+(32*3)) { // transition color = SKINCOLOR_SILVER; } else { color = K_RainbowColor(leveltime); } } else if (cv_kartpurpledrift.value && charge >= ds*3) { // Stage 3: Purple if (charge <= (ds*3)+(32*3)) { // transition color = SKINCOLOR_VIOLET; } else { color = SKINCOLOR_PURPLE; } } else if (charge >= ds*2) { // Stage 2: Red if (charge <= (ds*2)+(32*3)) { // transition color = SKINCOLOR_TANGERINE; } else { color = SKINCOLOR_KETCHUP; } } else if (charge >= ds) { // Stage 1: Blue if (charge <= (ds)+(32*3)) { // transition color = SKINCOLOR_MAGENTA; } else { color = SKINCOLOR_SAPPHIRE; } } return color; } static void K_SpawnDriftSparks(player_t *player) { fixed_t newx; fixed_t newy; mobj_t *spark; angle_t travelangle; INT32 i; I_Assert(player != NULL); I_Assert(player->mo != NULL); I_Assert(!P_MobjWasRemoved(player->mo)); if (leveltime % 2 == 1) return; if (!player->drift || player->driftcharge < K_GetKartDriftSparkValue(player)) return; travelangle = player->mo->angle-(ANGLE_45/5)*player->drift; for (i = 0; i < 2; i++) { newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale)); newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale)); spark = P_SpawnMobj(newx, newy, player->mo->z, MT_DRIFTSPARK); P_SetTarget(&spark->target, player->mo); spark->angle = travelangle-(ANGLE_45/5)*player->drift; spark->destscale = player->mo->scale; P_SetScale(spark, player->mo->scale); spark->momx = player->mo->momx/2; spark->momy = player->mo->momy/2; //spark->momz = player->mo->momz/2; if (player->driftcharge >= K_GetKartDriftSparkValue(player)*4) { spark->color = 1 + (leveltime % (numskincolors-1)); if (player->driftcharge <= K_GetKartDriftSparkValue(player)*4+(32*3)) { // transition P_SetScale(spark, (spark->destscale = spark->scale*3/2)); } } else if (cv_kartpurpledrift.value && player->driftcharge >= K_GetKartDriftSparkValue(player)*3) { if (player->driftcharge <= (K_GetKartDriftSparkValue(player)*3)+(24*3)) spark->color = SKINCOLOR_VIOLET; // transition else spark->color = SKINCOLOR_PURPLE; if (player->driftcharge <= K_GetKartDriftSparkValue(player)*3+(32*3)) { // transition P_SetScale(spark, (spark->destscale = spark->scale*3/2)); } } else if (player->driftcharge >= K_GetKartDriftSparkValue(player)*2) { if (player->driftcharge <= (K_GetKartDriftSparkValue(player)*2)+(24*3)) spark->color = SKINCOLOR_RASPBERRY; // transition else spark->color = SKINCOLOR_KETCHUP; if (player->driftcharge <= K_GetKartDriftSparkValue(player)*2+(32*3)) { // transition P_SetScale(spark, (spark->destscale = spark->scale*3/2)); } } else { spark->color = SKINCOLOR_SAPPHIRE; if (player->driftcharge <= K_GetKartDriftSparkValue(player)+(32*3)) { // transition P_SetScale(spark, (spark->destscale = spark->scale*2)); } } if ((player->drift > 0 && player->cmd.turning > 0) // Inward drifts || (player->drift < 0 && player->cmd.turning < 0)) { if ((player->drift < 0 && (i & 1)) || (player->drift > 0 && !(i & 1))) P_SetMobjState(spark, S_DRIFTSPARK_A1); else if ((player->drift < 0 && !(i & 1)) || (player->drift > 0 && (i & 1))) P_SetMobjState(spark, S_DRIFTSPARK_C1); } else if ((player->drift > 0 && player->cmd.turning < 0) // Outward drifts || (player->drift < 0 && player->cmd.turning > 0)) { if ((player->drift < 0 && (i & 1)) || (player->drift > 0 && !(i & 1))) P_SetMobjState(spark, S_DRIFTSPARK_C1); else if ((player->drift < 0 && !(i & 1)) || (player->drift > 0 && (i & 1))) P_SetMobjState(spark, S_DRIFTSPARK_A1); } K_MatchGenericExtraFlags(spark, player->mo); } } static void K_SpawnAIZDust(player_t *player) { fixed_t newx; fixed_t newy; mobj_t *spark; angle_t travelangle; I_Assert(player != NULL); I_Assert(player->mo != NULL); I_Assert(!P_MobjWasRemoved(player->mo)); if (leveltime % 2 == 1) return; if (!P_IsObjectOnGround(player->mo)) return; if (player->speed <= K_GetKartSpeed(player, false, true)) return; travelangle = K_MomentumAngle(player->mo); //S_StartSound(player->mo, sfx_s3k47); { newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle - (player->aizdriftstrat*ANGLE_45), FixedMul(24*FRACUNIT, player->mo->scale)); newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle - (player->aizdriftstrat*ANGLE_45), FixedMul(24*FRACUNIT, player->mo->scale)); spark = P_SpawnMobj(newx, newy, player->mo->z, MT_AIZDRIFTSTRAT); spark->angle = travelangle+(player->aizdriftstrat*ANGLE_90); P_SetScale(spark, (spark->destscale = (3*player->mo->scale)>>2)); spark->momx = (6*player->mo->momx)/5; spark->momy = (6*player->mo->momy)/5; spark->momz = P_GetMobjZMovement(player->mo); K_MatchGenericExtraFlags(spark, player->mo); } } void K_SpawnBoostTrail(player_t *player) { fixed_t newx, newy, newz; fixed_t ground; mobj_t *flame; angle_t travelangle; INT32 i; I_Assert(player != NULL); I_Assert(player->mo != NULL); I_Assert(!P_MobjWasRemoved(player->mo)); if (!P_IsObjectOnGround(player->mo) || player->hyudorotimer != 0 || ((gametyperules & GTR_BUMPERS) && player->bumper <= 0 && player->karmadelay)) return; if (player->mo->eflags & MFE_VERTICALFLIP) ground = player->mo->ceilingz; else ground = player->mo->floorz; if (player->drift != 0) travelangle = player->mo->angle; else travelangle = K_MomentumAngle(player->mo); for (i = 0; i < 2; i++) { newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(24*FRACUNIT, player->mo->scale)); newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(24*FRACUNIT, player->mo->scale)); newz = P_GetZAt(player->mo->standingslope, newx, newy, ground); if (player->mo->eflags & MFE_VERTICALFLIP) { newz -= FixedMul(mobjinfo[MT_SNEAKERTRAIL].height, player->mo->scale); } flame = P_SpawnMobj(newx, newy, ground, MT_SNEAKERTRAIL); P_SetTarget(&flame->target, player->mo); flame->angle = travelangle; flame->fuse = TICRATE*2; flame->destscale = player->mo->scale; P_SetScale(flame, player->mo->scale); // not K_MatchGenericExtraFlags so that a stolen sneaker can be seen K_FlipFromObject(flame, player->mo); flame->momx = 8; P_XYMovement(flame); if (P_MobjWasRemoved(flame)) continue; if (player->mo->eflags & MFE_VERTICALFLIP) { if (flame->z + flame->height < flame->ceilingz) P_RemoveMobj(flame); } else if (flame->z > flame->floorz) P_RemoveMobj(flame); } } void K_SpawnSparkleTrail(mobj_t *mo) { const INT32 rad = (mo->radius*2)>>FRACBITS; mobj_t *sparkle; INT32 i; I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); for (i = 0; i < 3; i++) { fixed_t newx = mo->x + mo->momx + (P_RandomRange(-rad, rad)<y + mo->momy + (P_RandomRange(-rad, rad)<z + mo->momz + (P_RandomRange(0, mo->height>>FRACBITS)<target, mo); sparkle->destscale = mo->destscale; P_SetScale(sparkle, mo->scale); sparkle->color = mo->color; } P_SetMobjState(sparkle, S_KARTINVULN_LARGE1); } void K_SpawnWipeoutTrail(mobj_t *mo, boolean translucent) { mobj_t *dust; angle_t aoff; I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); if (mo->player) aoff = (mo->player->drawangle + ANGLE_180); else aoff = (mo->angle + ANGLE_180); if ((leveltime / 2) & 1) aoff -= ANGLE_45; else aoff += ANGLE_45; dust = P_SpawnMobj(mo->x + FixedMul(24*mo->scale, FINECOSINE(aoff>>ANGLETOFINESHIFT)) + (P_RandomRange(-8,8) << FRACBITS), mo->y + FixedMul(24*mo->scale, FINESINE(aoff>>ANGLETOFINESHIFT)) + (P_RandomRange(-8,8) << FRACBITS), mo->z, MT_WIPEOUTTRAIL); P_SetTarget(&dust->target, mo); dust->angle = K_MomentumAngle(mo); dust->destscale = mo->scale; P_SetScale(dust, mo->scale); K_FlipFromObject(dust, mo); if (translucent) // offroad effect { dust->momx = mo->momx/2; dust->momy = mo->momy/2; dust->momz = mo->momz/2; } if (translucent) dust->renderflags |= RF_TRANS50; } // K_DriftDustHandling // Parameters: // spawner: The map object that is spawning the drift dust // Description: Spawns the drift dust for objects, players use rmomx/y, other objects use regular momx/y. // Also plays the drift sound. // Other objects should be angled towards where they're trying to go so they don't randomly spawn dust // Do note that most of the function won't run in odd intervals of frames void K_DriftDustHandling(mobj_t *spawner) { angle_t anglediff; const INT16 spawnrange = spawner->radius>>FRACBITS; if (!P_IsObjectOnGround(spawner) || leveltime % 2 != 0) return; if (spawner->player) { if (spawner->player->pflags & PF_SKIDDOWN) { anglediff = abs((signed)(spawner->angle - spawner->player->drawangle)); if (leveltime % 6 == 0) S_StartSound(spawner, sfx_screec); // repeated here because it doesn't always happen to be within the range when this is the case } else { angle_t playerangle = spawner->angle; if (spawner->player->speed < 5<player->cmd.forwardmove < 0) playerangle += ANGLE_180; anglediff = abs((signed)(playerangle - R_PointToAngle2(0, 0, spawner->player->rmomx, spawner->player->rmomy))); } } else { if (P_AproxDistance(spawner->momx, spawner->momy) < 5<angle - R_PointToAngle2(0, 0, spawner->momx, spawner->momy))); } if (anglediff > ANGLE_180) anglediff = InvAngle(anglediff); if (anglediff > ANG10*4) // Trying to turn further than 40 degrees { fixed_t spawnx = P_RandomRange(-spawnrange, spawnrange)<x + spawnx, spawner->y + spawny, spawner->z, MT_DRIFTDUST); dust->momx = FixedMul(spawner->momx + (P_RandomRange(-speedrange, speedrange)<scale)/4); dust->momy = FixedMul(spawner->momy + (P_RandomRange(-speedrange, speedrange)<scale)/4); dust->momz = P_MobjFlip(spawner) * (P_RandomRange(1, 4) * (spawner->scale)); P_SetScale(dust, spawner->scale/2); dust->destscale = spawner->scale * 3; dust->scalespeed = spawner->scale/12; P_SetTarget(&dust->target, spawner); if (leveltime % 6 == 0) S_StartSound(spawner, sfx_screec); K_MatchGenericExtraFlags(dust, spawner); } } static mobj_t *K_FindLastTrailMobj(player_t *player) { mobj_t *trail; if (!player || !(trail = player->mo) || !player->mo->hnext || !player->mo->hnext->health) return NULL; while (trail->hnext && !P_MobjWasRemoved(trail->hnext) && trail->hnext->health) { trail = trail->hnext; } return trail; } mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow) { mobj_t *mo; INT32 dir; fixed_t PROJSPEED; angle_t newangle; fixed_t newx, newy, newz; mobj_t *throwmo; if (!player) return NULL; // Figure out projectile speed by game speed if (missile && mapthing != MT_BALLHOG) // Trying to keep compatability... { PROJSPEED = mobjinfo[mapthing].speed; if (gamespeed == 0) PROJSPEED = FixedMul(PROJSPEED, FRACUNIT-FRACUNIT/4); else if (gamespeed == 2) PROJSPEED = FixedMul(PROJSPEED, FRACUNIT+FRACUNIT/4); PROJSPEED = FixedMul(PROJSPEED, mapobjectscale); } else { PROJSPEED = K_GetProjectileSpeed(); } if (altthrow) { if (altthrow == 2) // Kitchen sink throwing { #if 0 if (player->throwdir == 1) dir = 3; else if (player->throwdir == -1) dir = 1; else dir = 2; #else if (player->throwdir == 1) dir = 2; else dir = 1; #endif } else { if (player->throwdir == 1) dir = 2; else if (player->throwdir == -1) dir = -1; else dir = 1; } } else { if (player->throwdir != 0) dir = player->throwdir; else dir = defaultDir; } if (missile) // Shootables { if (mapthing == MT_BALLHOG) // Messy { mo = NULL; // can't return multiple projectiles if (dir == -1) { // Shoot backward K_SpawnKartMissile(player->mo, mapthing, (player->mo->angle + ANGLE_180) - 0x06000000, 0, PROJSPEED/8); K_SpawnKartMissile(player->mo, mapthing, (player->mo->angle + ANGLE_180) - 0x03000000, 0, PROJSPEED/8); K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/8); K_SpawnKartMissile(player->mo, mapthing, (player->mo->angle + ANGLE_180) + 0x03000000, 0, PROJSPEED/8); K_SpawnKartMissile(player->mo, mapthing, (player->mo->angle + ANGLE_180) + 0x06000000, 0, PROJSPEED/8); } else { // Shoot forward K_SpawnKartMissile(player->mo, mapthing, player->mo->angle - 0x06000000, 0, PROJSPEED); K_SpawnKartMissile(player->mo, mapthing, player->mo->angle - 0x03000000, 0, PROJSPEED); K_SpawnKartMissile(player->mo, mapthing, player->mo->angle, 0, PROJSPEED); K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + 0x03000000, 0, PROJSPEED); K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + 0x06000000, 0, PROJSPEED); } } else { if (dir == -1 && mapthing != MT_SPB) { // Shoot backward mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/8); } else { // Shoot forward mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle, 0, PROJSPEED); } if (mapthing == MT_DROPTARGET && mo) { mo->reactiontime = TICRATE/2; P_SetMobjState(mo, mo->info->painstate); } } } else { player->bananadrag = 0; // RESET timer, for multiple bananas if (dir > 0) { // Shoot forward mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, mapthing); //K_FlipFromObject(mo, player->mo); // These are really weird so let's make it a very specific case to make SURE it works... if (player->mo->eflags & MFE_VERTICALFLIP) { mo->z -= player->mo->height; mo->flags2 |= MF2_OBJECTFLIP; mo->eflags |= MFE_VERTICALFLIP; } mo->threshold = 10; P_SetTarget(&mo->target, player->mo); S_StartSound(player->mo, mo->info->seesound); if (mo) { angle_t fa = player->mo->angle>>ANGLETOFINESHIFT; fixed_t HEIGHT = (20 + (dir*10))*mapobjectscale + (player->mo->momz*P_MobjFlip(player->mo)); mo->momz = HEIGHT*P_MobjFlip(mo); mo->momx = player->mo->momx + FixedMul(FINECOSINE(fa), PROJSPEED*dir); mo->momy = player->mo->momy + FixedMul(FINESINE(fa), PROJSPEED*dir); mo->extravalue2 = dir; if (mo->eflags & MFE_UNDERWATER) mo->momz = (117 * mo->momz) / 200; } // this is the small graphic effect that plops in you when you throw an item: throwmo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FIREDITEM); P_SetTarget(&throwmo->target, player->mo); // Ditto: if (player->mo->eflags & MFE_VERTICALFLIP) { throwmo->z -= player->mo->height; throwmo->flags2 |= MF2_OBJECTFLIP; throwmo->eflags |= MFE_VERTICALFLIP; } throwmo->movecount = 0; // above player } else { mobj_t *lasttrail = K_FindLastTrailMobj(player); if (lasttrail) { newx = lasttrail->x; newy = lasttrail->y; newz = lasttrail->z; } else { // Drop it directly behind you. fixed_t dropradius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(mobjinfo[mapthing].radius, mobjinfo[mapthing].radius); newangle = player->mo->angle; newx = player->mo->x + P_ReturnThrustX(player->mo, newangle + ANGLE_180, dropradius); newy = player->mo->y + P_ReturnThrustY(player->mo, newangle + ANGLE_180, dropradius); newz = player->mo->z; } mo = P_SpawnMobj(newx, newy, newz, mapthing); // this will never return null because collision isn't processed here K_FlipFromObject(mo, player->mo); mo->threshold = 10; P_SetTarget(&mo->target, player->mo); if (P_IsObjectOnGround(player->mo)) { // floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn // This should set it for FOFs P_SetOrigin(mo, mo->x, mo->y, mo->z); // however, THIS can fuck up your day. just absolutely ruin you. if (P_MobjWasRemoved(mo)) return NULL; if (P_MobjFlip(mo) > 0) { if (mo->floorz > mo->target->z - mo->height) { mo->z = mo->floorz; } } else { if (mo->ceilingz < mo->target->z + mo->target->height + mo->height) { mo->z = mo->ceilingz - mo->height; } } } if (player->mo->eflags & MFE_VERTICALFLIP) mo->eflags |= MFE_VERTICALFLIP; if (mapthing == MT_SSMINE) mo->extravalue1 = 49; // Pads the start-up length from 21 frames to a full 2 seconds else if (mapthing == MT_BUBBLESHIELDTRAP) { P_SetScale(mo, ((5*mo->destscale)>>2)*4); mo->destscale = (5*mo->destscale)>>2; S_StartSound(mo, sfx_s3kbfl); } } } return mo; } void K_PuntMine(mobj_t *origMine, mobj_t *punter) { angle_t fa = K_MomentumAngle(punter); fixed_t z = (punter->momz * P_MobjFlip(punter)) + (30 * FRACUNIT); fixed_t spd; mobj_t *mine; if (!origMine || P_MobjWasRemoved(origMine)) return; // This guarantees you hit a mine being dragged if (origMine->type == MT_SSMINE_SHIELD) // Create a new mine, and clean up the old one { mobj_t *mineOwner = origMine->target; mine = P_SpawnMobj(origMine->x, origMine->y, origMine->z, MT_SSMINE); P_SetTarget(&mine->target, mineOwner); mine->angle = origMine->angle; mine->flags2 = origMine->flags2; mine->floorz = origMine->floorz; mine->ceilingz = origMine->ceilingz; P_SetScale(mine, origMine->scale); mine->destscale = origMine->destscale; mine->scalespeed = origMine->scalespeed; // Copy interp data mine->old_angle = origMine->old_angle; mine->old_x = origMine->old_x; mine->old_y = origMine->old_y; mine->old_z = origMine->old_z; // Since we aren't using P_KillMobj, we need to clean up the hnext reference P_SetTarget(&mineOwner->hnext, NULL); K_UnsetItemOut(mineOwner->player); if (mineOwner->player->itemamount) { mineOwner->player->itemamount--; } if (!mineOwner->player->itemamount) { mineOwner->player->itemtype = KITEM_NONE; } P_RemoveMobj(origMine); } else { mine = origMine; } if (!mine || P_MobjWasRemoved(mine)) return; if (mine->threshold > 0) return; spd = K_GetProjectileSpeed(); mine->flags |= MF_NOCLIPTHING; P_SetMobjState(mine, S_SSMINE_AIR1); mine->threshold = 10; mine->reactiontime = mine->info->reactiontime; mine->momx = punter->momx + FixedMul(FINECOSINE(fa >> ANGLETOFINESHIFT), spd); mine->momy = punter->momy + FixedMul(FINESINE(fa >> ANGLETOFINESHIFT), spd); P_SetObjectMomZ(mine, z, false); mine->flags &= ~MF_NOCLIPTHING; } #define THUNDERRADIUS 320 // Rough size of the outer-rim sprites, after scaling. // (The hitbox is already pretty strict due to only 1 active frame, // we don't need to have it disjointedly small too...) #define THUNDERSPRITE 80 static void K_DoThunderShield(player_t *player) { mobj_t *mo; int i = 0; fixed_t sx; fixed_t sy; angle_t an; S_StartSound(player->mo, sfx_zio3); K_ThunderShieldAttack(player->mo, (THUNDERRADIUS + THUNDERSPRITE) * FRACUNIT); // spawn vertical bolt mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); P_SetTarget(&mo->target, player->mo); P_SetMobjState(mo, S_LZIO11); mo->color = SKINCOLOR_TEAL; mo->scale = player->mo->scale*3 + (player->mo->scale/2); mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); P_SetTarget(&mo->target, player->mo); P_SetMobjState(mo, S_LZIO21); mo->color = SKINCOLOR_CYAN; mo->scale = player->mo->scale*3 + (player->mo->scale/2); // spawn horizontal bolts; for (i=0; i<7; i++) { mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); mo->angle = P_RandomRange(0, 359)*ANG1; mo->fuse = P_RandomRange(20, 50); P_SetTarget(&mo->target, player->mo); P_SetMobjState(mo, S_KLIT1); } // spawn the radius thing: an = ANGLE_22h; for (i=0; i<15; i++) { sx = player->mo->x + FixedMul((player->mo->scale*THUNDERRADIUS), FINECOSINE((an*i)>>ANGLETOFINESHIFT)); sy = player->mo->y + FixedMul((player->mo->scale*THUNDERRADIUS), FINESINE((an*i)>>ANGLETOFINESHIFT)); mo = P_SpawnMobj(sx, sy, player->mo->z, MT_THOK); mo->angle = an*i; mo->extravalue1 = THUNDERRADIUS; // Used to know whether we should teleport by radius or something. mo->scale = player->mo->scale*3; P_SetTarget(&mo->target, player->mo); P_SetMobjState(mo, S_KSPARK1); } } #undef THUNDERRADIUS #undef THUNDERSPRITE static void K_FlameDashLeftoverSmoke(mobj_t *src) { UINT8 i; for (i = 0; i < 2; i++) { mobj_t *smoke = P_SpawnMobj(src->x, src->y, src->z+(8<scale); smoke->destscale = 3*src->scale/2; smoke->scalespeed = src->scale/12; smoke->momx = 3*src->momx/4; smoke->momy = 3*src->momy/4; smoke->momz = 3*P_GetMobjZMovement(src)/4; P_Thrust(smoke, src->angle + FixedAngle(P_RandomRange(135, 225)<scale); smoke->momz += P_RandomRange(0, 4) * src->scale; } } static void K_DoHyudoroSteal(player_t *player) { INT32 i, numplayers = 0; INT32 playerswappable[MAXPLAYERS]; INT32 stealplayer = -1; // The player that's getting stolen from INT32 prandom = 0; boolean sink = P_RandomChance(FRACUNIT/64); INT32 hyu = hyudorotime; for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && players[i].mo && players[i].mo->health > 0 && players[i].playerstate == PST_LIVE && player != &players[i] && !players[i].exiting && !players[i].spectator // Player in-game // Can steal from this player && (gametype == GT_RACE //&& players[i].position < player->position) || ((gametyperules & GTR_BUMPERS) && players[i].bumper > 0)) // Has an item && (players[i].itemtype && players[i].itemamount && !(players[i].itemflags & IF_ITEMOUT) && !players[i].itemblink)) { playerswappable[numplayers] = i; numplayers++; } } prandom = P_RandomFixed(); S_StartSound(player->mo, sfx_s3k92); if (sink && numplayers > 0 && cv_kitchensink.value) // BEHOLD THE KITCHEN SINK { player->hyudorotimer = hyu; player->stealingtimer = stealtime; player->itemtype = KITEM_KITCHENSINK; player->itemamount = 1; K_UnsetItemOut(player); return; } else if ((gametype == GT_RACE && player->position == 1) || numplayers == 0) // No-one can be stolen from? Oh well... { player->hyudorotimer = hyu; player->stealingtimer = stealtime; return; } else if (numplayers == 1) // With just 2 players, we just need to set the other player to be the one to steal from { stealplayer = playerswappable[numplayers-1]; } else if (numplayers > 1) // We need to choose between the available candidates for the 2nd player { stealplayer = playerswappable[prandom%(numplayers-1)]; } if (stealplayer > -1) // Now here's where we do the stealing, has to be done here because we still know the player we're stealing from { player->hyudorotimer = hyu; player->stealingtimer = stealtime; players[stealplayer].stolentimer = stealtime; player->itemtype = players[stealplayer].itemtype; player->itemamount = players[stealplayer].itemamount; K_UnsetItemOut(player); players[stealplayer].itemtype = KITEM_NONE; players[stealplayer].itemamount = 0; K_UnsetItemOut(&players[stealplayer]); if (P_IsDisplayPlayer(&players[stealplayer]) && !r_splitscreen) S_StartSound(NULL, sfx_s3k92); } } // Handle these else where to reduce code duplication between panels and sneakers static void K_SneakerPanelStackSound(player_t *player) { const sfxenum_t normalsfx = sfx_cdfm01; const sfxenum_t smallsfx = sfx_cdfm40; sfxenum_t sfx = normalsfx; if ((player->numsneakers > 0) && stackingactive) { // Use a less annoying sound when stacking sneakers. sfx = smallsfx; } S_StopSoundByID(player->mo, normalsfx); S_StopSoundByID(player->mo, smallsfx); S_StartSound(player->mo, sfx); } static void K_SneakerPanelEffect(player_t *player, INT32 type) { if (!player->sneakertimer) { if (type == 2) { if (player->mo->hnext) { mobj_t *cur = player->mo->hnext; while (cur && !P_MobjWasRemoved(cur)) { if (!cur->tracer) { mobj_t *overlay = P_SpawnMobj(cur->x, cur->y, cur->z, MT_BOOSTFLAME); P_SetTarget(&overlay->target, cur); P_SetTarget(&cur->tracer, overlay); P_SetScale(overlay, (overlay->destscale = 3*cur->scale/4)); K_FlipFromObject(overlay, cur); } cur = cur->hnext; } } } else { mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BOOSTFLAME); P_SetTarget(&overlay->target, player->mo); P_SetScale(overlay, (overlay->destscale = player->mo->scale)); K_FlipFromObject(overlay, player->mo); } } } void K_DoSneaker(player_t *player, INT32 type) { const fixed_t intendedboost = K_GetSneakerBoostSpeed(); if (player->floorboost == 0 || player->floorboost == 3) { K_SneakerPanelStackSound(player); K_SpawnDashDustRelease(player); if (intendedboost > player->speedboost) player->karthud[khud_destboostcam] = FixedMul(FRACUNIT, FixedDiv((intendedboost - player->speedboost), intendedboost)); } K_SneakerPanelEffect(player, type); if (type != 0) { K_PlayBoostTaunt(player->mo); } player->sneakertimer = sneakertime; player->realsneakertimer = sneakertime; if (player->sneakertimer && (player->floorboost == 0 || player->floorboost == 3)) { player->numsneakers = CLAMP(player->numsneakers+1, 0, stackingactive ? MAXSNEAKERSTACK : 1); } // set angle for spun out players: player->boostangle = player->mo->angle; } void K_DoWaterRunPanel(player_t *player) { const fixed_t intendedboost = K_GetSneakerBoostSpeed(); if (player->floorboost == 0 || player->floorboost == 3) { K_SneakerPanelStackSound(player); K_SpawnDashDustRelease(player); if (intendedboost > player->speedboost) player->karthud[khud_destboostcam] = FixedMul(FRACUNIT, FixedDiv((intendedboost - player->speedboost), intendedboost)); } K_SneakerPanelEffect(player, 0); player->sneakertimer = TICRATE*2; player->realsneakertimer = TICRATE*2; if (player->sneakertimer && (player->floorboost == 0 || player->floorboost == 3)) { player->numsneakers = CLAMP(player->numsneakers+1, 0, stackingactive ? MAXSNEAKERSTACK : 1); } player->mo->flags2 |= MF2_WATERRUN; // set angle for spun out players: player->boostangle = player->mo->angle; } static void K_DoShrink(player_t *user) { INT32 i; mobj_t *mobj, *next; S_StartSound(user->mo, sfx_kc46); // Sound the BANG! for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator || !players[i].mo) continue; if (&players[i] == user) continue; if (players[i].position < user->position) { //P_FlashPal(&players[i], PAL_NUKE, 10); // Grow should get taken away. if (players[i].growshrinktimer > 0) K_RemoveGrowShrink(&players[i]); else { // Start shrinking! K_DropItems(&players[i]); players[i].growshrinktimer = -(15*TICRATE); if (players[i].mo && !P_MobjWasRemoved(players[i].mo)) { players[i].mo->scalespeed = mapobjectscale/TICRATE; players[i].mo->destscale = FixedMul(mapobjectscale, SHRINK_SCALE); if (K_PlayerShrinkCheat(&players[i]) == true) { players[i].mo->destscale = FixedMul(players[i].mo->destscale, SHRINK_SCALE); } S_StartSound(players[i].mo, sfx_kc59); } } } } // kill everything in the kitem list while we're at it: for (mobj = kitemcap; mobj; mobj = next) { next = mobj->itnext; // 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 if (mobj->type == MT_SPB) spbplace = -1; } } void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound) { const fixed_t vscale = mapobjectscale + (mo->scale - mapobjectscale); if (mo->player && mo->player->spectator) return; if (mo->eflags & MFE_SPRUNG) return; mo->standingslope = NULL; mo->eflags |= MFE_SPRUNG; if (mo->eflags & MFE_VERTICALFLIP) vertispeed *= -1; if (vertispeed == 0) { fixed_t thrust; if (mo->player) { thrust = 3*mo->player->speed/2; if (thrust < 48< 72<player->pogospring != 2) { if (mo->player->sneakertimer) thrust = FixedMul(thrust, 5*FRACUNIT/4); else if (mo->player->invincibilitytimer) thrust = FixedMul(thrust, 9*FRACUNIT/8); } } else { thrust = FixedDiv(3*P_AproxDistance(mo->momx, mo->momy)/2, 5*FRACUNIT/2); if (thrust < 16< 32<momz = P_MobjFlip(mo)*FixedMul(FINESINE(ANGLE_22h>>ANGLETOFINESHIFT), FixedMul(thrust, vscale)); } else mo->momz = FixedMul(vertispeed, vscale); if (mo->eflags & MFE_UNDERWATER) mo->momz = (117 * mo->momz) / 200; if (sound) S_StartSound(mo, (sound == 1 ? sfx_kc2f : sfx_kpogos)); } static void K_ThrowLandMine(player_t *player) { mobj_t *landMine; mobj_t *throwmo; landMine = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_LANDMINE); K_FlipFromObject(landMine, player->mo); landMine->threshold = 10; if (landMine->info->seesound) S_StartSound(player->mo, landMine->info->seesound); P_SetTarget(&landMine->target, player->mo); P_SetScale(landMine, player->mo->scale); landMine->destscale = player->mo->destscale; landMine->angle = player->mo->angle; landMine->momz = (30 * mapobjectscale * P_MobjFlip(player->mo)) + player->mo->momz; landMine->color = player->skincolor; throwmo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FIREDITEM); P_SetTarget(&throwmo->target, player->mo); // Ditto: if (player->mo->eflags & MFE_VERTICALFLIP) { throwmo->z -= player->mo->height; throwmo->eflags |= MFE_VERTICALFLIP; } throwmo->movecount = 0; // above player } void K_DoInvincibility(player_t *player, tic_t time) { if (!player->invincibilitytimer) { mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INVULNFLASH); P_SetTarget(&overlay->target, player->mo); overlay->destscale = player->mo->scale; P_SetScale(overlay, player->mo->scale); } player->invincibilitytimer = time; if (P_IsLocalPlayer(player) == true) { S_ChangeMusicSpecial("kinvnc"); } else //used to be "if (P_IsDisplayPlayer(player) == false)" { S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmi : sfx_kinvnc)); } P_RestoreMusic(player); } void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source) { mobj_t *cachenext; killnext: if (P_MobjWasRemoved(banana)) return; cachenext = banana->hnext; if (banana->health) { if (banana->eflags & MFE_VERTICALFLIP) banana->z -= banana->height; else banana->z += banana->height; S_StartSound(banana, banana->info->deathsound); P_KillMobj(banana, inflictor, source, DMG_NORMAL); P_SetObjectMomZ(banana, 8*FRACUNIT, false); if (inflictor) P_InstaThrust(banana, R_PointToAngle2(inflictor->x, inflictor->y, banana->x, banana->y)+ANGLE_90, 16*FRACUNIT); } if ((banana = cachenext)) goto killnext; } // Just for firing/dropping items. void K_UpdateHnextList(player_t *player, boolean clean) { mobj_t *work = player->mo, *nextwork; if (!work) return; nextwork = work->hnext; while ((work = nextwork) && !(work == NULL || P_MobjWasRemoved(work))) { nextwork = work->hnext; if (!clean && (!work->movedir || work->movedir <= (UINT16)player->itemamount)) { continue; } P_RemoveMobj(work); } if (player->mo->hnext == NULL || P_MobjWasRemoved(player->mo->hnext)) { // Like below, try to clean up the pointer if it's NULL. // Maybe this was a cause of the shrink/eggbox fails? P_SetTarget(&player->mo->hnext, NULL); } } // For getting hit! void K_DropHnextList(player_t *player, boolean keepshields) { mobj_t *work = player->mo, *nextwork, *dropwork; INT32 flip; mobjtype_t type; boolean orbit, ponground, dropall = true; INT32 shield = K_GetShieldFromPlayer(player); if (work == NULL || P_MobjWasRemoved(work)) { return; } flip = P_MobjFlip(player->mo); ponground = P_IsObjectOnGround(player->mo); if (shield != KSHIELD_NONE && !keepshields) { switch (shield) { case KSHIELD_THUNDER: K_DoThunderShield(player); break; case KSHIELD_BUBBLE: S_StartSound(player->mo, sfx_s3k4b); break; case KSHIELD_FLAME: S_StartSound(player->mo, sfx_s3k47); break; } player->curshield = KSHIELD_NONE; player->itemtype = KITEM_NONE; player->itemamount = 0; K_UnsetItemOut(player); } nextwork = work->hnext; while ((work = nextwork) && !(work == NULL || P_MobjWasRemoved(work))) { nextwork = work->hnext; if (!work->health) continue; // taking care of itself switch (work->type) { // Kart orbit items case MT_ORBINAUT_SHIELD: orbit = true; type = MT_ORBINAUT; break; case MT_JAWZ_SHIELD: orbit = true; type = MT_JAWZ_DUD; break; // Kart trailing items case MT_BANANA_SHIELD: orbit = false; type = MT_BANANA; break; case MT_SSMINE_SHIELD: orbit = false; dropall = false; type = MT_SSMINE; break; case MT_DROPTARGET_SHIELD: orbit = false; dropall = false; type = MT_DROPTARGET; break; case MT_EGGMANITEM_SHIELD: orbit = false; type = MT_EGGMANITEM; break; // intentionally do nothing case MT_ROCKETSNEAKER: case MT_SINK_SHIELD: return; default: continue; } dropwork = P_SpawnMobj(work->x, work->y, work->z, type); P_SetTarget(&dropwork->target, player->mo); P_AddKartItem(dropwork); // needs to be called here so shrink can bust items off players in front of the user. dropwork->angle = work->angle; dropwork->scalespeed = work->scalespeed; dropwork->spritexscale = work->spritexscale; dropwork->spriteyscale = work->spriteyscale; dropwork->flags |= MF_NOCLIPTHING; dropwork->flags2 = work->flags2; dropwork->eflags = work->eflags; dropwork->renderflags = work->renderflags; dropwork->color = work->color; dropwork->colorized = work->colorized; dropwork->whiteshadow = work->whiteshadow; dropwork->floorz = work->floorz; dropwork->ceilingz = work->ceilingz; dropwork->health = work->health; // will never be set to 0 as long as above guard exists // Copy interp data dropwork->old_angle = work->old_angle; dropwork->old_x = work->old_x; dropwork->old_y = work->old_y; dropwork->old_z = work->old_z; if (ponground) { // floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn // This should set it for FOFs //P_SetOrigin(dropwork, dropwork->x, dropwork->y, dropwork->z); -- handled better by above floorz/ceilingz passing if (flip == 1) { if (dropwork->floorz > dropwork->target->z - dropwork->height) { dropwork->z = dropwork->floorz; } } else { if (dropwork->ceilingz < dropwork->target->z + dropwork->target->height + dropwork->height) { dropwork->z = dropwork->ceilingz - dropwork->height; } } } if (orbit) // splay out { dropwork->flags2 |= MF2_AMBUSH; dropwork->z += flip; dropwork->momx = player->mo->momx>>1; dropwork->momy = player->mo->momy>>1; dropwork->momz = 3*flip*mapobjectscale; if (dropwork->eflags & MFE_UNDERWATER) dropwork->momz = (117 * dropwork->momz) / 200; P_Thrust(dropwork, work->angle - ANGLE_90, 6*mapobjectscale); dropwork->movecount = 2; dropwork->movedir = work->angle - ANGLE_90; P_SetMobjState(dropwork, dropwork->info->deathstate); dropwork->tics = -1; if (type == MT_JAWZ_DUD) { dropwork->z += 20*flip*dropwork->scale; } else { dropwork->angle -= ANGLE_90; } } else // plop on the ground { dropwork->threshold = 10; dropwork->flags &= ~MF_NOCLIPTHING; } P_RemoveMobj(work); } // we need this here too because this is done in afterthink - pointers are cleaned up at the START of each tic... P_SetTarget(&player->mo->hnext, NULL); player->bananadrag = 0; if (player->itemflags & IF_EGGMANOUT) { player->itemflags &= ~IF_EGGMANOUT; } else if ((player->itemflags & IF_ITEMOUT) && (dropall || (--player->itemamount <= 0))) { player->itemamount = 0; K_UnsetItemOut(player); player->itemtype = KITEM_NONE; } } mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount) { mobj_t *drop = P_SpawnMobj(x, y, z, MT_FLOATINGITEM); P_SetScale(drop, drop->scale>>4); drop->destscale = (3*drop->destscale)/2; drop->angle = angle; P_Thrust(drop, FixedAngle(P_RandomFixed() * 180) + angle, 16*mapobjectscale); drop->momz = flip * 3 * mapobjectscale; if (drop->eflags & MFE_UNDERWATER) drop->momz = (117 * drop->momz) / 200; if (type == 0) { UINT8 useodds = 0; INT32 spawnchance[NUMKARTRESULTS]; INT32 totalspawnchance = 0; INT32 i; memset(spawnchance, 0, sizeof (spawnchance)); useodds = amount; for (i = 1; i < NUMKARTRESULTS; i++) { spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( useodds, i, UINT32_MAX, 0, false, false, false ) ); } if (totalspawnchance > 0) { UINT8 newType; UINT8 newAmount; totalspawnchance = P_RandomKey(totalspawnchance); for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); // TODO: this is bad! // 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; default: newType = i; newAmount = 1; break; } if (newAmount > 1) { UINT8 j; for (j = 0; j < newAmount-1; j++) { K_CreatePaperItem( x, y, z, angle, flip, newType, 1 ); } } drop->threshold = newType; drop->movecount = 1; } else { drop->threshold = 1; drop->movecount = 1; } } else { drop->threshold = type; drop->movecount = amount; } drop->flags |= MF_NOCLIPTHING; return drop; } // For getting EXTRA hit! void K_DropItems(player_t *player) { K_DropHnextList(player, true); if (player->mo && !P_MobjWasRemoved(player->mo) && player->itemamount > 0) { mobj_t *drop = K_CreatePaperItem( player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, player->mo->angle + ANGLE_90, P_MobjFlip(player->mo), player->itemtype, player->itemamount ); K_FlipFromObject(drop, player->mo); } K_StripItems(player); } void K_DropRocketSneaker(player_t *player) { mobj_t *shoe = player->mo; fixed_t flingangle; boolean leftshoe = true; //left shoe is first if (!(player->mo && !P_MobjWasRemoved(player->mo) && player->mo->hnext && !P_MobjWasRemoved(player->mo->hnext))) return; while ((shoe = shoe->hnext) && !P_MobjWasRemoved(shoe)) { if (shoe->type != MT_ROCKETSNEAKER) return; //woah, not a rocketsneaker, bail! safeguard in case this gets used when you're holding non-rocketsneakers shoe->renderflags &= ~RF_DONTDRAW; shoe->flags &= ~MF_NOGRAVITY; shoe->angle += ANGLE_45; if (shoe->eflags & MFE_VERTICALFLIP) shoe->z -= shoe->height; else shoe->z += shoe->height; //left shoe goes off tot eh left, right shoe off to the right if (leftshoe) flingangle = -(ANG60); else flingangle = ANG60; S_StartSound(shoe, shoe->info->deathsound); P_SetObjectMomZ(shoe, 8*FRACUNIT, false); P_InstaThrust(shoe, R_PointToAngle2(shoe->target->x, shoe->target->y, shoe->x, shoe->y)+flingangle, 16*FRACUNIT); shoe->momx += shoe->target->momx; shoe->momy += shoe->target->momy; shoe->momz += shoe->target->momz; shoe->extravalue2 = 1; leftshoe = false; } P_SetTarget(&player->mo->hnext, NULL); player->rocketsneakertimer = 0; } void K_DropKitchenSink(player_t *player) { if (!(player->mo && !P_MobjWasRemoved(player->mo) && player->mo->hnext && !P_MobjWasRemoved(player->mo->hnext))) return; if (player->mo->hnext->type != MT_SINK_SHIELD) return; //so we can just call this function regardless of what is being held P_KillMobj(player->mo->hnext, NULL, NULL, DMG_NORMAL); P_SetTarget(&player->mo->hnext, NULL); } // When an item in the hnext chain dies. void K_RepairOrbitChain(mobj_t *orbit) { mobj_t *cachenext = orbit->hnext; // First, repair the chain if (orbit->hnext && orbit->hnext->health && !P_MobjWasRemoved(orbit->hnext)) { P_SetTarget(&orbit->hnext->hprev, orbit->hprev); P_SetTarget(&orbit->hnext, NULL); } if (orbit->hprev && orbit->hprev->health && !P_MobjWasRemoved(orbit->hprev)) { P_SetTarget(&orbit->hprev->hnext, cachenext); P_SetTarget(&orbit->hprev, NULL); } // Then recount to make sure item amount is correct if (orbit->target && orbit->target->player && !P_MobjWasRemoved(orbit->target)) { INT32 num = 0; mobj_t *cur = orbit->target->hnext; mobj_t *prev = NULL; while (cur && !P_MobjWasRemoved(cur)) { prev = cur; cur = cur->hnext; if (++num > orbit->target->player->itemamount) P_RemoveMobj(prev); else prev->movedir = num; } if (orbit->target && !P_MobjWasRemoved(orbit->target) && orbit->target->player->itemamount != num) orbit->target->player->itemamount = num; } } // Simplified version of a code bit in P_MobjFloorZ static fixed_t K_BananaSlopeZ(pslope_t *slope, fixed_t x, fixed_t y, fixed_t z, fixed_t radius, boolean ceiling) { fixed_t testx, testy; if (slope == NULL) { testx = x; testy = y; } else { if (slope->d.x < 0) testx = radius; else testx = -radius; if (slope->d.y < 0) testy = radius; else testy = -radius; if ((slope->zdelta > 0) ^ !!(ceiling)) { testx = -testx; testy = -testy; } testx += x; testy += y; } return P_GetZAt(slope, testx, testy, z); } void K_CalculateBananaSlope(mobj_t *mobj, fixed_t x, fixed_t y, fixed_t z, fixed_t radius, fixed_t height, boolean flip, boolean player) { fixed_t newz; sector_t *sec; pslope_t *slope = NULL; sec = R_PointInSubsector(x, y)->sector; if (flip) { slope = sec->c_slope; newz = K_BananaSlopeZ(slope, x, y, sec->ceilingheight, radius, true); } else { slope = sec->f_slope; newz = K_BananaSlopeZ(slope, x, y, sec->floorheight, radius, true); } // Check FOFs for a better suited slope if (sec->ffloors) { ffloor_t *rover; for (rover = sec->ffloors; rover; rover = rover->next) { fixed_t top, bottom; fixed_t d1, d2; if (!(rover->fofflags & FOF_EXISTS)) continue; if ((!(((rover->fofflags & FOF_BLOCKPLAYER && player) || (rover->fofflags & FOF_BLOCKOTHERS && !player)) || (rover->fofflags & FOF_QUICKSAND)) || (rover->fofflags & FOF_SWIMMABLE))) continue; top = K_BananaSlopeZ(*rover->t_slope, x, y, *rover->topheight, radius, false); bottom = K_BananaSlopeZ(*rover->b_slope, x, y, *rover->bottomheight, radius, true); if (flip) { if (rover->fofflags & FOF_QUICKSAND) { if (z < top && (z + height) > bottom) { if (newz > (z + height)) { newz = (z + height); slope = NULL; } } continue; } d1 = (z + height) - (top + ((bottom - top)/2)); d2 = z - (top + ((bottom - top)/2)); if (bottom < newz && abs(d1) < abs(d2)) { newz = bottom; slope = *rover->b_slope; } } else { if (rover->fofflags & FOF_QUICKSAND) { if (z < top && (z + height) > bottom) { if (newz < z) { newz = z; slope = NULL; } } continue; } d1 = z - (bottom + ((top - bottom)/2)); d2 = (z + height) - (bottom + ((top - bottom)/2)); if (top > newz && abs(d1) < abs(d2)) { newz = top; slope = *rover->t_slope; } } } } //mobj->standingslope = slope; P_SetPitchRollFromSlope(mobj, slope); } // Move the hnext chain! static void K_MoveHeldObjects(player_t *player) { TryMoveResult_t result = {0}; if (!player->mo) return; if (!player->mo->hnext) { player->bananadrag = 0; if (player->itemflags & IF_EGGMANOUT) player->itemflags &= ~IF_EGGMANOUT; else if (player->itemflags & IF_ITEMOUT) { player->itemamount = 0; K_UnsetItemOut(player); player->itemtype = KITEM_NONE; } return; } if (P_MobjWasRemoved(player->mo->hnext)) { // we need this here too because this is done in afterthink - pointers are cleaned up at the START of each tic... P_SetTarget(&player->mo->hnext, NULL); player->bananadrag = 0; if (player->itemflags & IF_EGGMANOUT) player->itemflags &= ~IF_EGGMANOUT; else if (player->itemflags & IF_EGGMANOUT) { player->itemamount = 0; K_UnsetItemOut(player); player->itemtype = KITEM_NONE; } return; } switch (player->mo->hnext->type) { case MT_ORBINAUT_SHIELD: // Kart orbit items case MT_JAWZ_SHIELD: { mobj_t *cur = player->mo->hnext; fixed_t speed = ((8 - min(4, player->itemamount)) * cur->info->speed) / 7; player->bananadrag = 0; // Just to make sure while (cur && !P_MobjWasRemoved(cur)) { const fixed_t radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(cur->radius, cur->radius); // mobj's distance from its Target, or Radius. fixed_t z; if (!cur->health) { cur = cur->hnext; continue; } cur->color = player->skincolor; cur->angle -= ANGLE_90; cur->angle += FixedAngle(speed); if (cur->extravalue1 < radius) cur->extravalue1 += P_AproxDistance(cur->extravalue1, radius) / 12; if (cur->extravalue1 > radius) cur->extravalue1 = radius; // If the player is on the ceiling, then flip your items as well. if (player && player->mo->eflags & MFE_VERTICALFLIP) cur->eflags |= MFE_VERTICALFLIP; else cur->eflags &= ~MFE_VERTICALFLIP; // Shrink your items if the player shrunk too. P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale))); if (P_MobjFlip(cur) > 0) z = player->mo->z; else z = player->mo->z + player->mo->height - cur->height; cur->flags |= MF_NOCLIPTHING; // temporarily make them noclip other objects so they can't hit anyone while in the player P_MoveOrigin(cur, player->mo->x, player->mo->y, z); cur->momx = FixedMul(FINECOSINE(cur->angle>>ANGLETOFINESHIFT), cur->extravalue1); cur->momy = FixedMul(FINESINE(cur->angle>>ANGLETOFINESHIFT), cur->extravalue1); cur->flags &= ~MF_NOCLIPTHING; if (!P_TryMove(cur, player->mo->x + cur->momx, player->mo->y + cur->momy, true, &result)) P_SlideMove(cur,&result); if (P_IsObjectOnGround(player->mo)) { if (P_MobjFlip(cur) > 0) { if (cur->floorz > player->mo->z - cur->height) z = cur->floorz; } else { if (cur->ceilingz < player->mo->z + player->mo->height + cur->height) z = cur->ceilingz - cur->height; } } // Center it during the scale up animation z += (FixedMul(mobjinfo[cur->type].height, player->mo->scale - cur->scale)>>1) * P_MobjFlip(cur); cur->z = z; cur->momx = cur->momy = 0; cur->angle += ANGLE_90; cur = cur->hnext; } } break; case MT_BANANA_SHIELD: // Kart trailing items case MT_SSMINE_SHIELD: case MT_DROPTARGET_SHIELD: case MT_EGGMANITEM_SHIELD: case MT_SINK_SHIELD: { mobj_t *cur = player->mo->hnext; mobj_t *curnext; mobj_t *targ = player->mo; if (P_IsObjectOnGround(player->mo) && player->speed > 0) player->bananadrag++; while (cur && !P_MobjWasRemoved(cur)) { const fixed_t radius = FixedHypot(targ->radius, targ->radius) + FixedHypot(cur->radius, cur->radius); angle_t ang; fixed_t targx, targy, targz; fixed_t speed, dist; curnext = cur->hnext; if (cur->type == MT_EGGMANITEM_SHIELD) { // Decided that this should use their "canon" color. cur->color = SKINCOLOR_BLACK; } else if (cur->type == MT_DROPTARGET_SHIELD) { cur->renderflags = (cur->renderflags|RF_FULLBRIGHT) ^ RF_FULLDARK; // the difference between semi and fullbright } cur->flags &= ~MF_NOCLIPTHING; if ((player->mo->eflags & MFE_VERTICALFLIP) != (cur->eflags & MFE_VERTICALFLIP)) K_FlipFromObject(cur, player->mo); if (!cur->health) { cur = curnext; continue; } if (cur->extravalue1 < radius) cur->extravalue1 += FixedMul(P_AproxDistance(cur->extravalue1, radius), FRACUNIT/12); if (cur->extravalue1 > radius) cur->extravalue1 = radius; if (cur != player->mo->hnext) { targ = cur->hprev; dist = cur->extravalue1/4; } else dist = cur->extravalue1/2; if (!targ || P_MobjWasRemoved(targ)) { cur = curnext; continue; } // Shrink your items if the player shrunk too. P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale))); ang = targ->angle; targx = targ->x + P_ReturnThrustX(cur, ang + ANGLE_180, dist); targy = targ->y + P_ReturnThrustY(cur, ang + ANGLE_180, dist); targz = targ->z; speed = FixedMul(R_PointToDist2(cur->x, cur->y, targx, targy), 3*FRACUNIT/4); if (P_IsObjectOnGround(targ)) targz = cur->floorz; cur->angle = R_PointToAngle2(cur->x, cur->y, targx, targy); /* if (P_IsObjectOnGround(player->mo) && player->speed > 0 && player->bananadrag > TICRATE && P_RandomChance(min(FRACUNIT/2, FixedDiv(player->speed, K_GetKartSpeed(player, false, false))/2))) { if (leveltime & 1) targz += 8*(2*FRACUNIT)/7; else targz -= 8*(2*FRACUNIT)/7; } */ if (speed > dist) P_InstaThrust(cur, cur->angle, speed-dist); P_SetObjectMomZ(cur, FixedMul(targz - cur->z, 7*FRACUNIT/8) - gravity, false); if (R_PointToDist2(cur->x, cur->y, targx, targy) > 768*FRACUNIT) { P_MoveOrigin(cur, targx, targy, cur->z); if (P_MobjWasRemoved(cur)) { cur = curnext; continue; } } if (P_IsObjectOnGround(cur)) { K_CalculateBananaSlope(cur, cur->x, cur->y, cur->z, cur->radius, cur->height, (cur->eflags & MFE_VERTICALFLIP), false); } cur = curnext; } } break; case MT_ROCKETSNEAKER: // Special rocket sneaker stuff { mobj_t *cur = player->mo->hnext; INT32 num = 0; while (cur && !P_MobjWasRemoved(cur)) { const fixed_t radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(cur->radius, cur->radius); boolean vibrate = ((leveltime & 1) && !cur->tracer); angle_t angoffset; fixed_t targx, targy, targz; cur->flags &= ~MF_NOCLIPTHING; if (player->rocketsneakertimer <= TICRATE && (leveltime & 1)) cur->renderflags |= RF_DONTDRAW; else cur->renderflags &= ~RF_DONTDRAW; if (num & 1) P_SetMobjStateNF(cur, (vibrate ? S_ROCKETSNEAKER_LVIBRATE : S_ROCKETSNEAKER_L)); else P_SetMobjStateNF(cur, (vibrate ? S_ROCKETSNEAKER_RVIBRATE : S_ROCKETSNEAKER_R)); if (!player->rocketsneakertimer || cur->extravalue2 || !cur->health) { num = (num+1) % 2; cur = cur->hnext; continue; } if (cur->extravalue1 < radius) cur->extravalue1 += FixedMul(P_AproxDistance(cur->extravalue1, radius), FRACUNIT/12); if (cur->extravalue1 > radius) cur->extravalue1 = radius; // Shrink your items if the player shrunk too. P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale))); #if 1 { angle_t input = player->drawangle - cur->angle; boolean invert = (input > ANGLE_180); if (invert) input = InvAngle(input); input = FixedAngle(AngleFixed(input)/4); if (invert) input = InvAngle(input); cur->angle = cur->angle + input; } #else cur->angle = player->drawangle; #endif angoffset = ANGLE_90 + (ANGLE_180 * num); targx = player->mo->x + P_ReturnThrustX(cur, cur->angle + angoffset, cur->extravalue1); targy = player->mo->y + P_ReturnThrustY(cur, cur->angle + angoffset, cur->extravalue1); { // bobbing, copy pasted from my kimokawaiii entry fixed_t sine = FixedMul(player->mo->scale, 8 * FINESINE((((M_TAU_FIXED * (4*TICRATE)) * leveltime) >> ANGLETOFINESHIFT) & FINEMASK)); targz = (player->mo->z + (player->mo->height/2)) + sine; if (player->mo->eflags & MFE_VERTICALFLIP) targz += (player->mo->height/2 - 32*player->mo->scale)*6; } if (cur->tracer && !P_MobjWasRemoved(cur->tracer)) { fixed_t diffx, diffy, diffz; diffx = targx - cur->x; diffy = targy - cur->y; diffz = targz - cur->z; P_MoveOrigin(cur->tracer, cur->tracer->x + diffx + P_ReturnThrustX(cur, cur->angle + angoffset, 6*cur->scale), cur->tracer->y + diffy + P_ReturnThrustY(cur, cur->angle + angoffset, 6*cur->scale), cur->tracer->z + diffz); P_SetScale(cur->tracer, (cur->tracer->destscale = 3*cur->scale/4)); } P_MoveOrigin(cur, targx, targy, targz); K_FlipFromObject(cur, player->mo); // Update graviflip in real time thanks. cur->roll = player->mo->roll; cur->pitch = player->mo->pitch; num = (num+1) % 2; cur = cur->hnext; } } break; default: break; } } player_t *K_FindJawzTarget(mobj_t *actor, player_t *source) { fixed_t best = -1; player_t *wtarg = NULL; INT32 i; for (i = 0; i < MAXPLAYERS; i++) { angle_t thisang; player_t *player; if (!playeringame[i]) continue; player = &players[i]; if (player->spectator) continue; // spectator if (!player->mo) continue; if (player->mo->health <= 0) continue; // dead // Don't target yourself, stupid. if (player == source) continue; // Don't home in on teammates. if (G_GametypeHasTeams() && source->ctfteam == player->ctfteam) continue; // Invisible, don't bother if (player->hyudorotimer) continue; // Find the angle, see who's got the best. thisang = actor->angle - R_PointToAngle2(actor->x, actor->y, player->mo->x, player->mo->y); if (thisang > ANGLE_180) thisang = InvAngle(thisang); // Jawz only go after the person directly ahead of you in race... sort of literally now! if (gametyperules & GTR_CIRCUIT) { // Don't go for people who are behind you if (thisang > ANGLE_67h) continue; // Don't pay attention to people who aren't above your position if (player->position >= source->position) continue; if ((best == -1) || (player->position > best)) { wtarg = player; best = player->position; } } else { fixed_t thisdist; fixed_t thisavg; // Don't go for people who are behind you if (thisang > ANGLE_45) continue; // Don't pay attention to dead players if (player->bumper <= 0) continue; // Z pos too high/low if (abs(player->mo->z - (actor->z + actor->momz)) > RING_DIST/8) continue; thisdist = P_AproxDistance(player->mo->x - (actor->x + actor->momx), player->mo->y - (actor->y + actor->momy)); if (thisdist > 2*RING_DIST) // Don't go for people who are too far away continue; thisavg = (AngleFixed(thisang) + thisdist) / 2; //CONS_Printf("got avg %d from player # %d\n", thisavg>>FRACBITS, i); if ((best == -1) || (thisavg < best)) { wtarg = player; best = thisavg; } } } return wtarg; } // Engine Sounds. static void K_UpdateEngineSounds(player_t *player) { const INT32 numsnds = 13; const fixed_t closedist = 160*FRACUNIT; const fixed_t fardist = 1536*FRACUNIT; const UINT8 dampenval = 48; // 255 * 48 = close enough to FRACUNIT/6 const UINT16 buttons = K_GetKartButtons(player); INT32 class, s, w; // engine class number UINT8 volume = 255; fixed_t volumedampen = FRACUNIT; INT32 targetsnd = 0; INT32 i; s = (player->kartspeed - 1) / 3; w = (player->kartweight - 1) / 3; #define LOCKSTAT(stat) \ if (stat < 0) { stat = 0; } \ if (stat > 2) { stat = 2; } LOCKSTAT(s); LOCKSTAT(w); #undef LOCKSTAT class = s + (3*w); if (leveltime < 8 || player->spectator) { // Silence the engines, and reset sound number while we're at it. player->karthud[khud_enginesnd] = 0; return; } #if 0 if ((leveltime % 8) != ((player-players) % 8)) // Per-player offset, to make engines sound distinct! #else if (leveltime % 8) #endif { // .25 seconds of wait time between each engine sound playback return; } if ((leveltime >= starttime-(2*TICRATE) && leveltime <= starttime) || player->respawn) // Startup boost and dropdashing { // Startup boosts only want to check for BT_ACCELERATE being pressed. targetsnd = ((buttons & BT_ACCELERATE) ? 12 : 0); } else { // Average out the value of forwardmove and the speed that you're moving at. targetsnd = (((6 * player->cmd.forwardmove) / 25) + ((player->speed / mapobjectscale) / 5)) / 2; } if (targetsnd < 0) { targetsnd = 0; } if (targetsnd > 12) { targetsnd = 12; } if (player->karthud[khud_enginesnd] < targetsnd) { player->karthud[khud_enginesnd]++; } if (player->karthud[khud_enginesnd] > targetsnd) { player->karthud[khud_enginesnd]--; } if (player->karthud[khud_enginesnd] < 0) { player->karthud[khud_enginesnd] = 0; } if (player->karthud[khud_enginesnd] > 12) { player->karthud[khud_enginesnd] = 12; } // This code calculates how many players (and thus, how many engine sounds) are within ear shot, // and rebalances the volume of your engine sound based on how far away they are. // This results in multiple things: // - When on your own, you will hear your own engine sound extremely clearly. // - When you were alone but someone is gaining on you, yours will go quiet, and you can hear theirs more clearly. // - When around tons of people, engine sounds will try to rebalance to not be as obnoxious. for (i = 0; i < MAXPLAYERS; i++) { UINT8 thisvol = 0; fixed_t dist; if (!playeringame[i] || !players[i].mo) { // This player doesn't exist. continue; } if (players[i].spectator) { // This player isn't playing an engine sound. continue; } if (P_IsDisplayPlayer(&players[i])) { // Don't dampen yourself! continue; } dist = P_AproxDistance( P_AproxDistance( player->mo->x - players[i].mo->x, player->mo->y - players[i].mo->y), player->mo->z - players[i].mo->z) / 2; dist = FixedDiv(dist, mapobjectscale); if (dist > fardist) { // ENEMY OUT OF RANGE ! continue; } else if (dist < closedist) { // engine sounds' approx. range thisvol = 255; } else { thisvol = (15 * ((closedist - dist) / FRACUNIT)) / ((fardist - closedist) >> (FRACBITS+4)); } volumedampen += (thisvol * dampenval); } if (volumedampen > FRACUNIT) { volume = FixedDiv(volume * FRACUNIT, volumedampen) / FRACUNIT; } if (volume <= 0) { // Don't need to play the sound at all. return; } S_StartSoundAtVolume(player->mo, (sfx_krta00 + player->karthud[khud_enginesnd]) + (class * numsnds), volume); } static void K_UpdateInvincibilitySounds(player_t *player) { INT32 sfxnum = sfx_None; boolean localplayer = P_IsLocalPlayer(player); if (player->mo->health > 0) { if (player->growshrinktimer > 0 && (!localplayer || cv_growmusic.value == 2)) // Prioritize Grow sfxnum = cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow; else if (player->invincibilitytimer > 0 && (!localplayer || cv_supermusic.value == 2)) sfxnum = cv_kartinvinsfx.value ? sfx_alarmi : sfx_kinvnc; } if (sfxnum != sfx_None && !S_SoundPlaying(player->mo, sfxnum)) S_StartSound(player->mo, sfxnum); #define STOPTHIS(this) \ if (sfxnum != this && S_SoundPlaying(player->mo, this)) \ S_StopSoundByID(player->mo, this); STOPTHIS(sfx_alarmi); STOPTHIS(sfx_alarmg); STOPTHIS(sfx_kinvnc); STOPTHIS(sfx_kgrow); #undef STOPTHIS } void K_KartPlayerHUDUpdate(player_t *player) { if (player->karthud[khud_lapanimation]) player->karthud[khud_lapanimation]--; if (player->karthud[khud_yougotem]) player->karthud[khud_yougotem]--; if (player->karthud[khud_voices]) player->karthud[khud_voices]--; if (player->karthud[khud_tauntvoices]) player->karthud[khud_tauntvoices]--; if (gametype == GT_RACE) { if ((K_RingsActive() == true)) { if (player->pflags & PF_RINGLOCK) { player->karthud[khud_ringlock] = true; } } } if ((gametyperules & GTR_BUMPERS) && (player->exiting || player->karmadelay)) { if (player->exiting) { if (player->exiting < 6*TICRATE) player->karthud[khud_cardanimation] += ((164-player->karthud[khud_cardanimation])/8)+1; else if (player->exiting == 6*TICRATE) player->karthud[khud_cardanimation] = 0; else if (player->karthud[khud_cardanimation] < 2*TICRATE) player->karthud[khud_cardanimation]++; } else { if (player->karmadelay < 6*TICRATE) player->karthud[khud_cardanimation] -= ((164-player->karthud[khud_cardanimation])/8)+1; else if (player->karmadelay < 9*TICRATE) player->karthud[khud_cardanimation] += ((164-player->karthud[khud_cardanimation])/8)+1; } if (player->karthud[khud_cardanimation] > 164) player->karthud[khud_cardanimation] = 164; if (player->karthud[khud_cardanimation] < 0) player->karthud[khud_cardanimation] = 0; } else if ((gametyperules & GTR_CIRCUIT) && player->exiting) { if (player->karthud[khud_cardanimation] < 2*TICRATE) player->karthud[khud_cardanimation]++; } else player->karthud[khud_cardanimation] = 0; } #undef RINGANIM_DELAYMAX // SRB2Kart: blockmap iterate for attraction shield users static mobj_t *attractmo; static fixed_t attractdist; static fixed_t attractzdist; static inline BlockItReturn_t PIT_AttractingRings(mobj_t *thing) { if (attractmo == NULL || P_MobjWasRemoved(attractmo) || attractmo->player == NULL) { return BMIT_ABORT; } if (thing == NULL || P_MobjWasRemoved(thing)) { return BMIT_CONTINUE; // invalid } if (thing == attractmo) { return BMIT_CONTINUE; // invalid } if (!(thing->type == MT_RING || thing->type == MT_FLINGRING)) { return BMIT_CONTINUE; // not a ring } if (thing->health <= 0) { return BMIT_CONTINUE; // dead } if (thing->extravalue1) { return BMIT_CONTINUE; // in special ring animation } if (thing->tracer != NULL && P_MobjWasRemoved(thing->tracer) == false) { return BMIT_CONTINUE; // already attracted } // see if it went over / under if (attractmo->z - attractzdist > thing->z + thing->height) { return BMIT_CONTINUE; // overhead } if (attractmo->z + attractmo->height + attractzdist < thing->z) { return BMIT_CONTINUE; // underneath } if (P_AproxDistance(attractmo->x - thing->x, attractmo->y - thing->y) > attractdist + thing->radius) { return BMIT_CONTINUE; // Too far away } if (RINGTOTAL(attractmo->player) >= 20 || (attractmo->player->pflags & PF_RINGLOCK)) { // Already reached max -- just joustle rings around. // Regular ring -> fling ring if (thing->info->reactiontime && thing->type != (mobjtype_t)thing->info->reactiontime) { thing->type = thing->info->reactiontime; thing->info = &mobjinfo[thing->type]; thing->flags = thing->info->flags; P_InstaThrust(thing, P_RandomRange(0,7) * ANGLE_45, 2 * thing->scale); P_SetObjectMomZ(thing, 8<fuse = 120*TICRATE; thing->cusval = 0; // Reset attraction flag } } else { // set target P_SetTarget(&thing->tracer, attractmo); } return BMIT_CONTINUE; // find other rings } /** Looks for rings near a player in the blockmap. * * \param pmo Player object looking for rings to attract * \sa A_AttractChase */ static void K_LookForRings(mobj_t *pmo) { INT32 bx, by, xl, xh, yl, yh; attractmo = pmo; attractdist = (400 * pmo->scale); attractzdist = attractdist >> 2; // Use blockmap to check for nearby rings yh = (unsigned)(pmo->y + (attractdist + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT; yl = (unsigned)(pmo->y - (attractdist + MAXRADIUS) - bmaporgy)>>MAPBLOCKSHIFT; xh = (unsigned)(pmo->x + (attractdist + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT; xl = (unsigned)(pmo->x - (attractdist + MAXRADIUS) - bmaporgx)>>MAPBLOCKSHIFT; BMBOUNDFIX(xl, xh, yl, yh); for (by = yl; by <= yh; by++) for (bx = xl; bx <= xh; bx++) P_BlockThingsIterator(bx, by, PIT_AttractingRings); } static void K_UpdateTripwire(player_t *player) { fixed_t speedThreshold = (3*K_GetKartSpeed(player, false, true))/4; boolean goodSpeed = (player->speed >= speedThreshold); boolean boostExists = (player->tripwireLeniency > 0); // can't be checked later because of subtractions... tripwirepass_t triplevel = K_TripwirePassConditions(player); if (triplevel != TRIPWIRE_NONE) { player->tripwirePass = triplevel; player->tripwireLeniency = max(player->tripwireLeniency, TRIPWIRETIME); } else { if (boostExists) { player->tripwireLeniency--; if (goodSpeed == false && player->tripwireLeniency > 0) { // Decrease at double speed when your speed is bad. player->tripwireLeniency--; } } if (player->tripwireLeniency <= 0) { player->tripwirePass = TRIPWIRE_NONE; } } } static void K_RaceStart(player_t *player) { // Start charging once you're given the opportunity. if (leveltime >= starttime-(2*TICRATE) && leveltime <= starttime) { if (player->cmd.buttons & BT_ACCELERATE) { if (player->boostcharge == 0) player->boostcharge = player->cmd.latency; player->boostcharge++; } else player->boostcharge = 0; } // Increase your size while charging your engine. if (leveltime < starttime+10) { player->mo->scalespeed = mapobjectscale/12; player->mo->destscale = mapobjectscale + (FixedMul(mapobjectscale, player->boostcharge*131)); if (K_PlayerShrinkCheat(player) && !modeattacking && !player->bot) player->mo->destscale = (6*player->mo->destscale)/8; } // Determine the outcome of your charge. if (leveltime > starttime && player->boostcharge) { // Not even trying? if (player->boostcharge < 35) { if (player->boostcharge > 17) S_StartSound(player->mo, sfx_cdfm00); // chosen instead of a conventional skid because it's more engine-like } // Get an instant boost! else if (player->boostcharge <= 50) { player->startboost = (50-player->boostcharge)+20; if (player->boostcharge <= 36) { player->startboost = 0; K_DoSneaker(player, 0); player->sneakertimer = 70; // PERFECT BOOST!! player->realsneakertimer = 70; if (!player->floorboost || player->floorboost == 3) // Let everyone hear this one S_StartSound(player->mo, sfx_s25f); } else { K_SpawnDashDustRelease(player); // already handled for perfect boosts by K_DoSneaker if ((!player->floorboost || player->floorboost == 3) && P_IsLocalPlayer(player)) { if (player->boostcharge <= 40) S_StartSound(player->mo, sfx_cdfm01); // You were almost there! else S_StartSound(player->mo, sfx_s23c); // Nope, better luck next time. } } } // You overcharged your engine? Those things are expensive!!! else if (player->boostcharge > 50) { player->nocontrol = 40; //S_StartSound(player->mo, sfx_kc34); S_StartSound(player->mo, sfx_s3k83); player->pflags |= PF_SKIDDOWN; // cheeky pflag reuse } player->boostcharge = 0; } } UINT8 K_RaceLapCount(INT16 mapNum) { if (!(gametyperules & GTR_CIRCUIT)) { // Not in Race mode return 0; } if (cv_numlaps.value == -1) { // Use map default return mapheaderinfo[mapNum]->numlaps; } return cv_numlaps.value; } static void K_TireGreaseEffect(player_t *player) { const INT16 spawnrange = player->mo->radius>>FRACBITS; fixed_t spawnx = P_RandomRange(-spawnrange, spawnrange)<mo->x + spawnx, player->mo->y + spawny, player->mo->z, MT_DRIFTDUST); dust->momx = FixedMul(player->mo->momx + (P_RandomRange(-speedrange, speedrange)<mo->scale)/4); dust->momy = FixedMul(player->mo->momy + (P_RandomRange(-speedrange, speedrange)<mo->scale)/4); dust->momz = P_MobjFlip(player->mo) * (P_RandomRange(1, 4) * (player->mo->scale)); P_SetScale(dust, player->mo->scale/2); dust->destscale = player->mo->scale * 3; dust->scalespeed = player->mo->scale/12; P_SetTarget(&dust->target, player->mo); if (leveltime % 6 == 0) S_StartSound(player->mo, sfx_screec); } boolean K_BoostChain(player_t *player, INT32 timer, boolean chainsound) { if (!chainingactive) { // You can't chain why bother? return false; } if (timer > player->chaintimer) { // Just what I needed! - Toad if (cv_kartchainingsound.value && chainsound && player->chaintimer && player->sneakertimer) { if (player->mo) { // Chain Sound! S_StartSound(player->mo, sfx_bstchn); } } player->chaintimer = timer; } if (player->chaintimer) { // You can continue chaining! return true; } // Aw shucks, time to drop the chain.... return false; } INT32 K_ChainOrDeincrementTime(player_t *player, INT32 timer, INT32 deincrement, boolean chainsound) { timer -= deincrement; if (K_BoostChain(player, timer, chainsound)) { // Its time to chain. return max(1, timer); } // Continue to drain the timer as normal. return timer; } // Get the tic inverse sum using kartspeed, kartweight and your number of boosts. static INT32 ticinversesum(UINT8 kartspeed, UINT8 kartweight, UINT8 grade) { return (TICRATE / kartspeed) + (TICRATE / CLAMP(kartweight, 1, 5)) + grade; } // Get the maximum required stacks needed for the ringnerf based on kartspeed and kartweight static INT32 statrangemap(UINT8 kartspeed, UINT8 kartweight) { INT32 scaledsw = (9 - kartspeed) + (9 - kartweight); fixed_t scaled_input = (scaledsw)*FRACUNIT/16; // Scale the result to be within range [2, 4] fixed_t result = 4*FRACUNIT - (FixedMul(scaled_input, 2*FRACUNIT)); // Stay within range please! if (result < 2*FRACUNIT) result = 2*FRACUNIT; if (result > 4*FRACUNIT) result = 4*FRACUNIT; result = result >> FRACBITS; return result; } static void K_HandleRingDeincrement(player_t *player, boolean chainnerf) { // Aggressively reduce extreme ringboost duration. // Less aggressive for accel types. UINT8 roller = TICRATE; UINT16 finalringtimer; roller += 4*(8-player->kartspeed); finalringtimer = max((player->ringboost / roller), 1); if (chainnerf) { UINT8 requiredgrade = statrangemap(player->kartspeed, player->kartweight); if (player->numboosts >= requiredgrade) { INT32 insum = ticinversesum(player->kartspeed, player->kartweight, player->numboosts); INT32 subring = (player->ringboost*2)/insum; if (player->kartspeed == 1) { // fuck off chao you aren't chaining an entire race. subring = (player->ringboost*4)/insum; } finalringtimer += subring; } if (player->ringboost - finalringtimer > player->chaintimer) { // Cap this to prevent displacement between chaintimer and ringtimer. player->chaintimer = min(player->ringboost, (TICRATE - TICRATE/4)); } } player->ringboost = max(0, player->ringboost - finalringtimer); } /** \brief Decreases various kart timers and powers per frame. Called in P_PlayerThink in p_user.c \param player player object passed from P_PlayerThink \param cmd control input from player \return void */ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) { /* reset sprite offsets :) */ player->mo->sprxoff = 0; player->mo->spryoff = 0; player->mo->sprzoff = 0; player->mo->spritexoffset = 0; player->mo->spriteyoffset = 0; player->cameraOffset = 0; if (player->loop.radius) { // Offset sprite Z position so wheels touch top of // hitbox when rotated 180 degrees. // TODO: this should be generalized for pitch/roll angle_t pitch = FixedAngle(player->loop.revolution * 360) / 2; player->mo->sprzoff += FixedMul(player->mo->height, FSIN(pitch)); } K_UpdateOffroad(player); K_UpdateEngineSounds(player); // Thanks, VAda! // update boost angle if not spun out if (!player->spinouttimer && !player->wipeoutslow) player->boostangle = player->mo->angle; K_GetKartBoostPower(player); // Special effect objects! if (player->mo && !player->spectator) { if (player->dashpadcooldown) // Twinkle Circuit afterimages { mobj_t *ghost; ghost = P_SpawnGhostMobj(player->mo); ghost->fuse = player->dashpadcooldown+1; ghost->momx = player->mo->momx / (player->dashpadcooldown+1); ghost->momy = player->mo->momy / (player->dashpadcooldown+1); ghost->momz = player->mo->momz / (player->dashpadcooldown+1); player->dashpadcooldown--; } if (player->speed > 0) { // Speed lines if (player->sneakertimer || player->ringboost || player->driftboost || player->startboost) { #if 0 if (player->invincibilitytimer) K_SpawnInvincibilitySpeedLines(player->mo); else #endif K_SpawnNormalSpeedLines(player); } // Could probably be moved somewhere else. K_HandleFootstepParticles(player->mo); if (player->tiregrease) { K_TireGreaseEffect(player); } } // Stacking Effect if (stackingactive && player->numboosts > 1) { K_SpawnStackingEffect(player); } } if (player->itemtype == KITEM_NONE) player->itemflags &= ~IF_HOLDREADY; // DKR style camera for boosting if (player->karthud[khud_boostcam] != 0 || player->karthud[khud_destboostcam] != 0) { if (player->karthud[khud_boostcam] < player->karthud[khud_destboostcam] && player->karthud[khud_destboostcam] != 0) { player->karthud[khud_boostcam] += FRACUNIT/(TICRATE/4); if (player->karthud[khud_boostcam] >= player->karthud[khud_destboostcam]) player->karthud[khud_destboostcam] = 0; } else { player->karthud[khud_boostcam] -= FRACUNIT/TICRATE; if (player->karthud[khud_boostcam] < player->karthud[khud_destboostcam]) player->karthud[khud_boostcam] = player->karthud[khud_destboostcam] = 0; } //CONS_Printf("cam: %d, dest: %d\n", player->karthud[khud_boostcam], player->karthud[khud_destboostcam]); } player->karthud[khud_timeovercam] = 0; // Make ABSOLUTELY SURE that your flashing tics don't get set WHILE you're still in hit animations. if (player->spinouttimer != 0 || player->wipeoutslow != 0 || player->squishedtimer != 0) { player->flashing = K_GetKartFlashing(player); } else if (player->flashing >= K_GetKartFlashing(player)) { player->flashing--; } if (player->spinouttimer) { if ((P_IsObjectOnGround(player->mo) || ( player->spinouttype & KSPIN_AIRTIMER )) && (!player->sneakertimer)) { player->spinouttimer--; if (player->wipeoutslow > 1) player->wipeoutslow--; } } else { if (player->wipeoutslow >= 1) player->mo->friction = ORIG_FRICTION; player->wipeoutslow = 0; } if ((K_RingsActive() == false)) { player->rings = 0; } else { if (player->rings > 20) player->rings = 20; else if (player->rings < -20) player->rings = -20; } if (comeback == false || !(gametyperules & GTR_KARMA) || (player->pflags & PF_ELIMINATED)) { player->karmadelay = comebacktime; } else if (player->karmadelay > 0 && !P_PlayerInPain(player)) { player->karmadelay--; if (P_IsDisplayPlayer(player) && player->bumper <= 0 && player->karmadelay <= 0) comebackshowninfo = true; // client has already seen the message } if (P_IsObjectOnGround(player->mo) && player->pogospring) { if (P_MobjFlip(player->mo)*player->mo->momz <= 0) player->pogospring = 0; } if (player->tripwireReboundDelay) player->tripwireReboundDelay--; if (player->ringdelay) player->ringdelay--; if (P_PlayerInPain(player)) player->ringboost = 0; else if (player->ringboost) { K_HandleRingDeincrement(player, chainingactive); } if (player->sneakertimer) { player->sneakertimer = K_ChainOrDeincrementTime(player, player->sneakertimer, 1, false); if (player->sneakertimer <= 0) { player->mo->flags2 &= ~MF2_WATERRUN; player->numsneakers = 0; } } if (player->realsneakertimer) player->realsneakertimer--; if (player->sneakertimer && player->wipeoutslow > 0 && player->wipeoutslow < wipeoutslowtime+1) player->wipeoutslow = wipeoutslowtime+1; if (player->floorboost > 0) player->floorboost--; if (player->driftboost) player->driftboost = K_ChainOrDeincrementTime(player, player->driftboost, 1, true); if (player->startboost > 0) player->startboost = K_ChainOrDeincrementTime(player, player->startboost, 1, false); if (player->invincibilitytimer) player->invincibilitytimer--; if (player->checkskip) player->checkskip--; if (player->bigwaypointgap && (player->bigwaypointgap > AUTORESPAWN_THRESHOLD || !P_PlayerInPain(player))) { player->bigwaypointgap--; if (!player->bigwaypointgap) P_KillMobj(player->mo, NULL, NULL, DMG_INSTAKILL); else if (player->bigwaypointgap == AUTORESPAWN_THRESHOLD) { S_StartSound(player->mo, sfx_s26d); CONS_Printf("You are going the wrong way! You will automatically respawn in 7 seconds.\n"); } } if (player->growshrinktimer != 0) { if (player->growshrinktimer > 0) player->growshrinktimer--; if (player->growshrinktimer < 0) player->growshrinktimer++; // Back to normal if (player->growshrinktimer == 0) K_RemoveGrowShrink(player); } if (player->superring) { player->nextringaward++; UINT8 ringrate = 3 - min(2, player->superring / 20); // Used to consume fat stacks of cash faster. if (player->nextringaward >= ringrate) { mobj_t *ring = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RING); ring->extravalue1 = 1; // Ring collect animation timer ring->angle = player->mo->angle; // animation angle P_SetTarget(&ring->target, player->mo); // toucher for thinker player->pickuprings++; if (player->superring == 1) ring->cvmem = 1; // play caching when collected player->nextringaward = 0; player->superring--; } } else { player->nextringaward = 99; // Next time we need to award superring, spawn the first one instantly. } // Start at lap 1 when using old checkpoint system just to be safe. if ((K_UsingLegacyCheckpoints()) && (player->laps == 0) && (numlaps > 0)) player->laps = 1; if (player->stealingtimer == 0 && player->stolentimer == 0 && player->rocketsneakertimer) player->rocketsneakertimer--; if (player->hyudorotimer) player->hyudorotimer--; if (player->ringvolume < MINRINGVOLUME) player->ringvolume = MINRINGVOLUME; else if (MAXRINGVOLUME - player->ringvolume < RINGVOLUMEREGEN) player->ringvolume = MAXRINGVOLUME; else player->ringvolume += RINGVOLUMEREGEN; // :D if (player->ringtransparency < MINRINGTRANSPARENCY) player->ringtransparency = MINRINGTRANSPARENCY; else if (MAXRINGTRANSPARENCY - player->ringtransparency < RINGTRANSPARENCYREGEN) player->ringtransparency = MAXRINGTRANSPARENCY; else player->ringtransparency += RINGTRANSPARENCYREGEN; if (player->sadtimer) player->sadtimer--; if (player->stealingtimer) player->stealingtimer--; if (player->stolentimer) player->stolentimer--; if (player->squishedtimer > 0) player->squishedtimer--; if (player->justbumped > 0) player->justbumped--; if (player->outruntime > 0) player->outruntime--; if (player->tiregrease > 0) player->tiregrease--; if (player->chaintimer) player->chaintimer--; if (player->itemblink && player->itemblink-- <= 0) { player->itemblinkmode = 0; player->itemblink = 0; } K_UpdateTripwire(player); K_KartPlayerHUDUpdate(player); if (P_IsObjectOnGround(player->mo)) player->waterskip = 0; if (player->instashield) player->instashield--; if (player->eggmanexplode) { if (player->spectator || (gametype == GT_BATTLE && !player->bumper)) player->eggmanexplode = 0; else { player->eggmanexplode--; if (player->eggmanexplode <= 0) { mobj_t *eggsexplode; K_KartResetPlayerColor(player); //player->flashing = 0; eggsexplode = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SPBEXPLOSION); if (player->eggmanblame >= 0 && player->eggmanblame < MAXPLAYERS && playeringame[player->eggmanblame] && !players[player->eggmanblame].spectator && players[player->eggmanblame].mo) P_SetTarget(&eggsexplode->target, players[player->eggmanblame].mo); } } } if (player->itemtype == KITEM_BUBBLESHIELD) { if (player->bubblecool) player->bubblecool--; } else { player->bubbleblowup = 0; player->bubblecool = 0; player->bubblepop = 0; } if (player->flamedash) player->flamedash--; if (player->flamestore) player->flamestore--; if (player->flametimer > 0) { if (player->stealingtimer == 0 && player->stolentimer == 0) { player->flametimer--; } if (player->flametimer == 0) { S_StartSound(player->mo, sfx_s3k47); K_DropHnextList(player, false); } } else { if (player->flamestore) K_FlameDashLeftoverSmoke(player->mo); } if (cmd->buttons & BT_DRIFT) { player->pflags |= PF_DRIFTINPUT; } else { player->pflags &= ~PF_DRIFTINPUT; } // Respawn Checker if (player->respawn) K_RespawnChecker(player); // Roulette Code K_KartItemRoulette(player, cmd); // Handle invincibility sfx K_UpdateInvincibilitySounds(player); // Also thanks, VAda! if (player->tripwireState != TRIPSTATE_NONE) { if (player->tripwireState == TRIPSTATE_PASSED) S_StartSound(player->mo, sfx_cdfm63); else if (player->tripwireState == TRIPSTATE_BLOCKED) S_StartSound(player->mo, sfx_kc4c); player->tripwireState = TRIPSTATE_NONE; } K_HandleDelayedHitByEm(player); if (!(gametyperules & GTR_FREEROAM)) K_RaceStart(player); // Squishing // If a Grow player or a sector crushes you, get flattened instead of being killed. if (player->squishedtimer <= 0) { player->mo->flags &= ~MF_NOCLIP; } else { player->mo->flags |= MF_NOCLIP; player->mo->momx = 0; player->mo->momy = 0; } if ((player->mo->eflags & MFE_UNDERWATER) && player->curshield != KSHIELD_BUBBLE) { if (player->breathTimer < UINT16_MAX) player->breathTimer++; } } void K_KartResetPlayerColor(player_t *player) { boolean fullbright = false; if (!player->mo || P_MobjWasRemoved(player->mo)) // Can't do anything return; if (player->mo->health <= 0 || player->playerstate == PST_DEAD) // Override everything { player->mo->colorized = (player->dye != 0); player->mo->color = player->dye ? player->dye : player->skincolor; goto finalise; } if (player->eggmanexplode) // You're gonna diiiiie { const INT32 flashtime = 4<<(player->eggmanexplode/TICRATE); if (player->eggmanexplode % (flashtime/2) != 0) { ; } else if (player->eggmanexplode % flashtime == 0) { player->mo->colorized = true; player->mo->color = SKINCOLOR_BLACK; fullbright = true; goto finalise; } else { player->mo->colorized = true; player->mo->color = SKINCOLOR_CRIMSON; fullbright = true; goto finalise; } } if (player->invincibilitytimer) // You're gonna kiiiiill { boolean skip = false; fullbright = true; player->mo->color = K_RainbowColor(leveltime / 2); player->mo->colorized = true; skip = true; if (skip) { goto finalise; } } if (player->growshrinktimer) // Ditto, for grow/shrink { if (player->growshrinktimer % 5 == 0) { player->mo->colorized = true; player->mo->color = (player->growshrinktimer < 0 ? SKINCOLOR_CREAMSICLE : SKINCOLOR_PERIWINKLE); fullbright = true; goto finalise; } } if (player->ringboost && (leveltime & 1)) // ring boosting { player->mo->colorized = true; fullbright = true; goto finalise; } else { player->mo->colorized = (player->dye != 0); player->mo->color = player->dye ? player->dye : player->skincolor; } finalise: if (player->curshield) { fullbright = true; } if (fullbright == true) { player->mo->frame |= FF_FULLBRIGHT; } else { if (!(player->mo->state->frame & FF_FULLBRIGHT)) player->mo->frame &= ~FF_FULLBRIGHT; } } void K_KartPlayerAfterThink(player_t *player) { K_KartResetPlayerColor(player); // Move held objects (Bananas, Orbinaut, etc) K_MoveHeldObjects(player); // Jawz reticule (seeking) if (player->itemtype == KITEM_JAWZ && (player->itemflags & IF_ITEMOUT)) { INT32 lasttarg = player->lastjawztarget; player_t *targ; mobj_t *ret; if (player->jawztargetdelay && playeringame[lasttarg] && !players[lasttarg].spectator) { targ = &players[lasttarg]; player->jawztargetdelay--; } else targ = K_FindJawzTarget(player->mo, player); if (!targ || !targ->mo || P_MobjWasRemoved(targ->mo)) { player->lastjawztarget = -1; player->jawztargetdelay = 0; return; } ret = P_SpawnMobj(targ->mo->x, targ->mo->y, targ->mo->z, MT_PLAYERRETICULE); ret->old_x = targ->mo->old_x; ret->old_y = targ->mo->old_y; ret->old_z = targ->mo->old_z; P_SetTarget(&ret->target, targ->mo); ret->frame |= ((leveltime % 10) / 2); ret->tics = 1; ret->color = player->skincolor; if (targ-players != lasttarg) { if (P_IsDisplayPlayer(player) || P_IsDisplayPlayer(targ)) S_StartSound(NULL, sfx_s3k89); else S_StartSound(targ->mo, sfx_s3k89); player->lastjawztarget = targ-players; player->jawztargetdelay = 5; } } else { player->lastjawztarget = -1; player->jawztargetdelay = 0; } if (player->itemtype == KITEM_THUNDERSHIELD) { K_LookForRings(player->mo); } } static int clostVal(fixed_t arr[], int N, fixed_t K) { // Stores the closest // value to K fixed_t res = arr[0]; // Traverse the array for (int i = 1; i < N; i++) { // If absolute difference // of K and res exceeds // absolute difference of K // and current element if (abs(K - res) > abs(K - arr[i])) { res = arr[i]; } } // Return the closest // array element return res; } // Decides if respawning at this position is safe, used for nu waypoints. boolean K_SafeRespawnPosition(mobj_t * mo) { respawnresult_t result; const boolean flip = (mo->eflags & MFE_VERTICALFLIP) ? true : false; fixed_t mobjx = mo->x; fixed_t mobjy = mo->y; fixed_t mobjz = mo->z; result.cursec = R_PointInSubsector(mobjx, mobjy)->sector; result.bestfofheight = INT32_MAX; result.bestpoheight = INT32_MAX; result.bestrover = NULL; result.bestpo = NULL; if (!result.cursec) // How tf do you do this????? return false; if (flip) result.bestsectorheight = P_GetSectorCeilingZAt(result.cursec,mobjx,mobjy); else result.bestsectorheight = P_GetSectorFloorZAt(result.cursec,mobjx,mobjy); if (result.cursec->ffloors) { ffloor_t *rover; fixed_t curbest = INT32_MAX; for (rover = result.cursec->ffloors; rover; rover = rover->next) { fixed_t compareheight; fixed_t valuecompare[2]; if (!(rover->fofflags & FOF_EXISTS)) continue; if (!(rover->fofflags & FOF_SOLID)) continue; if (!(rover->fofflags & FOF_BLOCKPLAYER) && (rover->fofflags & FOF_BLOCKOTHERS)) continue; if (flip) compareheight = P_GetFFloorBottomZAt(rover,mobjx,mobjy); else compareheight = P_GetFFloorTopZAt(rover,mobjx,mobjy); if (curbest == INT32_MAX) curbest = compareheight; valuecompare[0] = curbest; valuecompare[1] = compareheight; // compareheight is closer then curbest // probably should consider flip { int N = sizeof(valuecompare) / sizeof(valuecompare[0]); fixed_t closetvalue = clostVal(valuecompare, N, mobjz); if (closetvalue == valuecompare[1]) { curbest = compareheight; result.bestrover = rover; } } } result.bestfofheight = curbest; } if (numPolyObjects) { fixed_t curbest = INT32_MAX; INT32 xl, xh, yl, yh, bx, by; yh = (unsigned)(mobjy - bmaporgy)>>MAPBLOCKSHIFT; yl = (unsigned)(mobjy - bmaporgy)>>MAPBLOCKSHIFT; xh = (unsigned)(mobjx - bmaporgx)>>MAPBLOCKSHIFT; xl = (unsigned)(mobjx - bmaporgx)>>MAPBLOCKSHIFT; BMBOUNDFIX(xl, xh, yl, yh); // Check polyobjects and see if bestpoheight need to be altered { validcount++; for (by = yl; by <= yh; by++) { for (bx = xl; bx <= xh; bx++) { INT32 offset; polymaplink_t *plink; // haleyjd 02/22/06 if (bx < 0 || by < 0 || bx >= bmapwidth || by >= bmapheight) continue; offset = by*bmapwidth + bx; // haleyjd 02/22/06: consider polyobject lines plink = polyblocklinks[offset]; while (plink) { polyobj_t *po = plink->po; if (po->validcount != validcount) // if polyobj hasn't been checked { sector_t *polysec; fixed_t compareheight; fixed_t valuecompare[2]; po->validcount = validcount; if (!P_BBoxInsidePolyobj(po, g_tm.bbox) || !(po->flags & POF_SOLID)) { plink = (polymaplink_t *)(plink->link.next); continue; } if (!(po->flags & POF_CLIPPLANES)) { plink = (polymaplink_t *)(plink->link.next); continue; } // We're inside it! Yess... polysec = po->lines[0]->backsector; if (flip) compareheight = polysec->ceilingheight; else compareheight = polysec->floorheight; if (curbest == INT32_MAX) curbest = compareheight; valuecompare[0] = curbest; valuecompare[1] = compareheight; // compareheight is closer then curbest // probably should consider flip { int N = sizeof(valuecompare) / sizeof(valuecompare[0]); fixed_t closetvalue = clostVal(valuecompare, N, mobjz); if (closetvalue == valuecompare[1]) { curbest = compareheight; result.bestpo = plink->po; } } } plink = (polymaplink_t *)(plink->link.next); } } } result.bestpoheight = curbest; } } result.finaldistances[0] = result.bestsectorheight; result.finaldistances[1] = result.bestfofheight; result.finaldistances[2] = result.bestpoheight; { int N = sizeof(result.finaldistances) / sizeof(result.finaldistances[0]); fixed_t closetvalue = clostVal(result.finaldistances, N, mobjz); if (closetvalue == result.bestfofheight && (result.bestfofheight != INT32_MAX)) result.result = RESPAWNRS_ROVER; else if (closetvalue == result.bestpoheight && (result.bestpoheight != INT32_MAX)) result.result = RESPAWNRS_PO; else result.result = RESPAWNRS_SECTOR; } // if rover is valid and bestfofheight is closer then bestsectorheight and bestpoheight // probably should consider flip if (result.result == RESPAWNRS_ROVER) { // Check if rover is bad based on both its terrain, and sector specials. if (result.bestrover) { switch (result.bestrover->master->frontsector->damagetype) { case SD_GENERIC: case SD_DEATHPIT: case SD_INSTAKILL: case SD_LAVA: { //CONS_Printf("Dangerous rover!!!!\n"); return false; } default: break; } if (result.bestrover->master->frontsector->offroad) { //CONS_Printf("Offroad rover!!!!\n"); return false; } //CONS_Printf("Rover safe, allow respawn!!!!\n"); return true; } else { //CONS_Printf("Nep Fucked up, yell at him! NO ROVER IN FOF CHECK\n"); return false; } } // if po is valid and bestpoheight is closer then bestsectorheight and bestpoheight // probably should consider flip else if (result.result == RESPAWNRS_PO) { // Check if po is bad based on both its terrain, and sector specials. if (result.bestpo) { switch (result.bestpo->lines[0]->backsector->damagetype) { case SD_GENERIC: case SD_DEATHPIT: case SD_INSTAKILL: case SD_LAVA: { //CONS_Printf("Dangerous polyobj!!!!\n"); return false; } default: break; } if (result.bestpo->lines[0]->backsector->offroad) { //CONS_Printf("Offroad polyobj!!!!\n"); return false; } //CONS_Printf("Polyobj safe, allow respawn!!!!\n"); return true; } else { //CONS_Printf("Nep Fucked up, yell at him! NO POLYOBJ IN PO CHECK\n"); return false; } } // if everything else is not closest check sector // probably should consider flip else if (result.result == RESPAWNRS_SECTOR) { // Check if sector is bad based on both its terrain, and sector specials. switch (result.cursec->damagetype) { case SD_GENERIC: case SD_DEATHPIT: case SD_INSTAKILL: case SD_LAVA: { //CONS_Printf("Dangerous sector!!!!\n"); return false; } default: break; } if (result.cursec->offroad) { //CONS_Printf("Offroad Sector!!!!\n"); return false; } //CONS_Printf("Sector safe, allow respawn!!!!\n"); return true; } CONS_Printf("Nep Fucked up, yell at him! BYPASSED ALL\n"); return false; // fallback if I fuck up. } /*-------------------------------------------------- size_t K_NextRespawnWaypointIndex(waypoint_t *waypoint) See header file for description. --------------------------------------------------*/ size_t K_NextRespawnWaypointIndex(waypoint_t *waypoint) { size_t i = 0U; size_t newwaypoint = SIZE_MAX; // Set to the first valid nextwaypoint, for simplicity's sake. // If we reach the last waypoint and it's still not valid, just use it anyway. Someone needs to fix their map! for (i = 0U; i < waypoint->numnextwaypoints; i++) { newwaypoint = i; if ((i == waypoint->numnextwaypoints - 1U) || ((K_GetWaypointIsEnabled(waypoint->nextwaypoints[newwaypoint]) == true) && (K_GetWaypointIsSpawnpoint(waypoint->nextwaypoints[newwaypoint]) == true))) { break; } } return newwaypoint; } /*-------------------------------------------------- static boolean K_SetPlayerNextWaypoint(player_t *player) Sets the next waypoint of a player, by finding their closest waypoint, then checking which of itself and next or previous waypoints are infront of the player. Also sets the current waypoint. Input Arguments:- player - The player the next waypoint is being found for Return:- Whether it is safe to update the respawn waypoint --------------------------------------------------*/ static boolean K_SetPlayerNextWaypoint(player_t *player) { waypoint_t *finishline = K_GetFinishLineWaypoint(); waypoint_t *bestwaypoint = NULL; boolean updaterespawn = false; if ((player != NULL) && (player->mo != NULL) && (P_MobjWasRemoved(player->mo) == false)) { waypoint_t *waypoint = K_GetBestWaypointForMobj(player->mo, player->currentwaypoint); // Our current waypoint. player->currentwaypoint = bestwaypoint = waypoint; // check the waypoint's location in relation to the player // If it's generally in front, it's fine, otherwise, use the best next/previous waypoint. // EXCEPTION: If our best waypoint is the finishline AND we're facing towards it, don't do this. // Otherwise it breaks the distance calculations. if (waypoint != NULL) { angle_t playerangle = player->mo->angle; angle_t momangle = K_MomentumAngle(player->mo); angle_t angletowaypoint = R_PointToAngle2(player->mo->x, player->mo->y, waypoint->mobj->x, waypoint->mobj->y); angle_t angledelta = ANGLE_180; angle_t momdelta = ANGLE_180; // Remove WRONG WAY flag. player->pflags &= ~PF_WRONGWAY; angledelta = playerangle - angletowaypoint; if (angledelta > ANGLE_180) { angledelta = InvAngle(angledelta); } momdelta = momangle - angletowaypoint; if (momdelta > ANGLE_180) { momdelta = InvAngle(momdelta); } // We're using a lot of angle calculations here, because only using facing angle or only using momentum angle both have downsides. // nextwaypoints will be picked if you're facing OR moving forward. // prevwaypoints will be picked if you're facing AND moving backward. #if 0 if (angledelta > ANGLE_45 || momdelta > ANGLE_45) #endif { angle_t nextbestdelta = ANGLE_90; angle_t nextbestmomdelta = ANGLE_90; size_t i = 0U; if ((waypoint->nextwaypoints != NULL) && (waypoint->numnextwaypoints > 0U)) { for (i = 0U; i < waypoint->numnextwaypoints; i++) { if (!K_GetWaypointIsEnabled(waypoint->nextwaypoints[i])) { continue; } if (K_PlayerUsesBotMovement(player) == true && K_GetWaypointIsShortcut(waypoint->nextwaypoints[i]) == true && K_BotCanTakeCut(player) == false) { // Bots that aren't able to take a shortcut will ignore shortcut waypoints. // (However, if they're already on a shortcut, then we want them to keep going.) if (player->nextwaypoint != NULL && K_GetWaypointIsShortcut(player->nextwaypoint) == false) { continue; } } angletowaypoint = R_PointToAngle2( player->mo->x, player->mo->y, waypoint->nextwaypoints[i]->mobj->x, waypoint->nextwaypoints[i]->mobj->y); angledelta = playerangle - angletowaypoint; if (angledelta > ANGLE_180) { angledelta = InvAngle(angledelta); } momdelta = momangle - angletowaypoint; if (momdelta > ANGLE_180) { momdelta = InvAngle(momdelta); } if (angledelta < nextbestdelta || momdelta < nextbestmomdelta) { if (waypoint->nextwaypoints[i] != finishline) // Allow finish line. { if (P_TraceWaypointTraversal(player->mo, waypoint->nextwaypoints[i]->mobj) == false) { // Save sight checks when all of the other checks pass, so we only do it if we have to continue; } } bestwaypoint = waypoint->nextwaypoints[i]; if (angledelta < nextbestdelta) { nextbestdelta = angledelta; } if (momdelta < nextbestmomdelta) { nextbestmomdelta = momdelta; } updaterespawn = true; } } } if ((waypoint->prevwaypoints != NULL) && (waypoint->numprevwaypoints > 0U) && !(K_PlayerUsesBotMovement(player))) // Bots do not need prev waypoints { for (i = 0U; i < waypoint->numprevwaypoints; i++) { if (!K_GetWaypointIsEnabled(waypoint->prevwaypoints[i])) { continue; } angletowaypoint = R_PointToAngle2( player->mo->x, player->mo->y, waypoint->prevwaypoints[i]->mobj->x, waypoint->prevwaypoints[i]->mobj->y); angledelta = playerangle - angletowaypoint; if (angledelta > ANGLE_180) { angledelta = InvAngle(angledelta); } momdelta = momangle - angletowaypoint; if (momdelta > ANGLE_180) { momdelta = InvAngle(momdelta); } if (angledelta < nextbestdelta && momdelta < nextbestmomdelta) { if (waypoint->prevwaypoints[i] == finishline) // NEVER allow finish line. { continue; } if (P_TraceWaypointTraversal(player->mo, waypoint->prevwaypoints[i]->mobj) == false) { // Save sight checks when all of the other checks pass, so we only do it if we have to continue; } if ((waypoint->nextwaypoints != NULL) && (waypoint->numnextwaypoints > 0U)) { for (size_t j = 0U; j < waypoint->numnextwaypoints; j++) { if (!K_GetWaypointIsEnabled(waypoint->nextwaypoints[j])) { continue; } if (waypoint->nextwaypoints[j] == waypoint) { continue; } bestwaypoint = waypoint->nextwaypoints[j]; break; } } nextbestdelta = angledelta; nextbestmomdelta = momdelta; // Set wrong way flag if we're using prevwaypoints player->pflags |= PF_WRONGWAY; updaterespawn = false; } } } } } if ((!P_IsObjectOnGround(player->mo) || player->respawn || P_CheckDeathPitCollide(player->mo) || P_InQuicksand(player->mo) || !player->mo->health) ) { updaterespawn = false; } if (player->pflags & PF_UPDATEMYRESPAWN) { updaterespawn = true; player->pflags &= ~PF_UPDATEMYRESPAWN; } // If nextwaypoint is NULL, it means we don't want to update the waypoint until we touch another one. // player->nextwaypoint will keep its previous value in this case. if (bestwaypoint != NULL) { player->nextwaypoint = bestwaypoint; } } return updaterespawn; } /*-------------------------------------------------- static void K_UpdateDistanceFromFinishLine(player_t *const player) Updates the distance a player has to the finish line. Input Arguments:- player - The player the distance is being updated for Return:- None --------------------------------------------------*/ static void K_UpdateDistanceFromFinishLine(player_t *player) { if ((player != NULL) && (player->mo != NULL)) { waypoint_t *finishline = K_GetFinishLineWaypoint(); // nextwaypoint is now the waypoint that is in front of us if ((player->exiting && !(player->pflags & PF_NOCONTEST)) || player->spectator) { // Player has finished, we don't need to calculate this player->distancetofinish = 0U; } else if (player->pflags & PF_NOCONTEST) { // We also don't need to calculate this, but there's also no need to destroy the data... ; } else if ((player->currentwaypoint != NULL) && (player->nextwaypoint != NULL) && (finishline != NULL)) { const boolean useshortcuts = false; const boolean huntbackwards = false; boolean pathfindsuccess = false; path_t pathtofinish = {0}; pathfindsuccess = K_PathfindToWaypoint(player->nextwaypoint, finishline, &pathtofinish, useshortcuts, huntbackwards); // Update the player's distance to the finish line if a path was found. // Using shortcuts won't find a path, so distance won't be updated until the player gets back on track if (pathfindsuccess == true) { const boolean pathBackwardsReverse = ((player->pflags & PF_WRONGWAY) == 0); boolean pathBackwardsSuccess = false; path_t pathBackwards = {0}; fixed_t disttonext = 0; UINT32 traveldist = 0; UINT32 adddist = 0; disttonext = P_AproxDistance( (player->mo->x >> FRACBITS) - (player->nextwaypoint->mobj->x >> FRACBITS), (player->mo->y >> FRACBITS) - (player->nextwaypoint->mobj->y >> FRACBITS)); disttonext = P_AproxDistance(disttonext, (player->mo->z >> FRACBITS) - (player->nextwaypoint->mobj->z >> FRACBITS)); traveldist = ((UINT32)disttonext) * 2; pathBackwardsSuccess = K_PathfindThruCircuit(player->nextwaypoint, traveldist, &pathBackwards, false, pathBackwardsReverse); if (pathBackwardsSuccess == true) { if (pathBackwards.numnodes > 1) { // Find the closest segment, and add the distance to reach it. vector3_t point; size_t i; vector3_t best; fixed_t bestPoint = INT32_MAX; fixed_t bestDist = INT32_MAX; UINT32 bestGScore = UINT32_MAX; point.x = player->mo->x; point.y = player->mo->y; point.z = player->mo->z; best.x = point.x; best.y = point.y; best.z = point.z; for (i = 1; i < pathBackwards.numnodes; i++) { vector3_t line[2]; vector3_t result; waypoint_t *pwp = (waypoint_t *)pathBackwards.array[i - 1].nodedata; waypoint_t *wp = (waypoint_t *)pathBackwards.array[i].nodedata; fixed_t pDist = 0; UINT32 g = pathBackwards.array[i - 1].gscore; line[0].x = pwp->mobj->x; line[0].y = pwp->mobj->y; line[0].z = pwp->mobj->z; line[1].x = wp->mobj->x; line[1].y = wp->mobj->y; line[1].z = wp->mobj->z; P_ClosestPointOnLine3D(&point, line, &result); pDist = P_AproxDistance(point.x - result.x, point.y - result.y); pDist = P_AproxDistance(pDist, point.z - result.z); if (pDist < bestPoint) { FV3_Copy(&best, &result); bestPoint = pDist; bestDist = P_AproxDistance( (result.x >> FRACBITS) - (line[0].x >> FRACBITS), (result.y >> FRACBITS) - (line[0].y >> FRACBITS)); bestDist = P_AproxDistance(bestDist, (result.z >> FRACBITS) - (line[0].z >> FRACBITS)); bestGScore = g + ((UINT32)bestDist); } } #if 0 if (cv_kartdebugwaypoints.value) { mobj_t *debugmobj = P_SpawnMobj(best.x, best.y, best.z, MT_SPARK); P_SetMobjState(debugmobj, S_WAYPOINTORB); debugmobj->frame &= ~FF_TRANSMASK; debugmobj->frame |= FF_FULLBRIGHT; //FF_TRANS20 debugmobj->tics = 1; debugmobj->color = SKINCOLOR_BANANA; } #endif adddist = bestGScore; } /* else { // Only one point to work with, so just add your euclidean distance to that. waypoint_t *wp = (waypoint_t *)pathBackwards.array[0].nodedata; fixed_t disttowaypoint = P_AproxDistance( (player->mo->x >> FRACBITS) - (wp->mobj->x >> FRACBITS), (player->mo->y >> FRACBITS) - (wp->mobj->y >> FRACBITS)); disttowaypoint = P_AproxDistance(disttowaypoint, (player->mo->z >> FRACBITS) - (wp->mobj->z >> FRACBITS)); adddist = (UINT32)disttowaypoint; } */ Z_Free(pathBackwards.array); } /* else { // Fallback to adding euclidean distance to the next waypoint to the distancetofinish adddist = (UINT32)disttonext; } */ if (pathBackwardsReverse == false) { if (pathtofinish.totaldist > adddist) { player->distancetofinish = pathtofinish.totaldist - adddist; } else { player->distancetofinish = 0; } } else { player->distancetofinish = pathtofinish.totaldist + adddist; } Z_Free(pathtofinish.array); // distancetofinish is currently a flat distance to the finish line, but in order to be fully // correct we need to add to it the length of the entire circuit multiplied by the number of laps // left after this one. This will give us the total distance to the finish line, and allow item // distance calculation to work easily const mapheader_t *mapheader = mapheaderinfo[gamemap - 1]; if ((mapheader->levelflags & LF_SECTIONRACE) == 0U) { const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection; player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); } } } } } static UINT32 u32_delta(UINT32 x, UINT32 y) { return x > y ? x - y : y - x; } /*-------------------------------------------------- * static void K_FudgeRespawn(player_t *player, const waypoint_t *const waypoint) * * Fudges respawn coordinates to slightly before the waypoint if it would * be exactly on a line. See K_GetWaypointIsOnLine. - *-------------------------------------------------*/ static void K_FudgeRespawn(player_t *player, const waypoint_t *const waypoint) { const angle_t from = R_PointToAngle2(waypoint->mobj->x, waypoint->mobj->y, player->mo->x, player->mo->y) >> ANGLETOFINESHIFT; player->starpostx += FixedMul(16, FINECOSINE(from)); player->starposty += FixedMul(16, FINESINE(from)); } void K_SetRespawnAtNextWaypoint(player_t * player) { mobj_t *currentwaypoint = player->currentwaypoint->mobj; mobj_t *safewaypoint = player->nextwaypoint->mobj; angle_t respawnangle = R_PointToAngle2(currentwaypoint->x, currentwaypoint->y, safewaypoint->x, safewaypoint->y); // Safety :P if (!safewaypoint || !currentwaypoint) { // Better safe then sorry. return; } player->pflags |= PF_TRUSTWAYPOINTS; player->starposttime = player->realtime; player->starpostz = (safewaypoint->spawnpoint->z + 15) >> FRACBITS; player->starpostflip = (safewaypoint->flags2 & MF2_OBJECTFLIP); player->starpostangle = respawnangle; // Then do x and y player->starpostx = safewaypoint->x >> FRACBITS; player->starposty = safewaypoint->y >> FRACBITS; } static boolean K_MobjIsOnLine(mobj_t *const mobj) { const fixed_t x = mobj->x; const fixed_t y = mobj->y; line_t *line = P_FindNearestLine(x, y, mobj->subsector->sector, -1); vertex_t point; if (line != NULL) { P_ClosestPointOnLine(x, y, line, &point); if (x == point.x && y == point.y) return true; } return false; } static void K_MobjFudgeRespawn(player_t *player, const mobj_t *const mobj) { const angle_t from = R_PointToAngle2(mobj->x, mobj->y, player->mo->x, player->mo->y) >> ANGLETOFINESHIFT; player->starpostx += FixedMul(25, FINECOSINE(from)); player->starposty += FixedMul(25, FINESINE(from)); } /*-------------------------------------------------- static void K_UpdatePlayerWaypoints(player_t *const player) Updates the player's waypoints and finish line distance. Input Arguments:- player - The player to update Return:- None --------------------------------------------------*/ static void K_UpdatePlayerWaypoints(player_t *const player) { const UINT32 distance_threshold = FixedMul(32768, mapobjectscale); waypoint_t *const old_currentwaypoint = player->currentwaypoint; waypoint_t *const old_nextwaypoint = player->nextwaypoint; boolean updaterespawn = K_SetPlayerNextWaypoint(player); // Update prev value (used for grief prevention code) player->distancetofinishprev = player->distancetofinish; K_UpdateDistanceFromFinishLine(player); // Respawning should be a full reset. UINT32 delta = u32_delta(player->distancetofinish, player->distancetofinishprev); if (delta > distance_threshold && !player->respawn && // Respawning should be a full reset. old_currentwaypoint != NULL && // So should touching the first waypoint ever. player->laps != 0 && // POSITION rooms may have unorthodox waypoints to guide bots. !(player->pflags & PF_TRUSTWAYPOINTS)) // Special exception. { #define debug_args "Player %s: waypoint ID %d too far away (%u > %u)\n", \ sizeu1(player - players), K_GetWaypointID(player->nextwaypoint), delta, distance_threshold if (cv_kartdebuglap.value) CONS_Printf(debug_args); else CONS_Debug(DBG_GAMELOGIC, debug_args); #undef debug_args if (!cv_kartdebuglap.value) { // Distance jump is too great, keep the old waypoints and old distance. player->currentwaypoint = old_currentwaypoint; player->nextwaypoint = old_nextwaypoint; player->distancetofinish = player->distancetofinishprev; // Start the auto respawn timer when the distance jumps. if (!player->bigwaypointgap) { player->bigwaypointgap = AUTORESPAWN_TIME; } } } else { // Reset the auto respawn timer if distance changes are back to normal. if (player->bigwaypointgap && player->bigwaypointgap <= AUTORESPAWN_THRESHOLD + 1) { player->bigwaypointgap = 0; // While the player was in the "bigwaypointgap" state, laps did not change from crossing finish lines. // So reset the lap back to normal, in case they were able to get behind the line. if (!K_UsingLegacyCheckpoints()) { player->laps = player->lastsafelap; player->starpostnum = player->lastsafestarpost; } } } // Respawn point should only be updated when we're going to a nextwaypoint if ((updaterespawn) && (player->bigwaypointgap == 0) && (!player->respawn) && (player->nextwaypoint != old_nextwaypoint) && (K_GetWaypointIsSpawnpoint(player->nextwaypoint)) && (K_GetWaypointIsEnabled(player->nextwaypoint) == true)) { if (!(player->pflags & PF_WRONGWAY)) player->grieftime = 0; player->lastsafelap = player->laps; player->lastsafestarpost = player->starpostnum; // Check if respawn is safe. If not then goto next spawnpoint and respawn there. if (K_SafeRespawnPosition(player->mo)) { // Set time, z, flip and angle first. player->starposttime = player->realtime; player->starpostz = player->mo->z >> FRACBITS; player->starpostflip = (player->mo->eflags & MFE_VERTICALFLIP) ? true : false; player->starpostangle = player->mo->angle; // Then do x and y player->starpostx = player->mo->x >> FRACBITS; player->starposty = player->mo->y >> FRACBITS; if (K_MobjIsOnLine(player->mo)) { K_MobjFudgeRespawn(player, player->mo); } } else { // Now a clean function! Neat, eh? K_SetRespawnAtNextWaypoint(player); } if (player->nextwaypoint->onaline) { K_FudgeRespawn(player, player->nextwaypoint); } } player->pflags &= ~PF_TRUSTWAYPOINTS; // clear special exception } INT32 K_GetKartRingPower(player_t *player, boolean boosted) { INT32 ringPower = ((9 - player->kartspeed) + (9 - player->kartweight)) / 2; if (boosted == true && K_PlayerUsesBotMovement(player)) { // Double for Lv. 9 ringPower += (player->botvars.difficulty * ringPower) / DIFFICULTBOT; } if (player->kartspeed == 9 && player->kartweight == 9) { // Give the poor fuck an extra tic of ring duration so he isn't constantly dropping speed. ringPower += 1; } return ringPower; } // Returns false if this player being placed here causes them to collide with any other player // Used in g_game.c for match etc. respawning // This does not check along the z because the z is not correctly set for the spawnee at this point boolean K_CheckPlayersRespawnColliding(INT32 playernum, fixed_t x, fixed_t y) { INT32 i; fixed_t p1radius = players[playernum].mo->radius; for (i = 0; i < MAXPLAYERS; i++) { if (playernum == i || !playeringame[i] || players[i].spectator || !players[i].mo || players[i].mo->health <= 0 || players[i].playerstate != PST_LIVE || (players[i].mo->flags & MF_NOCLIP) || (players[i].mo->flags & MF_NOCLIPTHING)) continue; if (abs(x - players[i].mo->x) < (p1radius + players[i].mo->radius) && abs(y - players[i].mo->y) < (p1radius + players[i].mo->radius)) { return false; } } return true; } // countersteer is how strong the controls are telling us we are turning // turndir is the direction the controls are telling us to turn, -1 if turning right and 1 if turning left static INT16 K_GetKartDriftValue(player_t *player, fixed_t countersteer) { INT16 basedrift, driftadjust; fixed_t driftweight = player->kartweight*14; // 12 if (player->drift == 0 || !P_IsObjectOnGround(player->mo)) { // If they aren't drifting or on the ground, this doesn't apply return 0; } if (player->pflags & PF_DRIFTEND) { // Drift has ended and we are tweaking their angle back a bit return -266*player->drift; } basedrift = (83 * player->drift) - (((driftweight - 14) * player->drift) / 5); // 415 - 303 driftadjust = abs((252 - driftweight) * player->drift / 5); return basedrift + (FixedMul(driftadjust * FRACUNIT, countersteer) / FRACUNIT); } INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) { fixed_t p_topspeed = K_GetKartSpeed(player, false, false); fixed_t p_curspeed = min(player->speed, p_topspeed * 2); fixed_t p_maxspeed = p_topspeed * 3; fixed_t adjustangle = FixedDiv((p_maxspeed>>16) - (p_curspeed>>16), (p_maxspeed>>16) + player->kartweight); if (player->spectator) return turnvalue; if (player->drift != 0 && P_IsObjectOnGround(player->mo)) { // If we're drifting we have a completely different turning value if (!(player->pflags & PF_DRIFTEND)) { // 800 is the max set in g_game.c with angleturn fixed_t countersteer = FixedDiv(turnvalue*FRACUNIT, 800*FRACUNIT); turnvalue = K_GetKartDriftValue(player, countersteer); } else turnvalue = (INT16)(turnvalue + K_GetKartDriftValue(player, FRACUNIT)); return turnvalue; } turnvalue = FixedMul(turnvalue, adjustangle); // Weight has a small effect on turning if (player->invincibilitytimer || player->sneakertimer || player->growshrinktimer > 0) turnvalue = FixedMul(turnvalue, FixedDiv(5*FRACUNIT, 4*FRACUNIT)); if (player->flamedash && player->flamestore) // Reduce turning { fixed_t dashval = ((player->flamedash< FRACUNIT) return 0; // NO MORE TURNING! turnvalue = FixedMul(turnvalue, FRACUNIT-dashval); //CONS_Printf("dashval: %d\n",dashval); //CONS_Printf("turnval: %d\n",turnvalue); } return turnvalue; } INT32 K_GetKartDriftSparkValue(player_t *player) { UINT8 kartspeed = ((gametyperules & GTR_KARMA) && player->bumper <= 0) ? 1 : player->kartspeed; return (26*4 + kartspeed*2 + (9 - player->kartweight))*8; } INT32 K_GetKartDriftSparkValueForStage(player_t *player, UINT8 stage) { fixed_t mul = FRACUNIT; // This code is function is pretty much useless now that the timing changes are linear but bleh. switch (stage) { case 2: mul = 2*FRACUNIT; // x2 break; case 3: mul = 3*FRACUNIT; // x3 break; case 4: mul = 4*FRACUNIT; // x4 break; } return (FixedMul(K_GetKartDriftSparkValue(player) * FRACUNIT, mul) / FRACUNIT); } static void K_SpawnDriftEFX(player_t *player,SINT8 level) { if (!cv_kartdriftefx.value) { // Not wanted sorry! return; } mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BOOSTFLAME); P_SetMobjState(overlay, S_DRIFTBOOSTFLAME); P_SetTarget(&overlay->target, player->mo); P_SetScale(overlay, (overlay->destscale = player->mo->scale)); K_FlipFromObject(overlay, player->mo); overlay->extravalue1 = level; if (level == 1) overlay->color = SKINCOLOR_SAPPHIRE; else if (level == 2) overlay->color = SKINCOLOR_KETCHUP; else if (level == 3) overlay->color = SKINCOLOR_PURPLE; } static void K_KartDrift(player_t *player, boolean onground) { fixed_t minspeed = (10 * player->mo->scale); fixed_t driftadditive = 24; INT32 dsone = K_GetKartDriftSparkValue(player); INT32 dstwo = dsone*2; INT32 dsthree = cv_kartpurpledrift.value ? dsone*3 : dsone*4; INT32 dsfour = dsone*4; UINT16 buttons = K_GetKartButtons(player); // Grown players taking yellow spring panels will go below minspeed for one tic, // and will then wrongdrift or have their sparks removed because of this. // This fixes this problem. if (player->pogospring == 2 && player->mo->scale > mapobjectscale) minspeed = FixedMul(10<drift != -5 && player->drift != 5) && player->driftcharge < dsone && onground) { player->driftcharge = 0; } else if ((player->drift != -5 && player->drift != 5) && (player->driftcharge >= dsone && player->driftcharge < dstwo) && onground) { if (player->driftboost < 20) player->driftboost = 20; S_StartSound(player->mo, sfx_s23c); K_SpawnDriftEFX(player, 1); player->driftcharge = 0; } else if ((player->drift != -5 && player->drift != 5) && (player->driftcharge >= dstwo && player->driftcharge < dsthree) && onground) { if (player->driftboost < 50) player->driftboost = 50; if (cv_kartdriftsounds.value) S_StartSound(player->mo, sfx_kc5b); S_StartSound(player->mo, sfx_s23c); K_SpawnDriftEFX(player, 2); player->driftcharge = 0; } else if ((player->drift != -5 && player->drift != 5) && player->driftcharge < dsfour && onground) { if (player->driftboost < 80) player->driftboost = 80; if (cv_kartdriftsounds.value) { S_StartSound(player->mo, sfx_kc5b); S_StartSound(player->mo, sfx_kc3c); S_StartSound(player->mo, sfx_s3k47); } else { S_StartSound(player->mo, sfx_s23c); } K_SpawnDriftEFX(player, 3); player->driftcharge = 0; } else if ((player->drift != -5 && player->drift != 5) && player->driftcharge >= dsfour && onground) { if (player->driftboost < 125) player->driftboost = 125; if (cv_kartdriftsounds.value) { S_StartSound(player->mo, sfx_kc5b); S_StartSound(player->mo, sfx_kc4d); } else { S_StartSound(player->mo, sfx_s23c); } K_SpawnDriftEFX(player, 4); player->driftcharge = 0; } // Drifting: left or right? if ((player->cmd.turning > 0) && player->speed > minspeed && (player->pflags & PF_DRIFTINPUT) && (player->drift == 0 || (player->pflags & PF_DRIFTEND))) { // Starting left drift player->drift = 1; player->pflags &= ~PF_DRIFTEND; } else if ((player->cmd.turning < 0) && player->speed > minspeed && (player->pflags & PF_DRIFTINPUT) && (player->drift == 0 || (player->pflags & PF_DRIFTEND))) { // Starting right drift player->drift = -1; player->pflags &= ~PF_DRIFTEND; } else if (!(player->pflags & PF_DRIFTINPUT)) { // drift is not being performed so if we're just finishing set driftend and decrement counters if (player->drift > 0) { player->drift--; player->pflags |= PF_DRIFTEND; } else if (player->drift < 0) { player->drift++; player->pflags |= PF_DRIFTEND; } else player->pflags &= ~PF_DRIFTEND; } // Incease/decrease the drift value to continue drifting in that direction if (!P_PlayerInPain(player) && (player->pflags & PF_DRIFTINPUT) && onground && player->drift != 0) { if (player->drift >= 1) // Drifting to the left { player->drift++; if (player->drift > 5) player->drift = 5; if (player->cmd.turning > 0) // Inward driftadditive += abs(player->cmd.turning)/100; if (player->cmd.turning < 0) // Outward driftadditive -= abs(player->cmd.turning)/75; } else if (player->drift <= -1) // Drifting to the right { player->drift--; if (player->drift < -5) player->drift = -5; if (player->cmd.turning < 0) // Inward driftadditive += abs(player->cmd.turning)/100; if (player->cmd.turning > 0) // Outward driftadditive -= abs(player->cmd.turning)/75; } // Disable drift-sparks until you're going fast enough if (!(player->pflags & PF_GETSPARKS) || (player->offroad && K_ApplyOffroad(player))) driftadditive = 0; if (player->speed > minspeed*2) player->pflags |= PF_GETSPARKS; // Sound whenever you get a different tier of sparks if ((player->driftcharge < dsone && player->driftcharge+driftadditive >= dsone) || (player->driftcharge < dstwo && player->driftcharge+driftadditive >= dstwo) || (player->driftcharge < dsthree && player->driftcharge+driftadditive >= dsthree) || (player->driftcharge < dsfour && player->driftcharge+driftadditive >= dsfour)) { if (P_IsDisplayPlayer(player)) // UGHGHGH... S_StartSoundAtVolume(player->mo, sfx_s3ka2, 192); // Ugh... } player->driftcharge += driftadditive; player->pflags &= ~PF_DRIFTEND; } // Spawn Sparks regardless of size if (!P_PlayerInPain(player) && player->drift != 0) { if (player->driftcharge >= dsone) K_SpawnDriftSparks(player); } // Stop drifting if (P_PlayerInPain(player) || player->speed < minspeed) { player->drift = player->driftcharge = player->aizdriftstrat = 0; player->pflags &= ~(PF_BRAKEDRIFT|PF_GETSPARKS); } if ( (!(player->sneakertimer || player->flamestore)) || (!player->cmd.turning) || (!player->aizdriftstrat) || (player->cmd.turning > 0) != (player->aizdriftstrat > 0)) { if (!player->drift) player->aizdriftstrat = 0; else player->aizdriftstrat = ((player->drift > 0) ? 1 : -1); } else if (K_Sliptiding(player)) { K_SpawnAIZDust(player); if (abs(player->aizdrifttilt) < ANGLE_22h) { player->aizdrifttilt = (abs(player->aizdrifttilt) + ANGLE_11hh / 4) * player->aizdriftstrat; } if (abs(player->aizdriftturn) < ANGLE_112h) { player->aizdriftturn = (abs(player->aizdriftturn) + ANGLE_11hh) * player->aizdriftstrat; } } if (!K_Sliptiding(player)) { player->aizdrifttilt -= player->aizdrifttilt / 4; player->aizdriftturn -= player->aizdriftturn / 4; if (abs(player->aizdrifttilt) < ANGLE_11hh / 4) player->aizdrifttilt = 0; if (abs(player->aizdriftturn) < ANGLE_11hh) player->aizdriftturn = 0; } if (player->drift && ((buttons & BT_BRAKE) || !(buttons & BT_ACCELERATE)) && P_IsObjectOnGround(player->mo)) { if (!(player->pflags & PF_BRAKEDRIFT)) K_SpawnBrakeDriftSparks(player); player->pflags |= PF_BRAKEDRIFT; } else player->pflags &= ~PF_BRAKEDRIFT; } static void K_KartSlipdash(player_t *player, boolean onground) { boolean snaked = player->slipdashdir && player->aizdriftstrat && player->slipdashdir != player->aizdriftstrat; if (!cv_kartslipdash.value || player->spinouttimer) { player->slipdashcharge = 0; player->slipdashdir = 0; } else if ((K_Sliptiding(player) || player->aizdriftturn) && !snaked) { if (!player->slipdashdir) // anti-snaking player->slipdashdir = player->aizdriftstrat; if (onground && !player->drift) { fixed_t turn = max(0, player->cmd.turning * player->aizdriftstrat); turn = FINETANGENT((turn*(ANGLE_45/KART_FULLTURN) + ANGLE_90) >> ANGLETOFINESHIFT); player->slipdashcharge = min(player->slipdashcharge + turn/50, FRACUNIT); if (!(leveltime % 5)) S_StartSoundAtVolume(player->mo, sfx_cdfm17, 133 + turn/555); } } else if (player->slipdashcharge && onground) { INT32 i; boolean driftbonus = player->driftcharge && !snaked; if (snaked) player->slipdashcharge /= 4; S_StopSoundByID(player->mo, sfx_cdfm17); S_StartSoundAtVolume(player->mo, player->slipdashcharge > FRACUNIT/3 ? sfx_cdfm62 : sfx_s23c, driftbonus ? 200 : 255); P_Thrust(player->mo, player->mo->angle, FixedMul(K_GetKartSpeed(player, false, false), 2*player->slipdashcharge/3)); for (i = -1; i <= 1; i += 2) { mobj_t *strat = P_SpawnMobjFromMobj(player->mo, P_ReturnThrustX(NULL, player->mo->angle + ANGLE_67h*i, -20*player->mo->scale), P_ReturnThrustY(NULL, player->mo->angle + ANGLE_67h*i, -20*player->mo->scale), 0, MT_AIZDRIFTSTRAT ); strat->angle = player->mo->angle + ANGLE_67h*i; P_SetScale(strat, FixedMul(player->slipdashcharge + FRACUNIT/4, mapobjectscale)); strat->destscale = strat->scale; strat->momx = P_ReturnThrustX(NULL, player->mo->angle, -8*mapobjectscale); strat->momy = P_ReturnThrustY(NULL, player->mo->angle, -8*mapobjectscale); } if (driftbonus) { INT32 dsv = K_GetKartDriftSparkValue(player); player->driftcharge = min(player->driftcharge + FixedMul(player->slipdashcharge, dsv), dsv-1); S_StartSound(player->mo, sfx_s3k45); } player->slipdashcharge = 0; player->slipdashdir = 0; } else player->slipdashdir = 0; if (player->slipdashcharge) { mobj_t *spark = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_DRIFTSPARK); spark->color = SKINCOLOR_GREEN; spark->angle = player->mo->angle + player->aizdriftstrat*ANGLE_45; P_SetScale(spark, FixedMul(player->slipdashcharge + 2*FRACUNIT/3, mapobjectscale)); spark->destscale = 0; spark->scalespeed = mapobjectscale/4; } } INT32 K_GetDriftAngleOffset(player_t *player) { INT32 a = 0; boolean compat = wadfiles[skins[player->skin].wadnum]->compatmode; if (player->aizdriftturn) { a = player->aizdriftturn; if (compat) a /= 2; // don't turn too much in compatmode } else if (player->drift != 0) { a = (ANGLE_45 / 5) * player->drift; } return a; } // // K_KartUpdatePosition // void K_KartUpdatePosition(player_t *player) { fixed_t position = 1; fixed_t oldposition = player->position; fixed_t i; if (player->spectator || !player->mo) { // Ensure these are reset for spectators player->position = 0; player->positiondelay = 0; return; } for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator || !players[i].mo) continue; if (gametyperules & GTR_CIRCUIT) { if (player->exiting) // End of match standings { // Only time matters if (players[i].realtime < player->realtime) position++; } else { // I'm a lap behind this player OR // My distance to the finish line is higher, so I'm behind if ((players[i].laps > player->laps) || (players[i].distancetofinish < player->distancetofinish)) { position++; } } } else { if (player->exiting) // End of match standings { // Only score matters if (players[i].roundscore > player->roundscore) position++; } else { if (player->exiting) // End of match standings { // Only score matters if (players[i].roundscore > player->roundscore) position++; } else { // I have less points than but the same bumpers as this player OR // I have less bumpers than this player if ((players[i].bumper == player->bumper && players[i].roundscore > player->roundscore) || (players[i].bumper > player->bumper)) position++; } } } } if (leveltime < starttime || oldposition == 0) oldposition = position; if (oldposition != position) // Changed places? player->positiondelay = 10; // Position number growth player->position = position; } void K_KartLegacyUpdatePosition(player_t *player) { fixed_t position = 1; fixed_t oldposition = player->position; fixed_t i, ppcd, pncd, ipcd, incd; fixed_t pmo, imo; mobj_t *mo; if (player->spectator || !player->mo) return; for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator || !players[i].mo) continue; if (gametyperules & GTR_CIRCUIT) { if ((((players[i].starpostnum) + (numstarposts + 1) * players[i].laps) > ((player->starpostnum) + (numstarposts + 1) * player->laps))) position++; else if (((players[i].starpostnum) + (numstarposts+1)*players[i].laps) == ((player->starpostnum) + (numstarposts+1)*player->laps)) { ppcd = pncd = ipcd = incd = 0; player->prevcheck = players[i].prevcheck = 0; player->nextcheck = players[i].nextcheck = 0; // This checks every thing on the map, and looks for MT_BOSS3WAYPOINT (the thing we're using for checkpoint wp's, for now) for (mo = boss3cap; mo != NULL; mo = mo->tracer) { pmo = P_AproxDistance(P_AproxDistance( mo->x - player->mo->x, mo->y - player->mo->y), mo->z - player->mo->z) / FRACUNIT; imo = P_AproxDistance(P_AproxDistance( mo->x - players[i].mo->x, mo->y - players[i].mo->y), mo->z - players[i].mo->z) / FRACUNIT; if (mo->health == player->starpostnum && (!mo->movecount || mo->movecount == player->laps+1)) { player->prevcheck += pmo; ppcd++; } if (mo->health == (player->starpostnum + 1) && (!mo->movecount || mo->movecount == player->laps+1)) { player->nextcheck += pmo; pncd++; } if (mo->health == players[i].starpostnum && (!mo->movecount || mo->movecount == players[i].laps+1)) { players[i].prevcheck += imo; ipcd++; } if (mo->health == (players[i].starpostnum + 1) && (!mo->movecount || mo->movecount == players[i].laps+1)) { players[i].nextcheck += imo; incd++; } } if (ppcd > 1) player->prevcheck /= ppcd; if (pncd > 1) player->nextcheck /= pncd; if (ipcd > 1) players[i].prevcheck /= ipcd; if (incd > 1) players[i].nextcheck /= incd; if ((players[i].nextcheck > 0 || player->nextcheck > 0) && !player->exiting) { if ((players[i].nextcheck - players[i].prevcheck) < (player->nextcheck - player->prevcheck)) position++; } else if (!player->exiting) { if (players[i].prevcheck > player->prevcheck) position++; } else { if (players[i].starposttime < player->starposttime) position++; } } } } if (leveltime < starttime || oldposition == 0) oldposition = position; if (oldposition != position) // Changed places? player->positiondelay = 10; // Position number growth player->position = position; } void K_UpdateAllPlayerPositions(void) { INT32 i; if (!K_UsingLegacyCheckpoints()) { // First loop: Ensure all players' distance to the finish line are all accurate for (i = 0; i < MAXPLAYERS; i++) { player_t *player = &players[i]; if (!playeringame[i] || player->spectator || !player->mo || P_MobjWasRemoved(player->mo)) { continue; } if (player->respawn > 0 && player->lastsafelap != player->laps) { if (!K_UsingLegacyCheckpoints()) { player->laps = player->lastsafelap; player->starpostnum = player->lastsafestarpost; } } K_UpdatePlayerWaypoints(player); } } // Second loop: Ensure all player positions reflect everyone's distances for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) { if (K_UsingLegacyCheckpoints()) K_KartLegacyUpdatePosition(&players[i]); else K_KartUpdatePosition(&players[i]); } } } // // K_StripItems // void K_StripItems(player_t *player) { K_DropRocketSneaker(player); K_DropKitchenSink(player); player->itemtype = KITEM_NONE; player->itemamount = 0; player->itemflags &= ~(IF_ITEMOUT|IF_EGGMANOUT); if (!player->itemroulette || player->roulettetype != 2) { player->itemroulette = 0; player->roulettetype = 0; } player->hyudorotimer = 0; player->stealingtimer = 0; player->stolentimer = 0; player->curshield = KSHIELD_NONE; player->bananadrag = 0; player->sadtimer = 0; K_UpdateHnextList(player, true); } void K_StripOther(player_t *player) { player->itemroulette = 0; player->roulettetype = 0; player->invincibilitytimer = 0; if (player->growshrinktimer) { K_RemoveGrowShrink(player); } if (player->eggmanexplode) { player->eggmanexplode = 0; player->eggmanblame = -1; K_KartResetPlayerColor(player); } } SINT8 K_Sliptiding(player_t *player) { return player->drift ? 0 : player->aizdriftstrat; } static void K_AirFailsafe(player_t *player) { const fixed_t maxSpeed = 1*player->mo->scale; const fixed_t thrustSpeed = 6*player->mo->scale; // 10*player->mo->scale if (player->speed > maxSpeed // Above the max speed that you're allowed to use this technique. || player->respawn // Respawning, you don't need this AND drop dash :V || player->carry // Don't do in automated sections. || P_PlayerInPain(player)) // Don't assist people hit by mines. { player->pflags &= ~PF_AIRFAILSAFE; return; } if ((K_GetKartButtons(player) & BT_ACCELERATE) || player->cmd.forwardmove != 0) { // Queue up later player->pflags |= PF_AIRFAILSAFE; return; } if (player->pflags & PF_AIRFAILSAFE) { // Push the player forward P_Thrust(player->mo, K_MomentumAngle(player->mo), thrustSpeed); S_StartSound(player->mo, sfx_s23c); K_SpawnNormalSpeedLines(player); player->pflags &= ~PF_AIRFAILSAFE; } } // // static void K_AdjustPlayerFriction(player_t *player) { boolean onground = P_IsObjectOnGround(player->mo); fixed_t prevfriction = player->mo->friction; if (!onground) { // Don't Calculate friction in the air. return; } if (player->pogospring) { // JugadorXEI: Do *not* calculate friction when a player is pogo'd // because they'll be in the air and friction will not reset! return; } // Friction if (!player->offroad) { if (player->speed > 0 && player->cmd.forwardmove == 0 && player->mo->friction == 59392) player->mo->friction += 4608; } if (player->speed > 0 && player->cmd.forwardmove < 0) // change friction while braking no matter what, otherwise it's not any more effective than just letting go off accel player->mo->friction -= 2048; // Reduce friction after hitting a spring if (player->tiregrease) { player->mo->friction += ((FRACUNIT - FRACUNIT) / greasetics) * player->tiregrease; } // Karma ice physics if ((gametyperules & GTR_KARMA) && player->bumper <= 0) { player->mo->friction += 1228; if (player->mo->friction > FRACUNIT) player->mo->friction = FRACUNIT; if (player->mo->friction < 0) player->mo->friction = 0; player->mo->movefactor = FixedDiv(ORIG_FRICTION, player->mo->friction); if (player->mo->movefactor < FRACUNIT) player->mo->movefactor = 19*player->mo->movefactor - 18*FRACUNIT; else player->mo->movefactor = FRACUNIT; //player->mo->movefactor = ((player->mo->friction - 0xDB34)*(0xA))/0x80; if (player->mo->movefactor < 32) player->mo->movefactor = 32; } if (K_PlayerUsesBotMovement(player) == true && (player->dashpadcooldown == 0)) { const fixed_t factor = FixedMul( FixedDiv(FRACUNIT - prevfriction, FRACUNIT - ORIG_FRICTION), K_GetKartGameSpeedScalar(gamespeed) ); const fixed_t speedPercent = FixedDiv(stplyr->speed, FixedMul(K_GetKartSpeed(stplyr, false, false), ORIG_FRICTION)) /100; const fixed_t extraFriction = FixedMul(FixedMul(FRACUNIT >> 5, factor), speedPercent); // A bit extra friction to help them without drifting. // Remove this line once they can drift. player->mo->friction -= extraFriction; // Bots gain more traction as they rubberband. const fixed_t traction_value = FixedMul(player->botvars.rubberband, max(FRACUNIT, K_BotMapModifier())); if (traction_value > FRACUNIT) { const fixed_t traction_mul = traction_value - FRACUNIT; player->mo->friction -= FixedMul(extraFriction, traction_mul); } if (player->mo->friction > FRACUNIT) player->mo->friction = FRACUNIT; if (player->mo->friction < 0) player->mo->friction = 0; player->mo->movefactor = FixedDiv(ORIG_FRICTION, player->mo->friction); if (player->mo->movefactor < FRACUNIT) player->mo->movefactor = 19*player->mo->movefactor - 18*FRACUNIT; else player->mo->movefactor = FRACUNIT; //player->mo->movefactor = ((player->mo->friction - 0xDB34)*(0xA))/0x80; if (player->mo->movefactor < 32) player->mo->movefactor = 32; } // Wipeout slowdown if (player->speed > 0 && player->spinouttimer && player->wipeoutslow) { if (player->offroad) player->mo->friction -= 4912; if (player->wipeoutslow == 1) player->mo->friction -= 9824; } } void K_SetTireGrease(player_t *player, tic_t tics) { if (player->pogospring > 0) return; player->tiregrease = tics; } void K_SetItemOut(player_t *player) { player->itemflags |= IF_ITEMOUT; } void K_UnsetItemOut(player_t *player) { player->itemflags &= ~IF_ITEMOUT; player->bananadrag = 0; } // // K_MoveKartPlayer // void K_MoveKartPlayer(player_t *player, boolean onground) { UINT16 buttons = K_GetKartButtons(player); boolean ATTACK_IS_DOWN = ((buttons & BT_ATTACK) && !(player->oldcmd.buttons & BT_ATTACK)); boolean HOLDING_ITEM = (player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)); boolean NO_HYUDORO = (player->stealingtimer == 0 && player->stolentimer == 0); if (!player->exiting) { if (player->oldposition < player->position) // But first, if you lost a place, { player->oldposition = player->position; // then the other player taunts. K_RegularVoiceTimers(player); // and you can't for a bit } else if (player->oldposition > player->position) // Otherwise, { K_PlayOvertakeSound(player->mo); // Say "YOU'RE TOO SLOW!" player->oldposition = player->position; // Restore the old position, } } if (player->positiondelay) player->positiondelay--; // Prevent ring misfire if (!(buttons & BT_ATTACK)) { if (player->itemtype == KITEM_NONE && NO_HYUDORO && (K_RingsActive() == true) && !(HOLDING_ITEM || player->itemamount || player->itemroulette || player->rocketsneakertimer || player->eggmanexplode || (player->growshrinktimer > 0) || player->flametimer)) player->itemflags |= IF_USERINGS; else player->itemflags &= ~IF_USERINGS; } if (player && player->mo && player->mo->health > 0 && !player->spectator && !P_PlayerInPain(player) && !(player->exiting || mapreset) && leveltime > introtime) { // First, the really specific, finicky items that function without the item being directly in your item slot. { // Karma item dropping if (ATTACK_IS_DOWN && player->karmamode && !player->karmadelay) { mobj_t *newitem; if (player->karmamode == 1) { newitem = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RANDOMITEM); newitem->threshold = 69; // selected "randomly". } else { newitem = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM); if (player->eggmanblame >= 0 && player->eggmanblame < MAXPLAYERS && playeringame[player->eggmanblame] && !players[player->eggmanblame].spectator && players[player->eggmanblame].mo) P_SetTarget(&newitem->target, players[player->eggmanblame].mo); player->eggmanblame = -1; } newitem->flags2 = (player->mo->flags2 & MF2_OBJECTFLIP); newitem->fuse = 15*TICRATE; // selected randomly. player->karmamode = 0; player->karmadelay = comebacktime; S_StartSound(player->mo, sfx_s254); } // Ring boosting else if (player->itemflags & IF_USERINGS) { if ((buttons & BT_ATTACK) && !player->ringdelay && player->rings > 0) { mobj_t *ring = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RING); P_SetMobjState(ring, S_FASTRING1); if (P_IsDisplayPlayer(player)) { UINT8 startfade = 220; UINT8 transfactor = 10 * (min(startfade, player->ringtransparency)) / startfade; if (transfactor < 10) { transfactor = max(transfactor, 4); ring->renderflags |= ((10-transfactor) << RF_TRANSSHIFT); ring->renderflags |= RF_ADD; } } player->ringtransparency -= RINGTRANSPARENCYUSEPENALTY; ring->extravalue1 = 1; // Ring use animation timer ring->extravalue2 = 1; // Ring use animation flag ring->shadowscale = 0; P_SetTarget(&ring->target, player->mo); // user player->rings--; player->ringdelay = 3; } } // Other items else { // Eggman Monitor exploding if (player->eggmanexplode) { if (ATTACK_IS_DOWN && player->eggmanexplode <= 3*TICRATE && player->eggmanexplode > 1) { player->eggmanexplode = 1; player->botvars.itemconfirm = 0; } } // Eggman Monitor throwing else if (player->itemflags & IF_EGGMANOUT) { if (ATTACK_IS_DOWN) { K_ThrowKartItem(player, false, MT_EGGMANITEM, -1, 0); K_PlayAttackTaunt(player->mo); player->itemflags &= ~IF_EGGMANOUT; K_UpdateHnextList(player, true); player->botvars.itemconfirm = 0; } } // Rocket Sneaker usage else if (player->rocketsneakertimer > 1) { if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO) { K_DoSneaker(player, 2); K_PlayBoostTaunt(player->mo); if (player->rocketsneakertimer <= 3*TICRATE) player->rocketsneakertimer = 1; else player->rocketsneakertimer -= 3*TICRATE; player->botvars.itemconfirm = 0; } } // Flame Shield Usage else if (player->flametimer > 0) { if (!HOLDING_ITEM && NO_HYUDORO) { if (!ATTACK_IS_DOWN && player->flametimer > 0) { player->itemflags |= IF_HOLDREADY; } if ((buttons & BT_ATTACK) && (player->itemflags & IF_HOLDREADY) && onground) { // TODO: gametyperules const SINT8 incr = gametype == GT_BATTLE ? 3 : 2; const SINT8 metincr = gametype == GT_BATTLE ? 4 : 3; const SINT8 comincr = gametype == GT_BATTLE ? 8 : 4; if (player->flamestore == 0) { S_StartSound(player->mo, sfx_s3k48); K_PlayBoostTaunt(player->mo); } player->flamedash += incr; player->flamestore = min(player->flamestore + metincr, TICRATE*2); player->flametimer -= comincr; if (player->flametimer <= 0) { player->flametimer = 0; S_StartSound(player->mo, sfx_s3k47); K_DropHnextList(player, false); } /*if (!onground && (leveltime % 8)) { P_Thrust( player->mo, K_MomentumAngle(player->mo), mapobjectscale ); }*/ } } } // Grow Canceling else if (player->growshrinktimer > 0) { if (player->growcancel >= 0) { if (buttons & BT_ATTACK) { player->growcancel++; if (player->growcancel > 26) K_RemoveGrowShrink(player); } else player->growcancel = 0; } else { if ((buttons & BT_ATTACK) || (player->oldcmd.buttons & BT_ATTACK)) player->growcancel = -1; else player->growcancel = 0; } } else if (player->itemamount == 0) { K_UnsetItemOut(player); } else { switch (player->itemtype) { case KITEM_SNEAKER: if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO) { K_DoSneaker(player, 1); K_PlayBoostTaunt(player->mo); player->itemamount--; player->botvars.itemconfirm = 0; } break; case KITEM_ROCKETSNEAKER: if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO && player->rocketsneakertimer == 0) { INT32 moloop; mobj_t *mo = NULL; mobj_t *prev = player->mo; K_PlayBoostTaunt(player->mo); S_StartSound(player->mo, sfx_s3k3a); //K_DoSneaker(player, 2); player->rocketsneakertimer = (itemtime*3); player->itemamount--; K_UpdateHnextList(player, true); for (moloop = 0; moloop < 2; moloop++) { mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ROCKETSNEAKER); K_MatchGenericExtraFlags(mo, player->mo); mo->flags |= MF_NOCLIPTHING; mo->angle = player->mo->angle; mo->threshold = 10; mo->movecount = moloop%2; mo->movedir = mo->lastlook = moloop+1; P_SetTarget(&mo->target, player->mo); P_SetTarget(&mo->hprev, prev); P_SetTarget(&prev->hnext, mo); prev = mo; } player->botvars.itemconfirm = 0; } break; case KITEM_INVINCIBILITY: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage normally, so you're free to waste it if you have multiple { K_DoInvincibility(player, 10 * TICRATE); K_PlayPowerGloatSound(player->mo); player->itemamount--; player->botvars.itemconfirm = 0; } break; case KITEM_BANANA: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { INT32 moloop; mobj_t *mo; mobj_t *prev = player->mo; //K_PlayAttackTaunt(player->mo); K_SetItemOut(player); S_StartSound(player->mo, sfx_s254); for (moloop = 0; moloop < player->itemamount; moloop++) { mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BANANA_SHIELD); if (!mo) { player->itemamount = moloop; break; } mo->flags |= MF_NOCLIPTHING; mo->threshold = 10; mo->movecount = player->itemamount; mo->movedir = moloop+1; P_SetTarget(&mo->target, player->mo); P_SetTarget(&mo->hprev, prev); P_SetTarget(&prev->hnext, mo); prev = mo; } player->botvars.itemconfirm = 0; } else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Banana x3 thrown { player->itemamount--; K_ThrowKartItem(player, false, MT_BANANA, -1, 0); K_PlayAttackTaunt(player->mo); K_UpdateHnextList(player, false); player->botvars.itemconfirm = 0; } break; case KITEM_EGGMAN: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { mobj_t *mo; player->itemamount--; player->itemflags |= IF_EGGMANOUT; S_StartSound(player->mo, sfx_s254); mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_SHIELD); if (mo) { K_FlipFromObject(mo, player->mo); mo->flags |= MF_NOCLIPTHING; mo->threshold = 10; mo->movecount = 1; mo->movedir = 1; P_SetTarget(&mo->target, player->mo); P_SetTarget(&player->mo->hnext, mo); } player->botvars.itemconfirm = 0; } break; case KITEM_ORBINAUT: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { angle_t newangle; INT32 moloop; mobj_t *mo = NULL; mobj_t *prev = player->mo; //K_PlayAttackTaunt(player->mo); K_SetItemOut(player); S_StartSound(player->mo, sfx_s3k3a); for (moloop = 0; moloop < player->itemamount; moloop++) { newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ORBINAUT_SHIELD); if (!mo) { player->itemamount = moloop; break; } mo->flags |= MF_NOCLIPTHING; mo->angle = newangle; mo->threshold = 10; mo->movecount = player->itemamount; mo->movedir = mo->lastlook = moloop+1; mo->color = player->skincolor; P_SetTarget(&mo->target, player->mo); P_SetTarget(&mo->hprev, prev); P_SetTarget(&prev->hnext, mo); prev = mo; } player->botvars.itemconfirm = 0; } else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Orbinaut x3 thrown { player->itemamount--; K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0); K_PlayAttackTaunt(player->mo); K_UpdateHnextList(player, false); player->botvars.itemconfirm = 0; } break; case KITEM_JAWZ: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { angle_t newangle; INT32 moloop; mobj_t *mo = NULL; mobj_t *prev = player->mo; //K_PlayAttackTaunt(player->mo); K_SetItemOut(player); S_StartSound(player->mo, sfx_s3k3a); for (moloop = 0; moloop < player->itemamount; moloop++) { newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_JAWZ_SHIELD); if (!mo) { player->itemamount = moloop; break; } mo->flags |= MF_NOCLIPTHING; mo->angle = newangle; mo->threshold = 10; mo->movecount = player->itemamount; mo->movedir = mo->lastlook = moloop+1; P_SetTarget(&mo->target, player->mo); P_SetTarget(&mo->hprev, prev); P_SetTarget(&prev->hnext, mo); prev = mo; } player->botvars.itemconfirm = 0; } else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Jawz thrown { player->itemamount--; if (player->throwdir == 1 || player->throwdir == 0) K_ThrowKartItem(player, true, MT_JAWZ, 1, 0); else if (player->throwdir == -1) // Throwing backward gives you a dud that doesn't home in K_ThrowKartItem(player, true, MT_JAWZ_DUD, -1, 0); K_PlayAttackTaunt(player->mo); K_UpdateHnextList(player, false); player->botvars.itemconfirm = 0; } break; case KITEM_MINE: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { mobj_t *mo; K_SetItemOut(player); S_StartSound(player->mo, sfx_s254); mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SSMINE_SHIELD); if (mo) { mo->flags |= MF_NOCLIPTHING; mo->threshold = 10; mo->movecount = 1; mo->movedir = 1; P_SetTarget(&mo->target, player->mo); P_SetTarget(&player->mo->hnext, mo); } player->botvars.itemconfirm = 0; } else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) { player->itemamount--; K_ThrowKartItem(player, false, MT_SSMINE, 1, 1); K_PlayAttackTaunt(player->mo); player->itemflags &= ~IF_ITEMOUT; K_UpdateHnextList(player, true); player->botvars.itemconfirm = 0; } break; case KITEM_LANDMINE: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { player->itemamount--; K_ThrowLandMine(player); K_PlayAttackTaunt(player->mo); player->botvars.itemconfirm = 0; } break; case KITEM_DROPTARGET: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { mobj_t *mo; K_SetItemOut(player); S_StartSound(player->mo, sfx_s254); mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_DROPTARGET_SHIELD); if (mo) { mo->flags |= MF_NOCLIPTHING; mo->threshold = 10; mo->movecount = 1; mo->movedir = 1; P_SetTarget(&mo->target, player->mo); P_SetTarget(&player->mo->hnext, mo); } player->botvars.itemconfirm = 0; } else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) { player->itemamount--; K_ThrowKartItem(player, (player->throwdir > 0), MT_DROPTARGET, -1, 0); K_PlayAttackTaunt(player->mo); player->itemflags &= ~IF_ITEMOUT; K_UpdateHnextList(player, true); player->botvars.itemconfirm = 0; } break; case KITEM_BALLHOG: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { player->itemamount--; K_ThrowKartItem(player, true, MT_BALLHOG, 1, 0); K_PlayAttackTaunt(player->mo); player->botvars.itemconfirm = 0; } break; case KITEM_SPB: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { player->itemamount--; K_ThrowKartItem(player, true, MT_SPB, 1, 0); K_PlayAttackTaunt(player->mo); player->botvars.itemconfirm = 0; } break; case KITEM_GROW: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { player->itemamount--; if (player->growshrinktimer < 0) { // If you're shrunk, then "grow" will just make you normal again. K_RemoveGrowShrink(player); } else { K_PlayPowerGloatSound(player->mo); player->mo->scalespeed = mapobjectscale/TICRATE; player->mo->destscale = FixedMul(mapobjectscale, GROW_SCALE); if (K_PlayerShrinkCheat(player) == true) { player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE); } // TODO: gametyperules player->growshrinktimer = (gametype == GT_BATTLE ? 8 : 12) * TICRATE; if (player->invincibilitytimer > 0) { ; // invincibility has priority in P_RestoreMusic, no point in starting here } else if (P_IsLocalPlayer(player) == true) { S_ChangeMusicSpecial("kgrow"); } else //used to be "if (P_IsDisplayPlayer(player) == false)" { S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow)); } P_RestoreMusic(player); S_StartSound(player->mo, sfx_kc5a); } player->botvars.itemconfirm = 0; } break; case KITEM_SHRINK: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { K_DoShrink(player); player->itemamount--; K_PlayPowerGloatSound(player->mo); player->botvars.itemconfirm = 0; } break; case KITEM_THUNDERSHIELD: if (player->curshield != KSHIELD_THUNDER) { mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THUNDERSHIELD); P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); P_SetTarget(&shield->target, player->mo); S_StartSound(player->mo, sfx_s3k41); player->curshield = KSHIELD_THUNDER; } if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { K_DoThunderShield(player); if (player->itemamount > 0) { // Why is this a conditional? // Thunder shield: the only item that allows you to // activate a mine while you're out of its radius, // the SAME tic it sets your itemamount to 0 // ...:dumbestass: player->itemamount--; K_PlayAttackTaunt(player->mo); player->botvars.itemconfirm = 0; } } break; case KITEM_BUBBLESHIELD: if (player->curshield != KSHIELD_BUBBLE) { mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BUBBLESHIELD); P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); P_SetTarget(&shield->target, player->mo); S_StartSound(player->mo, sfx_s3k3f); player->curshield = KSHIELD_BUBBLE; } if (!HOLDING_ITEM && NO_HYUDORO) { if ((buttons & BT_ATTACK) && (player->itemflags & IF_HOLDREADY)) { if (player->bubbleblowup == 0) S_StartSound(player->mo, sfx_s3k75); player->bubbleblowup++; player->bubblecool = player->bubbleblowup*4; if (player->bubbleblowup > bubbletime*2) { player->itemamount--; K_ThrowKartItem(player, (player->throwdir > 0), MT_BUBBLESHIELDTRAP, -1, 0); K_PlayAttackTaunt(player->mo); player->bubbleblowup = 0; player->bubblecool = 0; player->itemflags &= ~IF_HOLDREADY; player->botvars.itemconfirm = 0; } } else { if (player->bubbleblowup > bubbletime) player->bubbleblowup = bubbletime; if (player->bubbleblowup) player->bubbleblowup--; if (player->bubblecool) player->itemflags &= ~IF_HOLDREADY; else player->itemflags |= IF_HOLDREADY; } } break; case KITEM_FLAMESHIELD: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && player->flametimer == 0) { player->itemamount--; player->flametimer = (itemtime*3); mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_FLAMESHIELD); P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); P_SetTarget(&shield->target, player->mo); S_StartSound(player->mo, sfx_s3k3e); player->curshield = KSHIELD_FLAME; } break; case KITEM_HYUDORO: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { player->itemamount--; K_DoHyudoroSteal(player); // yes. yes they do. K_PlayAttackTaunt(player->mo); player->botvars.itemconfirm = 0; } break; case KITEM_POGOSPRING: if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO && player->pogospring == 0) { player->itemamount--; K_PlayBoostTaunt(player->mo); K_DoPogoSpring(player->mo, 32<pogospring = 1; player->botvars.itemconfirm = 0; } break; case KITEM_SUPERRING: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { SINT8 awardamount = 15; player->itemamount--; if (player->position == 1) { awardamount = 5; } else if(!K_IsPlayerLosing(player)) { awardamount = 10; } K_AwardPlayerRings(player, awardamount, true); player->botvars.itemconfirm = 0; } break; case KITEM_KITCHENSINK: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { mobj_t *mo; K_SetItemOut(player); S_StartSound(player->mo, sfx_s254); mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SINK_SHIELD); if (mo) { mo->flags |= MF_NOCLIPTHING; mo->threshold = 10; mo->movecount = 1; mo->movedir = 1; P_SetTarget(&mo->target, player->mo); P_SetTarget(&player->mo->hnext, mo); } player->botvars.itemconfirm = 0; } else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Sink thrown { player->itemamount--; K_ThrowKartItem(player, false, MT_SINK, 1, 2); K_PlayAttackTaunt(player->mo); player->itemflags &= ~IF_ITEMOUT; K_UpdateHnextList(player, true); player->botvars.itemconfirm = 0; } break; case KITEM_SAD: if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && !player->sadtimer) { player->sadtimer = stealtime; player->itemamount--; player->botvars.itemconfirm = 0; } break; default: break; } } } } } // No more! if (!player->itemamount) { player->itemflags &= ~IF_ITEMOUT; player->itemtype = KITEM_NONE; } if (K_GetShieldFromPlayer(player) == KSHIELD_NONE) { player->curshield = KSHIELD_NONE; // RESET shield type player->bubbleblowup = 0; player->bubblecool = 0; //player->flamedash = 0; player->flametimer = 0; } if (player->growshrinktimer <= 0) player->growcancel = -1; if (player->itemtype == KITEM_SPB || player->itemtype == KITEM_SHRINK || player->growshrinktimer < 0) indirectitemcooldown = 20*TICRATE; if (player->hyudorotimer > 0) { INT32 hyu = hyudorotime; if (leveltime & 1) { player->mo->renderflags |= RF_DONTDRAW; } else { if (player->hyudorotimer >= (TICRATE/2) && player->hyudorotimer <= hyu-(TICRATE/2)) player->mo->renderflags &= ~K_GetPlayerDontDrawFlag(player); else player->mo->renderflags &= ~RF_DONTDRAW; } player->flashing = player->hyudorotimer; // We'll do this for now, let's people know about the invisible people through subtle hints } else if (player->hyudorotimer == 0) { player->mo->renderflags &= ~RF_DONTDRAW; } if (gametype == GT_BATTLE && player->bumper <= 0) // dead in match? you da bomb { K_DropItems(player); //K_StripItems(player); K_StripOther(player); player->mo->renderflags |= RF_GHOSTLY; player->flashing = player->karmadelay; } else if (gametype == GT_RACE || player->bumper > 0) { player->mo->renderflags &= ~(RF_TRANSMASK|RF_BRIGHTMASK); } K_AdjustPlayerFriction(player); K_KartDrift(player, onground); K_KartSlipdash(player, onground); // Quick Turning // You can't turn your kart when you're not moving. // So now it's time to burn some rubber! if (player->speed < 2 && leveltime > starttime && player->cmd.buttons & BT_ACCELERATE && player->cmd.buttons & BT_BRAKE && player->cmd.turning != 0) { if (leveltime % 8 == 0) S_StartSound(player->mo, sfx_s224); } if (onground == false) { K_AirFailsafe(player); } else { player->pflags &= ~PF_AIRFAILSAFE; } } void K_CheckSpectateStatus(boolean considermapreset) { UINT8 respawnlist[MAXPLAYERS]; UINT8 i, j, numingame = 0, numjoiners = 0; UINT8 numhumans = 0, numbots = 0; // Maintain spectate wait timer for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) { continue; } if (!players[i].spectator) { numingame++; if (players[i].bot) { numbots++; } else { numhumans++; } players[i].spectatewait = 0; players[i].spectatorreentry = 0; continue; } if ((players[i].pflags & PF_WANTSTOJOIN)) { players[i].spectatewait++; } else { players[i].spectatewait = 0; } if (gamestate != GS_LEVEL || considermapreset == false) { players[i].spectatorreentry = 0; } else if (players[i].spectatorreentry > 0) { players[i].spectatorreentry--; } } // No one's allowed to join if (!cv_allowteamchange.value) return; // DON'T allow if you've hit the in-game player cap if (cv_ingamecap.value && numhumans >= cv_ingamecap.value) return; // Get the number of players in game, and the players to be de-spectated. for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) continue; if (!players[i].spectator) { // Allow if you're not in a level if (gamestate != GS_LEVEL) continue; // DON'T allow if anyone's exiting if (players[i].exiting) return; // Allow if the match hasn't started yet if (numingame < 2 || leveltime < starttime || mapreset) continue; // DON'T allow if the match is 20 seconds in if (leveltime > (starttime + 20*TICRATE)) return; // DON'T allow if the race is at 2 laps if ((gametyperules & GTR_CIRCUIT) && players[i].laps >= 2) return; continue; } if (players[i].bot) { // Spectating bots are controlled by other mechanisms. continue; } if (!(players[i].pflags & PF_WANTSTOJOIN)) { // This spectator does not want to join. continue; } if (netgame && numingame > 0 && players[i].spectatorreentry > 0) { // This person has their reentry cooldown active. continue; } respawnlist[numjoiners++] = i; } // Literally zero point in going any further if nobody is joining. if (!numjoiners) return; // Organize by spectate wait timer (if there's more than one to sort) if (cv_maxplayers.value && numjoiners > 1) { UINT8 oldrespawnlist[MAXPLAYERS]; memcpy(oldrespawnlist, respawnlist, numjoiners); for (i = 0; i < numjoiners; i++) { UINT8 pos = 0; INT32 ispecwait = players[oldrespawnlist[i]].spectatewait; for (j = 0; j < numjoiners; j++) { INT32 jspecwait = players[oldrespawnlist[j]].spectatewait; if (j == i) continue; if (jspecwait > ispecwait) pos++; else if (jspecwait == ispecwait && j < i) pos++; } respawnlist[pos] = oldrespawnlist[i]; } } const UINT8 previngame = numingame; INT16 removeBotID = MAXPLAYERS - 1; // Finally, we can de-spectate everyone! for (i = 0; i < numjoiners; i++) { // Hit the in-game player cap while adding people? if (cv_maxplayers.value && numingame >= cv_maxplayers.value) { if (numbots > 0) { // Find a bot to kill to make room while (removeBotID >= 0) { if (playeringame[removeBotID] && players[removeBotID].bot) { //CONS_Printf("bot %s kicked to make room on tic %d\n", player_names[removeBotID], leveltime); CL_RemovePlayer(removeBotID, KR_LEAVE); numbots--; numingame--; break; } removeBotID--; } if (removeBotID < 0) { break; } } else { break; } } //CONS_Printf("player %s is joining on tic %d\n", player_names[respawnlist[i]], leveltime); P_SpectatorJoinGame(&players[respawnlist[i]]); numhumans++; numingame++; } if (considermapreset == false) return; // Reset the match when 2P joins 1P, DUEL mode // Reset the match when 3P joins 1P and 2P, DUEL mode must be disabled if (!mapreset && gamestate == GS_LEVEL && (previngame < 2 && numingame >= 2)) { S_ChangeMusicInternal("chalng", false); // COME ON mapreset = 3*TICRATE; // Even though only the server uses this for game logic, set for everyone for HUD } } UINT8 K_GetInvincibilityItemFrame(void) { return ((leveltime % (7*3)) / 3); } UINT8 K_GetMultItemFrame(UINT8 count, UINT8 max) { return min(count - 1, max); } boolean K_IsSPBInGame(void) { UINT8 i; thinker_t *think; // is there an SPB chasing anyone? if (spbplace != -1) return true; // do any players have an SPB in their item slot? for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; if (players[i].itemtype == KITEM_SPB) return true; } // spbplace is still -1 until a fired SPB finds a target, so look for an in-map SPB just in case for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next) { if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) continue; if (((mobj_t *)think)->type == MT_SPB) return true; } return false; } boolean K_RingsActive(void) { if (!(gametyperules & GTR_RINGS)) { // No Rings in this gametype. return false; } if (!ringsactive) { // Rings aren't enabled. return false; } return true; } boolean K_StackingActive(void) { if (stackingactive) { // Booststacking is enabled! return true; } return false; } boolean K_ChainingActive(void) { if (chainingactive) { // Boostchaining is enabled! return true; } return false; } boolean K_UsingLegacyCheckpoints(void) { if (numbosswaypoints > 0) { // We are using Kart V1 waypoints! return true; } return false; } void K_UpdateMobjItemOverlay(mobj_t *part, SINT8 itemType, UINT8 itemCount) { switch (itemType) { case KITEM_SNEAKER: part->sprite = SPR_ITSN; part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetMultItemFrame(itemCount, 2); break; case KITEM_ORBINAUT: part->sprite = SPR_ITMO; part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetMultItemFrame(itemCount, 3); break; case KITEM_BANANA: part->sprite = SPR_ITBA; part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetMultItemFrame(itemCount, 3); break; case KITEM_JAWZ: part->sprite = SPR_ITJA; part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetMultItemFrame(itemCount, 1); break; case KITEM_INVINCIBILITY: part->sprite = SPR_ITMI; part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); break; case KITEM_SAD: part->sprite = SPR_ITEM; part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; break; default: part->sprite = SPR_ITEM; part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(itemType); break; } } //}