blankart/src/k_kart.c
Sally Coolatta 6e8e0ad0f5 Lightning Shield fixs
- Rename to Lightning Shield (it keeps fucking me up when I want to kartgiveitem it to myself)
- Fix inflictors causing damage being considered damage hitlag. Fixes the player using Lightning Shield being in damage hitlag state (most notable in the DI branch because hurting someone with it lets you DI afterwards, which also fucks with your turning)
- Rewrote attack function to use custom blockmap search instead of P_NukeEnemies.
- It can no longer attack below you, only above & around you.
- Increased the attack radius, so that it actually lines up with the sprites...
- Fixed extremely inconsistent ring blockmap search by moving it to playerafterthink (don't ask me why)
- Rings get joulsted by Lightning Shield when you pass by them without
- Added MAXRADIUS to the blockmap checks for lightning shield & mines, to reduce blockmap inconsistencies
2022-05-20 18:12:34 -04:00

10396 lines
268 KiB
C

// SONIC ROBO BLAST 2 KART ~ ZarroTsu
//-----------------------------------------------------------------------------
/// \file k_kart.c
/// \brief SRB2kart general.
/// All of the SRB2kart-unique stuff.
#include "k_kart.h"
#include "k_battle.h"
#include "k_boss.h"
#include "k_pwrlv.h"
#include "k_color.h"
#include "k_respawn.h"
#include "doomdef.h"
#include "hu_stuff.h"
#include "g_game.h"
#include "m_random.h"
#include "p_local.h"
#include "p_slopes.h"
#include "p_setup.h"
#include "r_draw.h"
#include "r_local.h"
#include "s_sound.h"
#include "st_stuff.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_waypoint.h"
#include "k_bot.h"
#include "k_hud.h"
#include "k_terrain.h"
#include "k_director.h"
#include "k_collide.h"
// 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_TimerReset(void)
{
starttime = introtime = 3;
numbulbs = 1;
}
void K_TimerInit(void)
{
UINT8 i;
UINT8 numPlayers = 0;//, numspec = 0;
if (!bossinfo.boss)
{
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
{
continue;
}
if (players[i].spectator == true)
{
//numspec++;
continue;
}
numPlayers++;
}
if (numPlayers >= 2)
{
rainbowstartavailable = true;
}
else
{
rainbowstartavailable = false;
}
// No intro in Record Attack / 1v1
// Leave unset for the value in K_TimerReset
if (numPlayers > 2)
{
introtime = (108) + 5; // 108 for rotation, + 5 for white fade
}
numbulbs = 5;
if (numPlayers > 2)
{
numbulbs += (numPlayers-2);
}
starttime = (introtime + (3*TICRATE)) + ((2*TICRATE) + (numbulbs * bulbtime)); // Start countdown time, + buffer time
}
// NOW you can try to spawn in the Battle capsules, if there's not enough players for a match
K_BattleInit();
//CONS_Printf("numbulbs set to %d (%d players, %d spectators) on tic %d\n", numbulbs, numPlayers, numspec, leveltime);
}
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;
}
player_t *K_GetItemBoxPlayer(mobj_t *mobj)
{
fixed_t closest = INT32_MAX;
player_t *player = NULL;
UINT8 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!(playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo) && !players[i].spectator))
{
continue;
}
// Always use normal item box rules -- could pass in "2" for fakes but they blend in better like this
if (P_CanPickupItem(&players[i], 1))
{
fixed_t dist = P_AproxDistance(P_AproxDistance(
players[i].mo->x - mobj->x,
players[i].mo->y - mobj->y),
players[i].mo->z - mobj->z
);
if (dist > 8192*mobj->scale)
{
continue;
}
if (dist < closest)
{
player = &players[i];
closest = dist;
}
}
}
return player;
}
// 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_landmine);
CV_RegisterVar(&cv_droptarget);
CV_RegisterVar(&cv_ballhog);
CV_RegisterVar(&cv_selfpropelledbomb);
CV_RegisterVar(&cv_grow);
CV_RegisterVar(&cv_shrink);
CV_RegisterVar(&cv_lightningshield);
CV_RegisterVar(&cv_bubbleshield);
CV_RegisterVar(&cv_flameshield);
CV_RegisterVar(&cv_hyudoro);
CV_RegisterVar(&cv_pogospring);
CV_RegisterVar(&cv_superring);
CV_RegisterVar(&cv_kitchensink);
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_kartbumpers);
CV_RegisterVar(&cv_kartfrantic);
CV_RegisterVar(&cv_kartcomeback);
CV_RegisterVar(&cv_kartencore);
CV_RegisterVar(&cv_kartvoterulechanges);
CV_RegisterVar(&cv_kartspeedometer);
CV_RegisterVar(&cv_kartvoices);
CV_RegisterVar(&cv_kartbot);
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_kartdebugbotpredict);
CV_RegisterVar(&cv_kartdebugcheckpoint);
CV_RegisterVar(&cv_kartdebugnodes);
CV_RegisterVar(&cv_kartdebugcolorize);
CV_RegisterVar(&cv_kartdebugdirector);
}
//}
boolean K_IsPlayerLosing(player_t *player)
{
INT32 winningpos = 1;
UINT8 i, pcount = 0;
if (battlecapsules && player->bumpers <= 0)
return true; // DNF in break the capsules
if (bossinfo.boss)
return (player->bumpers <= 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_landmine,
&cv_droptarget,
&cv_ballhog,
&cv_selfpropelledbomb,
&cv_grow,
&cv_shrink,
&cv_lightningshield,
&cv_bubbleshield,
&cv_flameshield,
&cv_hyudoro,
&cv_pogospring,
&cv_superring,
&cv_kitchensink,
&cv_dualsneaker,
&cv_triplesneaker,
&cv_triplebanana,
&cv_decabanana,
&cv_tripleorbinaut,
&cv_quadorbinaut,
&cv_dualjawz
};
#define NUMKARTODDS 80
// Less ugly 2D arrays
static INT32 K_KartItemOddsRace[NUMKARTRESULTS-1][8] =
{
//P-Odds 0 1 2 3 4 5 6 7
/*Sneaker*/ { 0, 0, 2, 4, 6, 0, 0, 0 }, // Sneaker
/*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 2, 4, 6 }, // Rocket Sneaker
/*Invincibility*/ { 0, 0, 0, 0, 2, 4, 6, 9 }, // Invincibility
/*Banana*/ { 4, 3, 1, 0, 0, 0, 0, 0 }, // Banana
/*Eggman Monitor*/ { 1, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor
/*Orbinaut*/ { 5, 4, 2, 2, 0, 0, 0, 0 }, // Orbinaut
/*Jawz*/ { 0, 3, 2, 1, 1, 0, 0, 0 }, // Jawz
/*Mine*/ { 0, 2, 3, 1, 0, 0, 0, 0 }, // Mine
/*Land Mine*/ { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine
/*Ballhog*/ { 0, 0, 2, 1, 0, 0, 0, 0 }, // Ballhog
/*Self-Propelled Bomb*/ { 0, 0, 0, 0, 0, 2, 4, 0 }, // Self-Propelled Bomb
/*Grow*/ { 0, 0, 0, 1, 2, 3, 0, 0 }, // Grow
/*Shrink*/ { 0, 0, 0, 0, 0, 0, 2, 0 }, // Shrink
/*Lightning Shield*/ { 1, 2, 0, 0, 0, 0, 0, 0 }, // Lightning Shield
/*Bubble Shield*/ { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield
/*Flame Shield*/ { 0, 0, 0, 0, 0, 1, 3, 5 }, // Flame Shield
/*Hyudoro*/ { 0, 0, 0, 1, 1, 0, 0, 0 }, // Hyudoro
/*Pogo Spring*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring
/*Super Ring*/ { 2, 1, 1, 0, 0, 0, 0, 0 }, // Super Ring
/*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink
/*Drop Target*/ { 4, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target
/*Sneaker x2*/ { 0, 0, 2, 2, 1, 0, 0, 0 }, // Sneaker x2
/*Sneaker x3*/ { 0, 0, 0, 2, 6,10, 5, 0 }, // Sneaker x3
/*Banana x3*/ { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3
/*Banana x10*/ { 0, 0, 0, 1, 0, 0, 0, 0 }, // Banana x10
/*Orbinaut x3*/ { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3
/*Orbinaut x4*/ { 0, 0, 0, 1, 1, 0, 0, 0 }, // Orbinaut x4
/*Jawz x2*/ { 0, 0, 1, 2, 0, 0, 0, 0 } // Jawz x2
};
static INT32 K_KartItemOddsBattle[NUMKARTRESULTS][2] =
{
//P-Odds 0 1
/*Sneaker*/ { 2, 1 }, // Sneaker
/*Rocket Sneaker*/ { 0, 0 }, // Rocket Sneaker
/*Invincibility*/ { 2, 1 }, // Invincibility
/*Banana*/ { 1, 0 }, // Banana
/*Eggman Monitor*/ { 1, 0 }, // Eggman Monitor
/*Orbinaut*/ { 8, 0 }, // Orbinaut
/*Jawz*/ { 8, 1 }, // Jawz
/*Mine*/ { 6, 1 }, // Mine
/*Land Mine*/ { 0, 0 }, // Land Mine
/*Ballhog*/ { 2, 1 }, // Ballhog
/*Self-Propelled Bomb*/ { 0, 0 }, // Self-Propelled Bomb
/*Grow*/ { 2, 1 }, // Grow
/*Shrink*/ { 0, 0 }, // Shrink
/*Lightning Shield*/ { 4, 0 }, // Lightning Shield
/*Bubble Shield*/ { 1, 0 }, // Bubble Shield
/*Flame Shield*/ { 0, 0 }, // Flame Shield
/*Hyudoro*/ { 2, 0 }, // Hyudoro
/*Pogo Spring*/ { 2, 0 }, // Pogo Spring
/*Super Ring*/ { 0, 0 }, // Super Ring
/*Kitchen Sink*/ { 0, 0 }, // Kitchen Sink
/*Drop Target*/ { 0, 0 }, // Drop Target
/*Sneaker x2*/ { 0, 0 }, // Sneaker x2
/*Sneaker x3*/ { 1, 1 }, // Sneaker x3
/*Banana x3*/ { 1, 0 }, // Banana x3
/*Banana x10*/ { 1, 1 }, // Banana x10
/*Orbinaut x3*/ { 2, 0 }, // Orbinaut x3
/*Orbinaut x4*/ { 1, 1 }, // Orbinaut x4
/*Jawz x2*/ { 2, 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
// Array of states to pick the starting point of the animation, based on the actual time left for invincibility.
static INT32 K_SparkleTrailStartStates[KART_NUMINVSPARKLESANIM][2] = {
{S_KARTINVULN12, S_KARTINVULNB12},
{S_KARTINVULN11, S_KARTINVULNB11},
{S_KARTINVULN10, S_KARTINVULNB10},
{S_KARTINVULN9, S_KARTINVULNB9},
{S_KARTINVULN8, S_KARTINVULNB8},
{S_KARTINVULN7, S_KARTINVULNB7},
{S_KARTINVULN6, S_KARTINVULNB6},
{S_KARTINVULN5, S_KARTINVULNB5},
{S_KARTINVULN4, S_KARTINVULNB4},
{S_KARTINVULN3, S_KARTINVULNB3},
{S_KARTINVULN2, S_KARTINVULNB2},
{S_KARTINVULN1, S_KARTINVULNB1}
};
INT32 K_GetShieldFromItem(INT32 item)
{
switch (item)
{
case KITEM_LIGHTNINGSHIELD: return KSHIELD_LIGHTNING;
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 = 5*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 (gametype == GT_BATTLE)
{
I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table
newodds = K_KartItemOddsBattle[item-1][pos];
}
else
{
I_Assert(pos < 8); // Ditto
newodds = K_KartItemOddsRace[item-1][pos];
}
// 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].bumpers)
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
{
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:
case KITEM_SUPERRING:
notNearEnd = true;
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_LIGHTNINGSHIELD:
cooldownOnStart = true;
powerItem = true;
if (spbplace != -1)
newodds = 0;
break;
case KITEM_HYUDORO:
cooldownOnStart = true;
notNearEnd = 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;
}
//{ 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 (gametype == GT_BATTLE && 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 (gametype == GT_BATTLE) // 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);
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;
}
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].bumpers > bestbumper)
bestbumper = players[i].bumpers;
}
// No forced SPB in 1v1s, it has to be randomly rolled
if (pingame <= 2)
dontforcespb = true;
// This makes the roulette produce the random noises.
if ((player->itemroulette % 3) == 1 && P_IsDisplayPlayer(player) && !demo.freecam)
{
#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->pflags & (PF_ITEMOUT|PF_EGGMANOUT|PF_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 (cmd->buttons & BT_ATTACK)
player->pflags |= PF_ATTACKDOWN;
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
pdis = (3 * pdis) / 2;
spbrush = true;
}
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->karthud[khud_itemblink] = TICRATE;
//player->karthud[khud_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->karthud[khud_itemblink] = TICRATE;
player->karthud[khud_itemblinkmode] = 2;
player->itemroulette = 0;
player->roulettetype = 0;
if (P_IsDisplayPlayer(player) && !demo.freecam)
S_StartSound(NULL, sfx_dbgsal);
return;
}
// SPECIAL CASE No. 3:
// Record Attack / alone mashing behavior
if (modeattacking || pingame == 1)
{
if (gametype == GT_RACE)
{
if (mashed && (modeattacking || cv_superring.value)) // ANY mashed value? You get rings.
{
K_KartGetItemResult(player, KITEM_SUPERRING);
player->karthud[khud_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->karthud[khud_itemblinkmode] = 0;
if (P_IsDisplayPlayer(player))
S_StartSound(NULL, sfx_itrolf);
}
}
else if (gametype == GT_BATTLE)
{
if (mashed && (modeattacking || bossinfo.boss || cv_banana.value)) // ANY mashed value? You get a banana.
{
K_KartGetItemResult(player, KITEM_BANANA);
player->karthud[khud_itemblinkmode] = 1;
if (P_IsDisplayPlayer(player))
S_StartSound(NULL, sfx_itrolm);
}
else if (bossinfo.boss)
{
K_KartGetItemResult(player, KITEM_ORBINAUT);
player->karthud[khud_itemblinkmode] = 0;
if (P_IsDisplayPlayer(player))
S_StartSound(NULL, sfx_itrolf);
}
else
{
if (modeattacking || cv_tripleorbinaut.value) // Waited patiently? You get Orbinaut x3!
K_KartGetItemResult(player, KRITEM_TRIPLEORBINAUT);
else // Default to sad if nothing's enabled...
K_KartGetItemResult(player, KITEM_SAD);
player->karthud[khud_itemblinkmode] = 0;
if (P_IsDisplayPlayer(player))
S_StartSound(NULL, sfx_itrolf);
}
}
player->karthud[khud_itemblink] = TICRATE;
player->itemroulette = 0;
player->roulettetype = 0;
return;
}
// SPECIAL CASE No. 4:
// Being in ring debt occasionally forces Super Ring on you if you mashed
if (!(gametyperules & GTR_SPHERES) && 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->karthud[khud_itemblink] = TICRATE;
player->karthud[khud_itemblinkmode] = 1;
player->itemroulette = 0;
player->roulettetype = 0;
if (P_IsDisplayPlayer(player))
S_StartSound(NULL, sfx_itrolm);
return;
}
}
// SPECIAL CASE No. 5:
// Force SPB onto 2nd if they get too far behind
if ((gametyperules & GTR_CIRCUIT) && player->position == 2 && pdis > SPBFORCEDIST
&& spbplace == -1 && !indirectitemcooldown && !dontforcespb
&& cv_selfpropelledbomb.value)
{
K_KartGetItemResult(player, KITEM_SPB);
player->karthud[khud_itemblink] = TICRATE;
player->karthud[khud_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
useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush);
for (i = 1; i < NUMKARTRESULTS; i++)
{
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->karthud[khud_itemblink] = TICRATE;
player->karthud[khud_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
{
weight = (mobj->player->kartweight) * FRACUNIT;
if (mobj->player->speed > K_GetKartSpeed(mobj->player, false))
weight += (mobj->player->speed - K_GetKartSpeed(mobj->player, false))/8;
if (mobj->player->itemtype == KITEM_BUBBLESHIELD)
weight += 9*FRACUNIT;
}
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 FixedMul(weight, mobj->scale);
}
static void K_SpawnBumpForObjs(mobj_t *mobj1, mobj_t *mobj2)
{
mobj_t *fx = P_SpawnMobj(
mobj1->x/2 + mobj2->x/2,
mobj1->y/2 + mobj2->y/2,
mobj1->z/2 + mobj2->z/2,
MT_BUMP
);
fixed_t avgScale = (mobj1->scale + mobj2->scale) / 2;
if (mobj1->eflags & MFE_VERTICALFLIP)
{
fx->eflags |= MFE_VERTICALFLIP;
}
else
{
fx->eflags &= ~MFE_VERTICALFLIP;
}
P_SetScale(fx, (fx->destscale = avgScale));
if ((mobj1->player && mobj1->player->itemtype == KITEM_BUBBLESHIELD)
|| (mobj2->player && mobj2->player->itemtype == KITEM_BUBBLESHIELD))
{
S_StartSound(mobj1, sfx_s3k44);
}
else if (mobj1->type == MT_DROPTARGET || mobj1->type == MT_DROPTARGET_SHIELD) // no need to check the other way around
{
// Sound handled in K_DropTargetCollide
// S_StartSound(mobj2, sfx_s258);
fx->colorized = true;
fx->color = mobj1->color;
}
else
{
S_StartSound(mobj1, sfx_s3k49);
}
}
static void K_PlayerJustBumped(player_t *player)
{
mobj_t *playerMobj = NULL;
if (player == NULL)
{
return;
}
playerMobj = player->mo;
if (playerMobj == NULL || P_MobjWasRemoved(playerMobj))
{
return;
}
if (abs(player->rmomx) < playerMobj->scale && abs(player->rmomy) < playerMobj->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
player->rmomx = playerMobj->momx - player->cmomx;
player->rmomy = playerMobj->momy - player->cmomy;
}
player->justbumped = bumptime;
player->spindash = 0;
if (player->spinouttimer)
{
player->wipeoutslow = wipeoutslowtime+1;
player->spinouttimer = max(wipeoutslowtime+1, player->spinouttimer);
//player->spinouttype = KSPIN_WIPEOUT; // Enforce type
}
}
static fixed_t K_GetBounceForce(mobj_t *mobj1, mobj_t *mobj2, fixed_t distx, fixed_t disty)
{
const fixed_t forceMul = (4 * FRACUNIT) / 10; // Multiply by this value to make it feel like old bumps.
fixed_t momdifx, momdify;
fixed_t dot;
fixed_t force = 0;
momdifx = mobj1->momx - mobj2->momx;
momdify = mobj1->momy - mobj2->momy;
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 0;
}
{ // 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;
}
}
dot = FixedMul(momdifx, distx) + FixedMul(momdify, disty);
if (dot >= 0)
{
// They're moving away from each other
return 0;
}
// Return the push force!
force = FixedDiv(dot, FixedMul(distx, distx) + FixedMul(disty, disty));
return FixedMul(force, forceMul);
}
boolean K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2)
{
const fixed_t minBump = 25*mapobjectscale;
mobj_t *goombaBounce = NULL;
fixed_t distx, disty, dist;
fixed_t force;
fixed_t mass1, mass2;
if ((!mobj1 || P_MobjWasRemoved(mobj1))
|| (!mobj2 || P_MobjWasRemoved(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.state != RESPAWNST_NONE)
|| (mobj2->player && mobj2->player->respawn.state != RESPAWNST_NONE))
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;
}
}
// 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;
}
// Adds the OTHER object's momentum times a bunch, for the best chance of getting the correct direction
distx = (mobj1->x + mobj2->momx) - (mobj2->x + mobj1->momx);
disty = (mobj1->y + mobj2->momy) - (mobj2->y + mobj1->momy);
force = K_GetBounceForce(mobj1, mobj2, distx, disty);
if (force == 0)
{
return false;
}
mass1 = K_GetMobjWeight(mobj1, mobj2);
mass2 = K_GetMobjWeight(mobj2, mobj1);
if ((P_IsObjectOnGround(mobj1) && mobj2->momz < 0) // Grounded
|| (mass2 == 0 && mass1 > 0)) // The other party is immovable
{
goombaBounce = mobj2;
}
else if ((P_IsObjectOnGround(mobj2) && mobj1->momz < 0) // Grounded
|| (mass1 == 0 && mass2 > 0)) // The other party is immovable
{
goombaBounce = mobj1;
}
if (goombaBounce != NULL)
{
// Perform a Goomba Bounce by reversing your z momentum.
goombaBounce->momz = -goombaBounce->momz;
}
else
{
// Trade z momentum values.
fixed_t newz = mobj1->momz;
mobj1->momz = mobj2->momz;
mobj2->momz = newz;
}
// Multiply by force
distx = FixedMul(force, distx);
disty = FixedMul(force, disty);
dist = FixedHypot(distx, disty);
// if the speed difference is less than this let's assume they're going proportionately faster from each other
if (dist < minBump)
{
fixed_t normalisedx = FixedDiv(distx, dist);
fixed_t normalisedy = FixedDiv(disty, dist);
distx = FixedMul(minBump, normalisedx);
disty = FixedMul(minBump, normalisedy);
}
if (mass2 > 0)
{
mobj1->momx = mobj1->momx - FixedMul(FixedDiv(2*mass2, mass1 + mass2), distx);
mobj1->momy = mobj1->momy - FixedMul(FixedDiv(2*mass2, mass1 + mass2), disty);
}
if (mass1 > 0)
{
mobj2->momx = mobj2->momx - FixedMul(FixedDiv(2*mass1, mass1 + mass2), -distx);
mobj2->momy = mobj2->momy - FixedMul(FixedDiv(2*mass1, mass1 + mass2), -disty);
}
K_SpawnBumpForObjs(mobj1, mobj2);
K_PlayerJustBumped(mobj1->player);
K_PlayerJustBumped(mobj2->player);
return true;
}
// K_KartBouncing, but simplified to act like P_BouncePlayerMove
boolean K_KartSolidBounce(mobj_t *bounceMobj, mobj_t *solidMobj)
{
const fixed_t minBump = 25*mapobjectscale;
fixed_t distx, disty, dist;
fixed_t force;
if ((!bounceMobj || P_MobjWasRemoved(bounceMobj))
|| (!solidMobj || P_MobjWasRemoved(solidMobj)))
{
return false;
}
// Don't bump when you're being reborn
if (bounceMobj->player && bounceMobj->player->playerstate != PST_LIVE)
return false;
if (bounceMobj->player && bounceMobj->player->respawn.state != RESPAWNST_NONE)
return false;
// Don't bump if you've recently bumped
if (bounceMobj->player && bounceMobj->player->justbumped)
{
bounceMobj->player->justbumped = bumptime;
return false;
}
// Adds the OTHER object's momentum times a bunch, for the best chance of getting the correct direction
{
distx = (bounceMobj->x + solidMobj->momx) - (solidMobj->x + bounceMobj->momx);
disty = (bounceMobj->y + solidMobj->momy) - (solidMobj->y + bounceMobj->momy);
}
force = K_GetBounceForce(bounceMobj, solidMobj, distx, disty);
if (force == 0)
{
return false;
}
// Multiply by force
distx = FixedMul(force, distx);
disty = FixedMul(force, disty);
dist = FixedHypot(distx, disty);
{
// Normalize to the desired push value.
fixed_t normalisedx = FixedDiv(distx, dist);
fixed_t normalisedy = FixedDiv(disty, dist);
fixed_t bounceSpeed;
bounceSpeed = FixedHypot(bounceMobj->momx, bounceMobj->momy);
bounceSpeed = FixedMul(bounceSpeed, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
bounceSpeed += minBump;
distx = FixedMul(bounceSpeed, normalisedx);
disty = FixedMul(bounceSpeed, normalisedy);
}
bounceMobj->momx = bounceMobj->momx - distx;
bounceMobj->momy = bounceMobj->momy - disty;
bounceMobj->momz = -bounceMobj->momz;
K_SpawnBumpForObjs(bounceMobj, solidMobj);
K_PlayerJustBumped(bounceMobj->player);
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 UINT8 K_CheckOffroadCollide(mobj_t *mo)
{
// Check for sectors in touching_sectorlist
UINT8 i; // special type iter
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));
// If tiregrease is active, don't
if (mo->player && mo->player->tiregrease)
return 0;
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 & SF_FLIPSPECIAL_FLOOR) && mo->z == flr) // floor check
|| ((mo->eflags & MFE_VERTICALFLIP && (s->flags & SF_FLIPSPECIAL_CEILING) && (mo->z + mo->height) == cel)) ) // ceiling check.
for (i = 2; i < 5; i++) // check for sector special
if (GETSECSPECIAL(s->special, 1) == i)
return i-1; // return offroad type
// 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->flags & FF_EXISTS)) // This FOF doesn't exist anymore.
continue;
s2 = &sectors[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 & SF_FLIPSPECIAL_FLOOR) && mo->z == cel) // "floor" check
|| ((s2->flags & SF_FLIPSPECIAL_CEILING) && (mo->z + mo->height) == flr) ) // "ceiling" check.
for (i = 2; i < 5; i++) // check for sector special
if (GETSECSPECIAL(s2->special, 1) == i)
return i-1; // return offroad type
}
}
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;
if (terrain != NULL && terrain->offroad > 0)
{
offroadstrength = (terrain->offroad << FRACBITS);
}
else
{
offroadstrength = (K_CheckOffroadCollide(player->mo) << FRACBITS);
}
// If you are in offroad, a timer starts.
if (offroadstrength)
{
if (player->offroad < offroadstrength)
player->offroad += offroadstrength / TICRATE;
if (player->offroad > offroadstrength)
player->offroad = offroadstrength;
}
else
player->offroad = 0;
}
static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t curdist, fixed_t maxdist, boolean transparent)
{
#define CHAOTIXBANDLEN 15
#define CHAOTIXBANDCOLORS 9
static const UINT8 colors[CHAOTIXBANDCOLORS] = {
SKINCOLOR_SAPPHIRE,
SKINCOLOR_PLATINUM,
SKINCOLOR_TEA,
SKINCOLOR_GARDEN,
SKINCOLOR_BANANA,
SKINCOLOR_GOLD,
SKINCOLOR_ORANGE,
SKINCOLOR_SCARLET,
SKINCOLOR_TAFFY
};
fixed_t minimumdist = FixedMul(RING_DIST>>1, player->mo->scale);
UINT8 n = CHAOTIXBANDLEN;
UINT8 offset = ((leveltime / 3) % 3);
fixed_t stepx, stepy, stepz;
fixed_t curx, cury, curz;
UINT8 c;
if (maxdist == 0)
c = 0;
else
c = FixedMul(CHAOTIXBANDCOLORS<<FRACBITS, FixedDiv(curdist-minimumdist, maxdist-minimumdist)) >> FRACBITS;
stepx = (victim->mo->x - player->mo->x) / CHAOTIXBANDLEN;
stepy = (victim->mo->y - player->mo->y) / CHAOTIXBANDLEN;
stepz = ((victim->mo->z + (victim->mo->height / 2)) - (player->mo->z + (player->mo->height / 2))) / CHAOTIXBANDLEN;
curx = player->mo->x + stepx;
cury = player->mo->y + stepy;
curz = player->mo->z + stepz;
while (n)
{
if (offset == 0)
{
mobj_t *band = P_SpawnMobj(curx + (P_RandomRange(-12,12)*mapobjectscale),
cury + (P_RandomRange(-12,12)*mapobjectscale),
curz + (P_RandomRange(24,48)*mapobjectscale),
MT_SIGNSPARKLE);
P_SetMobjState(band, S_SIGNSPARK1 + (leveltime % 11));
P_SetScale(band, (band->destscale = (3*player->mo->scale)/2));
band->color = colors[c];
band->colorized = true;
band->fuse = 2;
if (transparent)
band->renderflags |= RF_GHOSTLY;
band->renderflags |= RF_DONTDRAW & ~(K_GetPlayerDontDrawFlag(player) | K_GetPlayerDontDrawFlag(victim));
}
curx += stepx;
cury += stepy;
curz += stepz;
offset = abs(offset-1) % 3;
n--;
}
#undef CHAOTIXBANDLEN
}
/** \brief Updates the player's drafting values once per frame
\param player player object passed from K_KartPlayerThink
\return void
*/
static void K_UpdateDraft(player_t *player)
{
fixed_t topspd = K_GetKartSpeed(player, false);
fixed_t draftdistance;
fixed_t minDist;
UINT8 leniency;
UINT8 i;
if (player->itemtype == KITEM_FLAMESHIELD)
{
// Flame Shield gets infinite draft distance as its passive effect.
draftdistance = 0;
}
else
{
// Distance you have to be to draft. If you're still accelerating, then this distance is lessened.
// This distance biases toward low weight! (min weight gets 4096 units, max weight gets 3072 units)
// This distance is also scaled based on game speed.
draftdistance = (3072 + (128 * (9 - player->kartweight))) * player->mo->scale;
if (player->speed < topspd)
draftdistance = FixedMul(draftdistance, FixedDiv(player->speed, topspd));
draftdistance = FixedMul(draftdistance, K_GetKartGameSpeedScalar(gamespeed));
}
minDist = 640 * player->mo->scale;
if (gametype == GT_BATTLE)
{
// TODO: gametyperules
minDist /= 4;
}
// On the contrary, the leniency period biases toward high weight.
// (See also: the leniency variable in K_SpawnDraftDust)
leniency = (3*TICRATE)/4 + ((player->kartweight-1) * (TICRATE/4));
// Not enough speed to draft.
if (player->speed >= 20*player->mo->scale)
{
//#define EASYDRAFTTEST
// Let's hunt for players to draft off of!
for (i = 0; i < MAXPLAYERS; i++)
{
fixed_t dist, olddraft;
#ifndef EASYDRAFTTEST
angle_t yourangle, theirangle, diff;
#endif
if (!playeringame[i] || players[i].spectator || !players[i].mo)
continue;
#ifndef EASYDRAFTTEST
// Don't draft on yourself :V
if (&players[i] == player)
continue;
#endif
// Not enough speed to draft off of.
if (players[i].speed < 20*players[i].mo->scale)
continue;
// No tethering off of the guy who got the starting bonus :P
if (players[i].startboost > 0)
continue;
#ifndef EASYDRAFTTEST
yourangle = K_MomentumAngle(player->mo);
theirangle = K_MomentumAngle(players[i].mo);
diff = R_PointToAngle2(player->mo->x, player->mo->y, players[i].mo->x, players[i].mo->y) - yourangle;
if (diff > ANGLE_180)
diff = InvAngle(diff);
// Not in front of this player.
if (diff > ANG10)
continue;
diff = yourangle - theirangle;
if (diff > ANGLE_180)
diff = InvAngle(diff);
// Not moving in the same direction.
if (diff > ANGLE_90)
continue;
#endif
dist = P_AproxDistance(P_AproxDistance(players[i].mo->x - player->mo->x, players[i].mo->y - player->mo->y), players[i].mo->z - player->mo->z);
#ifndef EASYDRAFTTEST
// TOO close to draft.
if (dist < minDist)
continue;
// Not close enough to draft.
if (dist > draftdistance && draftdistance > 0)
continue;
#endif
olddraft = player->draftpower;
player->draftleeway = leniency;
player->lastdraft = i;
// Draft power is used later in K_GetKartBoostPower, ranging from 0 for normal speed and FRACUNIT for max draft speed.
// How much this increments every tic biases toward acceleration! (min speed gets 1.5% per tic, max speed gets 0.5% per tic)
if (player->draftpower < FRACUNIT)
player->draftpower += (FRACUNIT/200) + ((9 - player->kartspeed) * ((3*FRACUNIT)/1600));
if (player->draftpower > FRACUNIT)
player->draftpower = FRACUNIT;
// Play draft finish noise
if (olddraft < FRACUNIT && player->draftpower >= FRACUNIT)
S_StartSound(player->mo, sfx_cdfm62);
// Spawn in the visual!
K_DrawDraftCombiring(player, &players[i], dist, draftdistance, false);
return; // Finished doing our draft.
}
}
// No one to draft off of? Then you can knock that off.
if (player->draftleeway) // Prevent small disruptions from stopping your draft.
{
player->draftleeway--;
if (player->lastdraft >= 0
&& player->lastdraft < MAXPLAYERS
&& playeringame[player->lastdraft]
&& !players[player->lastdraft].spectator
&& players[player->lastdraft].mo)
{
player_t *victim = &players[player->lastdraft];
fixed_t dist = P_AproxDistance(P_AproxDistance(victim->mo->x - player->mo->x, victim->mo->y - player->mo->y), victim->mo->z - player->mo->z);
K_DrawDraftCombiring(player, victim, dist, draftdistance, true);
}
}
else // Remove draft speed boost.
{
player->draftpower = 0;
player->lastdraft = -1;
}
}
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 && !player->spindash)
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);
P_InitAngle(dust, 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));
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;
}
static void
spawn_brake_dust
( mobj_t * master,
angle_t aoff,
fixed_t rad,
fixed_t scale)
{
const angle_t a = master->angle + aoff;
mobj_t *spark = P_SpawnMobjFromMobj(master,
P_ReturnThrustX(NULL, a, rad),
P_ReturnThrustY(NULL, a, rad), 0,
MT_BRAKEDUST);
spark->momx = master->momx;
spark->momy = master->momy;
spark->momz = P_GetMobjZMovement(master);
spark->angle = a - ANGLE_180;
spark->pitch = master->pitch;
spark->roll = master->roll;
P_Thrust(spark, a, 16 * spark->scale);
P_SetScale(spark, (spark->destscale =
FixedMul(scale, spark->scale)));
}
static void K_SpawnBrakeVisuals(player_t *player)
{
const fixed_t scale =
K_GetBrakeFXScale(player, 2*FRACUNIT);
if (leveltime & 1)
{
angle_t aoff;
fixed_t radf;
UINT8 wheel = 3;
if (player->drift)
{
/* brake-drifting: dust flies from outer wheel */
wheel ^= 1 << (player->drift < 0);
aoff = 7 * ANG10;
radf = 32 * FRACUNIT;
}
else
{
aoff = ANG30;
radf = 24 * FRACUNIT;
}
if (wheel & 1)
{
spawn_brake_dust(player->mo,
aoff, radf, scale);
}
if (wheel & 2)
{
spawn_brake_dust(player->mo,
InvAngle(aoff), radf, scale);
}
}
if (leveltime % 4 == 0)
S_StartSound(player->mo, sfx_s3k67);
/* vertical shaking, scales with speed */
player->mo->spriteyoffset = P_RandomFlip(2 * scale);
}
void K_SpawnDriftBoostClip(player_t *player)
{
mobj_t *clip;
fixed_t scale = 115*FRACUNIT/100;
fixed_t momz = P_GetMobjZMovement(player->mo);
fixed_t z;
if (( player->mo->eflags & MFE_VERTICALFLIP ))
z = player->mo->z;
else
z = player->mo->z + player->mo->height;
clip = P_SpawnMobj(player->mo->x, player->mo->y, z, MT_DRIFTCLIP);
P_SetTarget(&clip->target, player->mo);
P_SetScale(clip, ( clip->destscale = FixedMul(scale, player->mo->scale) ));
K_MatchGenericExtraFlags(clip, player->mo);
clip->fuse = 105;
clip->momz = 7 * P_MobjFlip(clip) * clip->scale;
if (momz > 0)
clip->momz += momz;
P_InstaThrust(clip, player->mo->angle +
P_RandomFlip(P_RandomRange(FRACUNIT/2, FRACUNIT)),
FixedMul(scale, player->speed));
}
void K_SpawnDriftBoostClipSpark(mobj_t *clip)
{
mobj_t *spark;
spark = P_SpawnMobj(clip->x, clip->y, clip->z, MT_DRIFTCLIPSPARK);
P_SetTarget(&spark->target, clip);
P_SetScale(spark, ( spark->destscale = clip->scale ));
K_MatchGenericExtraFlags(spark, clip);
spark->momx = clip->momx/2;
spark->momy = clip->momx/2;
}
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);
P_InitAngle(fast, 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;
}
}
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);
P_InitAngle(fast, 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;
}
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 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 INT16 minturn = KART_FULLTURN/8;
const fixed_t fastspeed = (K_GetKartSpeed(player, false) * 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 = (((buttons & BT_ACCELERATE) == BT_ACCELERATE) || (onground && 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_LOOKDOWN;
// Uses turning over steering -- it's important to show player feedback immediately.
if (player->cmd.turning < -minturn)
{
turndir = -1;
}
else if (player->cmd.turning > minturn)
{
turndir = 1;
}
}
else if (drift == 0)
{
// Prioritize looking back frames over turning
turndir = 0;
}
// Sliptides: drift -> lookback frames
if (abs(player->aizdriftturn) >= ANGLE_90)
{
destGlanceDir = -(2*intsign(player->aizdriftturn));
player->glanceDir = destGlanceDir;
drift = turndir = 0;
player->pflags &= ~PF_LOOKDOWN;
}
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_LOOKDOWN))
{
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_LOOKDOWN;
}
}
else if (K_GetForwardMove(player) < 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_LOOKDOWN;
// 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)
{
sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons
if (cv_kartvoices.value)
S_StartSound(source, sfx_khurt1 + pick);
K_RegularVoiceTimers(source->player);
}
void K_PlayHitEmSound(mobj_t *source)
{
if (source->player->follower)
{
follower_t fl = followers[source->player->followerskin];
source->player->follower->movecount = fl.hitconfirmtime; // movecount is used to play the hitconfirm animation for followers.
}
if (cv_kartvoices.value)
S_StartSound(source, sfx_khitem);
else
S_StartSound(source, sfx_s1c9); // The only lost gameplay functionality with voices disabled
K_RegularVoiceTimers(source->player);
}
void K_PlayPowerGloatSound(mobj_t *source)
{
if (cv_kartvoices.value)
S_StartSound(source, sfx_kgloat);
K_RegularVoiceTimers(source->player);
}
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)
{
if (player->invincibilitytimer || player->hyudorotimer || player->sneakertimer)
return false;
return true;
}
boolean K_SlopeResistance(player_t *player)
{
if (player->invincibilitytimer || player->sneakertimer || player->tiregrease || player->flamedash)
return true;
return false;
}
boolean K_TripwirePassConditions(player_t *player)
{
if (
player->invincibilitytimer ||
player->sneakertimer ||
player->growshrinktimer > 0 ||
player->flamedash ||
player->speed > 2 * K_GetKartSpeed(player, false)
)
return true;
return false;
}
boolean K_TripwirePass(player_t *player)
{
return (K_TripwirePassConditions(player) || (player->tripwireLeniency > 0));
}
boolean K_WaterRun(player_t *player)
{
if (
player->invincibilitytimer ||
player->sneakertimer ||
player->tiregrease ||
player->flamedash ||
player->speed > 2 * K_GetKartSpeed(player, false)
)
return true;
return false;
}
static fixed_t K_FlameShieldDashVar(INT32 val)
{
// 1 second = 75% + 50% top speed
return (3*FRACUNIT/4) + (((val * FRACUNIT) / TICRATE) / 2);
}
INT16 K_GetSpindashChargeTime(player_t *player)
{
// more charge time for higher speed
// Tails = 2s, Knuckles = 2.6s, Metal = 3.2s
return (player->kartspeed + 8) * (TICRATE/5);
}
fixed_t K_GetSpindashChargeSpeed(player_t *player)
{
// more speed for higher weight & speed
// Tails = +6.25%, Fang = +20.31%, Mighty = +20.31%, Metal = +25%
// (can be higher than this value when overcharged)
return (player->kartspeed + player->kartweight) * (FRACUNIT/32);
}
// sets boostpower, speedboost, accelboost, and handleboost to whatever we need it to be
static void K_GetKartBoostPower(player_t *player)
{
// Light weights have stronger boost stacking -- aka, better metabolism than heavies XD
const fixed_t maxmetabolismincrease = FRACUNIT/2;
const fixed_t metabolism = FRACUNIT - ((9-player->kartweight) * maxmetabolismincrease / 8);
// v2 almost broke sliptiding when it fixed turning bugs!
// This value is fine-tuned to feel like v1 again without reverting any of those changes.
const fixed_t sliptidehandling = FRACUNIT/2;
fixed_t boostpower = FRACUNIT;
fixed_t speedboost = 0, accelboost = 0, handleboost = 0;
UINT8 numboosts = 0;
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, FixedMul(player->offroad, K_GetKartGameSpeedScalar(gamespeed)) + FRACUNIT);
if (player->bananadrag > TICRATE)
boostpower = (4*boostpower)/5;
// Note: Handling will ONLY stack when sliptiding!
// When you're not, it just uses the best instead of adding together, like the old behavior.
#define ADDBOOST(s,a,h) { \
numboosts++; \
speedboost += FixedDiv(s, FRACUNIT + (metabolism * (numboosts-1))); \
accelboost += FixedDiv(a, FRACUNIT + (metabolism * (numboosts-1))); \
if (player->aizdriftstrat) \
handleboost += FixedDiv(h, FRACUNIT + (metabolism * (numboosts-1))); \
else \
handleboost = max(h, handleboost); \
}
if (player->sneakertimer) // Sneaker
{
UINT8 i;
for (i = 0; i < player->numsneakers; i++)
{
ADDBOOST(FRACUNIT/2, 8*FRACUNIT, sliptidehandling); // + 50% top speed, + 800% acceleration, +50% handling
}
}
if (player->invincibilitytimer) // Invincibility
{
ADDBOOST(3*FRACUNIT/8, 3*FRACUNIT, sliptidehandling/2); // + 37.5% top speed, + 300% acceleration, +25% handling
}
if (player->growshrinktimer > 0) // Grow
{
ADDBOOST(0, 0, sliptidehandling/2); // + 0% top speed, + 0% acceleration, +25% handling
}
if (player->flamedash) // Flame Shield dash
{
fixed_t dash = K_FlameShieldDashVar(player->flamedash);
ADDBOOST(
dash, // + infinite top speed
3*FRACUNIT, // + 300% acceleration
FixedMul(FixedDiv(dash, FRACUNIT/2), sliptidehandling/2) // + infinite handling
);
}
if (player->spindashboost) // Spindash boost
{
const fixed_t MAXCHARGESPEED = K_GetSpindashChargeSpeed(player);
const fixed_t exponent = FixedMul(player->spindashspeed, player->spindashspeed);
// character & charge dependent
ADDBOOST(
FixedMul(MAXCHARGESPEED, exponent), // + 0 to K_GetSpindashChargeSpeed()% top speed
(40 * exponent), // + 0% to 4000% acceleration
0 // + 0% handling
);
}
if (player->startboost) // Startup Boost
{
ADDBOOST(FRACUNIT/2, 4*FRACUNIT, 0); // + 50% top speed, + 400% acceleration, +0% handling
}
if (player->driftboost) // Drift Boost
{
if (player->strongdriftboost) // Purple/Rainbow drift boost
{
ADDBOOST(FRACUNIT/3, 4*FRACUNIT, 0); // + 33% top speed, + 400% acceleration, +0% handling
}
else
{
ADDBOOST(FRACUNIT/4, 4*FRACUNIT, 0); // + 25% top speed, + 400% acceleration, +0% handling
}
}
if (player->trickboost) // Trick pannel up-boost
{
ADDBOOST(player->trickboostpower, 5*FRACUNIT, 0); // <trickboostpower>% speed, 500% accel, 0% handling
}
if (player->ringboost) // Ring Boost
{
ADDBOOST(FRACUNIT/5, 4*FRACUNIT, 0); // + 20% top speed, + 400% acceleration, +0% handling
}
if (player->eggmanexplode) // Ready-to-explode
{
ADDBOOST(3*FRACUNIT/20, FRACUNIT, 0); // + 15% top speed, + 100% acceleration, +0% handling
}
if (player->draftpower > 0) // Drafting
{
// 30% - 44%, each point of speed adds 1.75%
fixed_t draftspeed = ((3*FRACUNIT)/10) + ((player->kartspeed-1) * ((7*FRACUNIT)/400));
if (gametype == GT_BATTLE)
{
// TODO: gametyperules
draftspeed *= 2;
}
speedboost += FixedMul(draftspeed, player->draftpower); // (Drafting suffers no boost stack penalty.)
numboosts++;
}
player->boostpower = boostpower;
// value smoothing
if (speedboost > player->speedboost)
{
player->speedboost = speedboost;
}
else
{
player->speedboost += (speedboost - player->speedboost) / (TICRATE/2);
}
player->accelboost = accelboost;
player->handleboost = handleboost;
player->numboosts = numboosts;
}
fixed_t K_GrowShrinkSpeedMul(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)
{
// Grown
// Change x2 speed into x1.5
speedMul = FixedDiv(FixedMul(playerScale, GROW_PHYSICS_SCALE), GROW_SCALE);
}
else if (scaleDiff < 0)
{
// Shrunk
// Change x0.5 speed into x0.75
speedMul = FixedDiv(FixedMul(playerScale, SHRINK_PHYSICS_SCALE), SHRINK_SCALE);
}
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 = (3*FRACUNIT)/64;
fixed_t g_cc = K_GetKartGameSpeedScalar(gamespeed) + xspd;
fixed_t k_speed = 148;
fixed_t finalspeed;
k_speed += kartspeed*4; // 152 - 184
finalspeed = FixedMul(k_speed<<14, g_cc);
return finalspeed;
}
fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower)
{
const boolean mobjValid = (player->mo != NULL && P_MobjWasRemoved(player->mo) == false);
fixed_t finalspeed = K_GetKartSpeedFromStat(player->kartspeed);
if (player->spheres > 0)
{
fixed_t sphereAdd = (FRACUNIT/80); // 50% at max
finalspeed = FixedMul(finalspeed, FRACUNIT + (sphereAdd * player->spheres));
}
if (K_PlayerUsesBotMovement(player))
{
// Increase bot speed by 1-10% depending on difficulty
fixed_t add = (player->botvars.difficulty * (FRACUNIT/10)) / MAXBOTDIFFICULTY;
finalspeed = FixedMul(finalspeed, FRACUNIT + add);
if (player->botvars.rival == true)
{
// +10% top speed for the rival
finalspeed = FixedMul(finalspeed, 11*FRACUNIT/10);
}
}
finalspeed = FixedMul(finalspeed, mapobjectscale);
if (doboostpower == true)
{
if (mobjValid == true)
{
// Scale with the player.
finalspeed = FixedMul(finalspeed, K_GrowShrinkSpeedMul(player));
}
if (K_PlayerUsesBotMovement(player))
{
finalspeed = FixedMul(finalspeed, K_BotTopSpeedRubberband(player));
}
finalspeed = FixedMul(finalspeed, player->boostpower + player->speedboost);
}
return finalspeed;
}
fixed_t K_GetKartAccel(player_t *player)
{
fixed_t k_accel = 121;
k_accel += 17 * (9 - player->kartspeed); // 121 - 257
if (player->spheres > 0)
{
fixed_t sphereAdd = (FRACUNIT/10); // 500% at max
k_accel = FixedMul(k_accel, FRACUNIT + (sphereAdd * player->spheres));
}
return FixedMul(k_accel, (FRACUNIT + player->accelboost) / 4);
}
UINT16 K_GetKartFlashing(player_t *player)
{
UINT16 tics = flashingtics;
if (gametype == GT_BATTLE)
{
// TODO: gametyperules
return 1;
}
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 ((player->pflags & PF_STASIS) || (player->carry == CR_SLIDING))
{
return 0;
}
if (player->sneakertimer || player->spindashboost)
{
return MAXPLMOVE;
}
if (player->spinouttimer || K_PlayerEBrake(player))
{
return 0;
}
if (K_KartKickstart(player)) // unlike the brute forward of sneakers, allow for backwards easing here
{
forwardmove += MAXPLMOVE;
if (forwardmove > MAXPLMOVE)
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);
const fixed_t p_accel = K_GetKartAccel(player);
fixed_t newspeed, oldspeed, finalspeed;
fixed_t orig = ORIG_FRICTION;
if (K_PlayerUsesBotMovement(player))
{
orig = K_BotFrictionRubberband(player, ORIG_FRICTION);
}
oldspeed = R_PointToDist2(0, 0, player->rmomx, player->rmomy); // FixedMul(P_AproxDistance(player->rmomx, player->rmomy), player->mo->scale);
// Don't calculate the acceleration as ever being above top speed
if (oldspeed > p_speed)
oldspeed = p_speed;
newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), orig);
finalspeed = newspeed - oldspeed;
return finalspeed;
}
fixed_t K_3dKartMovement(player_t *player)
{
fixed_t finalspeed = K_GetNewSpeed(player);
fixed_t movemul = FRACUNIT;
SINT8 forwardmove = K_GetForwardMove(player);
movemul = abs(forwardmove * FRACUNIT) / 50;
// forwardmove is:
// 50 while accelerating,
// 0 with no gas, and
// -25 when only braking.
if (forwardmove >= 0)
{
finalspeed = FixedMul(finalspeed, movemul);
}
else
{
finalspeed = FixedMul(-mapobjectscale/2, movemul);
}
return finalspeed;
}
angle_t K_MomentumAngle(mobj_t *mo)
{
if (mo->momx || mo->momy)
{
return R_PointToAngle2(0, 0, mo->momx, mo->momy);
}
else
{
return mo->angle; // default to facing angle, rather than 0
}
}
void K_AddHitLag(mobj_t *mo, INT32 tics, boolean fromDamage)
{
if (mo == NULL || P_MobjWasRemoved(mo) || (mo->flags & MF_NOHITLAGFORME && mo->type != MT_PLAYER))
{
return;
}
mo->hitlag += tics;
mo->hitlag = min(mo->hitlag, MAXHITLAGTICS);
if (fromDamage == true)
{
// Dunno if this should flat-out &~ the flag out too.
// Decided it probably just just keep it since it's "adding" hitlag.
mo->eflags |= MFE_DAMAGEHITLAG;
}
}
void K_SetHitLagForObjects(mobj_t *mo1, mobj_t *mo2, INT32 tics, boolean fromDamage)
{
INT32 finalTics = tics;
if (tics <= 0)
{
return;
}
if ((mo1 && !P_MobjWasRemoved(mo1)) == true && (mo2 && !P_MobjWasRemoved(mo2)) == true)
{
const fixed_t speedTicFactor = (mapobjectscale * 8);
const INT32 angleTicFactor = ANGLE_22h;
const fixed_t mo1speed = FixedHypot(FixedHypot(mo1->momx, mo1->momy), mo1->momz);
const fixed_t mo2speed = FixedHypot(FixedHypot(mo2->momx, mo2->momy), mo2->momz);
const fixed_t speedDiff = abs(mo2speed - mo1speed);
const fixed_t scaleDiff = abs(mo2->scale - mo1->scale);
angle_t mo1angle = K_MomentumAngle(mo1);
angle_t mo2angle = K_MomentumAngle(mo2);
INT32 angleDiff = 0;
if (mo1speed > 0 && mo2speed > 0)
{
// If either object is completely not moving, their speed doesn't matter.
angleDiff = AngleDelta(mo1angle, mo2angle);
}
// Add extra "damage" based on what was happening to the objects on impact.
finalTics += (FixedMul(speedDiff, FRACUNIT + scaleDiff) / speedTicFactor) + (angleDiff / angleTicFactor);
// This shouldn't happen anymore, but just in case something funky happens.
if (finalTics < tics)
{
finalTics = tics;
}
}
K_AddHitLag(mo1, finalTics, fromDamage);
K_AddHitLag(mo2, finalTics, false); // mo2 is the inflictor, so don't use the damage property.
}
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_DoPowerClash(player_t *t1, player_t *t2) {
mobj_t *clash;
// short-circuit instashield for vfx visibility
t1->instashield = 1;
t2->instashield = 1;
S_StartSound(t1->mo, sfx_parry);
K_AddHitLag(t1->mo, 6, false);
K_AddHitLag(t2->mo, 6, false);
clash = P_SpawnMobj((t1->mo->x/2) + (t2->mo->x/2), (t1->mo->y/2) + (t2->mo->y/2), (t1->mo->z/2) + (t2->mo->z/2), MT_POWERCLASH);
// needs to handle mixed scale collisions (t1 grow t2 invinc)...
clash->z = clash->z + (t1->mo->height/4) + (t2->mo->height/4);
clash->angle = R_PointToAngle2(clash->x, clash->y, t1->mo->x, t1->mo->y) + ANGLE_90;
// Shrink over time (accidental behavior that looked good)
clash->destscale = (t1->mo->scale/2) + (t2->mo->scale/2);
P_SetScale(clash, 3*clash->destscale/2);
}
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->bumpers > 0) && (victim->bumpers <= 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
if (player->speed < K_GetKartSpeed(player, true)/4)
P_InstaThrust(player->mo, player->mo->angle, FixedMul(K_GetKartSpeed(player, true)/4, player->mo->scale));
S_StartSound(player->mo, sfx_slip);
}
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);
if (player->invincibilitytimer == 0)
player->mo->color = player->skincolor;
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;
P_RestoreMusic(player);
}
void K_TumblePlayer(player_t *player, mobj_t *inflictor, mobj_t *source)
{
fixed_t gravityadjust;
(void)source;
K_DirectorFollowAttack(player, inflictor, source);
player->tumbleBounces = 1;
if (player->tripWireState == TRIP_PASSED)
{
player->tumbleHeight = 50;
}
else
{
player->mo->momx = 2 * player->mo->momx / 3;
player->mo->momy = 2 * player->mo->momy / 3;
player->tumbleHeight = 30;
}
player->pflags &= ~PF_TUMBLESOUND;
if (inflictor && !P_MobjWasRemoved(inflictor))
{
const fixed_t addHeight = FixedHypot(FixedHypot(inflictor->momx, inflictor->momy) / 2, FixedHypot(player->mo->momx, player->mo->momy) / 2);
player->tumbleHeight += (addHeight / player->mo->scale);
}
S_StartSound(player->mo, sfx_s3k9b);
// adapt momz w/ gravity?
// as far as kart goes normal gravity is 2 (FRACUNIT*2)
gravityadjust = P_GetMobjGravity(player->mo)/2; // so we'll halve it for our calculations.
if (player->mo->eflags & MFE_UNDERWATER)
gravityadjust /= 2; // halve "gravity" underwater
// and then modulate momz like that...
player->mo->momz = -gravityadjust * player->tumbleHeight;
P_SetPlayerMobjState(player->mo, S_KART_SPINOUT);
if (P_IsDisplayPlayer(player))
P_StartQuake(64<<FRACBITS, 10);
}
static boolean K_LastTumbleBounceCondition(player_t *player)
{
return (player->tumbleBounces > TUMBLEBOUNCES && player->tumbleHeight < 60);
}
static void K_HandleTumbleBounce(player_t *player)
{
fixed_t gravityadjust;
player->tumbleBounces++;
player->tumbleHeight = (player->tumbleHeight * ((player->tumbleHeight > 100) ? 3 : 4)) / 5;
player->pflags &= ~PF_TUMBLESOUND;
if (player->tumbleHeight < 10)
{
// 10 minimum bounce height
player->tumbleHeight = 10;
}
if (K_LastTumbleBounceCondition(player))
{
// Leave tumble state when below 40 height, and have bounced off the ground enough
if (player->pflags & PF_TUMBLELASTBOUNCE)
{
// End tumble state
player->tumbleBounces = 0;
player->pflags &= ~PF_TUMBLELASTBOUNCE; // Reset for next time
return;
}
else
{
// One last bounce at the minimum height, to reset the animation
player->tumbleHeight = 10;
player->pflags |= PF_TUMBLELASTBOUNCE;
player->mo->rollangle = 0; // p_user.c will stop rotating the player automatically
}
}
if (P_IsDisplayPlayer(player) && player->tumbleHeight >= 40)
P_StartQuake((player->tumbleHeight*3/2)<<FRACBITS, 6); // funny earthquakes for the FEEL
S_StartSound(player->mo, (player->tumbleHeight < 40) ? sfx_s3k5d : sfx_s3k5f); // s3k5d is bounce < 50, s3k5f otherwise!
player->mo->momx = player->mo->momx / 2;
player->mo->momy = player->mo->momy / 2;
// adapt momz w/ gravity?
// as far as kart goes normal gravity is 2 (FRACUNIT*2)
gravityadjust = P_GetMobjGravity(player->mo)/2; // so we'll halve it for our calculations.
if (player->mo->eflags & MFE_UNDERWATER)
gravityadjust /= 2; // halve "gravity" underwater
// and then modulate momz like that...
player->mo->momz = -gravityadjust * player->tumbleHeight;
}
// Play a falling sound when you start falling while tumbling and you're nowhere near done bouncing
static void K_HandleTumbleSound(player_t *player)
{
fixed_t momz;
momz = player->mo->momz * P_MobjFlip(player->mo);
if (!K_LastTumbleBounceCondition(player) &&
!(player->pflags & PF_TUMBLESOUND) && momz < -10*player->mo->scale)
{
S_StartSound(player->mo, sfx_s3k51);
player->pflags |= PF_TUMBLESOUND;
}
}
void K_ApplyTripWire(player_t *player, tripwirestate_t state)
{
if (state == TRIP_PASSED)
S_StartSound(player->mo, sfx_ssa015);
else if (state == TRIP_BLOCKED)
S_StartSound(player->mo, sfx_kc40);
player->tripWireState = state;
K_AddHitLag(player->mo, 10, false);
if (state == TRIP_PASSED && player->spinouttimer &&
player->speed > 2* K_GetKartSpeed(player, false))
{
K_TumblePlayer(player, NULL, NULL);
}
}
INT32 K_ExplodePlayer(player_t *player, mobj_t *inflictor, mobj_t *source) // A bit of a hack, we just throw the player up higher here and extend their spinout timer
{
INT32 ringburst = 10;
(void)source;
K_DirectorFollowAttack(player, inflictor, source);
player->mo->momz = 18*mapobjectscale*P_MobjFlip(player->mo); // please stop forgetting mobjflip checks!!!!
player->mo->momx = player->mo->momy = 0;
player->spinouttype = KSPIN_EXPLOSION;
player->spinouttimer = (3*TICRATE/2)+2;
if (inflictor && !P_MobjWasRemoved(inflictor))
{
if (inflictor->type == MT_SPBEXPLOSION && inflictor->extravalue1)
{
player->spinouttimer = ((5*player->spinouttimer)/2)+1;
player->mo->momz *= 2;
ringburst = 20;
}
}
if (player->mo->eflags & MFE_UNDERWATER)
player->mo->momz = (117 * player->mo->momz) / 200;
P_SetPlayerMobjState(player->mo, S_KART_SPINOUT);
if (P_IsDisplayPlayer(player))
P_StartQuake(64<<FRACBITS, 5);
return ringburst;
}
// This kind of wipeout happens with no rings -- doesn't remove a bumper, has no invulnerability, and is much shorter.
void K_DebtStingPlayer(player_t *player, mobj_t *source)
{
INT32 length = TICRATE;
if (source->player)
{
length += (4 * (source->player->kartweight - player->kartweight));
}
player->spinouttype = KSPIN_STUNG;
player->spinouttimer = length;
player->wipeoutslow = min(length-1, wipeoutslowtime+1);
P_SetPlayerMobjState(player->mo, S_KART_SPINOUT);
}
void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers)
{
if (!(gametyperules & GTR_BUMPERS))
{
// Bumpers aren't being used
return;
}
// TODO: replace all console text print-outs with a real visual
if (player->bumpers > 0 && prevBumpers == 0)
{
if (netgame)
{
CONS_Printf(M_GetText("%s is back in the game!\n"), player_names[player-players]);
}
}
else if (player->bumpers == 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]);
}
}
K_CalculateBattleWanted();
K_CheckBumpers();
}
void K_DestroyBumpers(player_t *player, UINT8 amount)
{
UINT8 oldBumpers = player->bumpers;
if (!(gametyperules & GTR_BUMPERS))
{
return;
}
amount = min(amount, player->bumpers);
if (amount == 0)
{
return;
}
player->bumpers -= amount;
K_HandleBumperChanges(player, oldBumpers);
}
void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount)
{
UINT8 oldPlayerBumpers = player->bumpers;
UINT8 oldVictimBumpers = victim->bumpers;
UINT8 tookBumpers = 0;
if (!(gametyperules & GTR_BUMPERS))
{
return;
}
amount = min(amount, victim->bumpers);
if (amount == 0)
{
return;
}
while ((tookBumpers < amount) && (victim->bumpers > 0))
{
UINT8 newbumper = player->bumpers;
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);
P_InitAngle(newmo, (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->bumpers++;
victim->bumpers--;
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);
}
#define MINEQUAKEDIST 4096
// Does the proximity screen flash and quake for explosions
void K_MineFlashScreen(mobj_t *source)
{
INT32 pnum;
player_t *p;
// check for potential display players near the source so we can have a sick earthquake / flashpal.
for (pnum = 0; pnum < MAXPLAYERS; pnum++)
{
p = &players[pnum];
if (!playeringame[pnum] || !P_IsDisplayPlayer(p))
continue;
if (R_PointToDist2(p->mo->x, p->mo->y, source->x, source->y) < mapobjectscale*MINEQUAKEDIST)
{
P_StartQuake(55<<FRACBITS, 12);
if (!bombflashtimer && P_CheckSight(p->mo, source))
{
bombflashtimer = TICRATE*2;
P_FlashPal(p, PAL_WHITE, 1);
}
break; // we can break right now because quakes are global to all split players somehow.
}
}
}
// 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_MineFlashScreen(source);
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);
P_InitAngle(dust, (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;
}
}
#undef MINEQUAKEDIST
fixed_t K_ItemScaleForPlayer(player_t *player)
{
switch (player->itemscale)
{
case ITEMSCALE_GROW:
return FixedMul(GROW_SCALE, mapobjectscale);
case ITEMSCALE_SHRINK:
return FixedMul(SHRINK_SCALE, mapobjectscale);
default:
return mapobjectscale;
}
}
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;
fixed_t finalscale = mapobjectscale;
mobj_t *throwmo;
if (source->player != NULL)
{
if (source->player->itemscale == ITEMSCALE_SHRINK)
{
// Nerf the base item speed a bit.
finalspeed = FixedMul(finalspeed, SHRINK_PHYSICS_SCALE);
}
if (source->player->speed > K_GetKartSpeed(source->player, 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)), // Multiply speed to be proportional to your own, boosted maxspeed.
(((180<<FRACBITS) - AngleFixed(input)) / 180) // multiply speed based on angle diff... i.e: don't do this for firing backward :V
)));
}
finalscale = K_ItemScaleForPlayer(source->player);
}
if (type == MT_BUBBLESHIELDTRAP)
{
finalscale = source->scale;
}
x = source->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
th = P_SpawnMobj(x, y, z, type); // this will never return null because collision isn't processed here
K_FlipFromObject(th, source);
th->flags2 |= flags2;
th->threshold = 10;
if (th->info->seesound)
S_StartSound(source, th->info->seesound);
P_SetTarget(&th->target, source);
P_SetScale(th, finalscale);
th->destscale = finalscale;
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); // however, THIS can fuck up your day. just absolutely ruin you.
if (P_MobjWasRemoved(th))
return NULL;
// 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;
}
P_InitAngle(th, an);
th->momx = FixedMul(finalspeed, FINECOSINE(an>>ANGLETOFINESHIFT));
th->momy = FixedMul(finalspeed, FINESINE(an>>ANGLETOFINESHIFT));
th->momz = source->momz;
if (source->player != NULL)
{
th->cusval = source->player->itemscale;
}
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 th;
}
UINT16 K_DriftSparkColor(player_t *player, INT32 charge)
{
const INT32 dsone = K_GetKartDriftSparkValueForStage(player, 1);
const INT32 dstwo = K_GetKartDriftSparkValueForStage(player, 2);
const INT32 dsthree = K_GetKartDriftSparkValueForStage(player, 3);
const INT32 dsfour = K_GetKartDriftSparkValueForStage(player, 4);
UINT16 color = SKINCOLOR_NONE;
if (charge < 0)
{
// Stage 0: Yellow
color = SKINCOLOR_GOLD;
}
else if (charge >= dsfour)
{
// Stage 4: Rainbow
if (charge <= dsfour+(32*3))
{
// transition
color = SKINCOLOR_SILVER;
}
else
{
color = K_RainbowColor(leveltime);
}
}
else if (charge >= dsthree)
{
// Stage 3: Purple
if (charge <= dsthree+(16*3))
{
// transition 1
color = SKINCOLOR_TAFFY;
}
else if (charge <= dsthree+(32*3))
{
// transition 2
color = SKINCOLOR_MOONSET;
}
else
{
color = SKINCOLOR_PURPLE;
}
}
else if (charge >= dstwo)
{
// Stage 2: Blue
if (charge <= dstwo+(32*3))
{
// transition
color = SKINCOLOR_NOVA;
}
else
{
color = SKINCOLOR_SAPPHIRE;
}
}
else if (charge >= dsone)
{
// Stage 1: Red
if (charge <= dsone+(32*3))
{
// transition
color = SKINCOLOR_TANGERINE;
}
else
{
color = SKINCOLOR_KETCHUP;
}
}
return color;
}
static void K_SpawnDriftElectricity(player_t *player)
{
UINT8 i;
UINT16 color = K_DriftSparkColor(player, player->driftcharge);
mobj_t *mo = player->mo;
fixed_t vr = FixedDiv(mo->radius/3, mo->scale); // P_SpawnMobjFromMobj will rescale
fixed_t horizontalradius = FixedDiv(5*mo->radius/3, mo->scale);
angle_t verticalangle = K_MomentumAngle(mo) + ANGLE_180; // points away from the momentum angle
for (i = 0; i < 2; i++)
{
// i == 0 is right, i == 1 is left
mobj_t *spark;
angle_t horizonatalangle = verticalangle + (i ? ANGLE_90 : ANGLE_270);
angle_t sparkangle = verticalangle + ANGLE_180;
fixed_t verticalradius = vr; // local version of the above so we can modify it
fixed_t scalefactor = 0; // positive values enlarge sparks, negative values shrink them
fixed_t x, y;
if (player->drift == 0)
; // idk what you're doing spawning drift sparks when you're not drifting but you do you
else
{
scalefactor = -(2*i - 1) * min(max(player->steering, -1), 1) * FRACUNIT;
if ((player->drift > 0) == !(i)) // inwards spark should be closer to the player
verticalradius = 0;
}
x = P_ReturnThrustX(mo, verticalangle, verticalradius)
+ P_ReturnThrustX(mo, horizonatalangle, horizontalradius);
y = P_ReturnThrustY(mo, verticalangle, verticalradius)
+ P_ReturnThrustY(mo, horizonatalangle, horizontalradius);
spark = P_SpawnMobjFromMobj(mo, x, y, 0, MT_DRIFTELECTRICITY);
P_InitAngle(spark, sparkangle);
spark->momx = mo->momx;
spark->momy = mo->momy;
spark->momz = mo->momz;
spark->color = color;
K_GenericExtraFlagsNoZAdjust(spark, mo);
spark->spritexscale += scalefactor/3;
spark->spriteyscale += scalefactor/8;
}
}
void K_SpawnDriftElectricSparks(player_t *player)
{
SINT8 hdir, vdir, i;
mobj_t *mo = player->mo;
angle_t momangle = K_MomentumAngle(mo) + ANGLE_180;
fixed_t radius = 2 * FixedDiv(mo->radius, mo->scale); // P_SpawnMobjFromMobj will rescale
fixed_t x = P_ReturnThrustX(mo, momangle, radius);
fixed_t y = P_ReturnThrustY(mo, momangle, radius);
fixed_t z = FixedDiv(mo->height, 2 * mo->scale); // P_SpawnMobjFromMobj will rescale
fixed_t sparkspeed = mobjinfo[MT_DRIFTELECTRICSPARK].speed;
fixed_t sparkradius = 2 * mobjinfo[MT_DRIFTELECTRICSPARK].radius;
UINT16 color = K_DriftSparkColor(player, player->driftcharge);
// if the sparks are spawned from first blood rather than drift boost, color will be SKINCOLOR_NONE. ew!
if (color == SKINCOLOR_NONE)
color = SKINCOLOR_SILVER;
for (hdir = -1; hdir <= 1; hdir += 2)
{
for (vdir = -1; vdir <= 1; vdir += 2)
{
fixed_t hspeed = FixedMul(hdir * sparkspeed, mo->scale); // P_InstaThrust treats speed as absolute
fixed_t vspeed = vdir * sparkspeed; // P_SetObjectMomZ scales speed with object scale
angle_t sparkangle = mo->angle + ANGLE_45;
for (i = 0; i < 4; i++)
{
fixed_t xoff = P_ReturnThrustX(mo, sparkangle, sparkradius);
fixed_t yoff = P_ReturnThrustY(mo, sparkangle, sparkradius);
mobj_t *spark = P_SpawnMobjFromMobj(mo, x + xoff, y + yoff, z, MT_DRIFTELECTRICSPARK);
P_InitAngle(spark, sparkangle);
spark->color = color;
P_InstaThrust(spark, mo->angle + ANGLE_90, hspeed);
P_SetObjectMomZ(spark, vspeed, false);
spark->momx += mo->momx; // copy player speed
spark->momy += mo->momy;
spark->momz += P_GetMobjZMovement(mo);
sparkangle += ANGLE_90;
}
}
}
S_StartSound(mo, sfx_s3k45);
}
static void K_SpawnDriftSparks(player_t *player)
{
const INT32 dsone = K_GetKartDriftSparkValueForStage(player, 1);
const INT32 dstwo = K_GetKartDriftSparkValueForStage(player, 2);
const INT32 dsthree = K_GetKartDriftSparkValueForStage(player, 3);
const INT32 dsfour = K_GetKartDriftSparkValueForStage(player, 4);
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 < dsone && !(player->driftcharge < 0)))
return;
travelangle = player->mo->angle-(ANGLE_45/5)*player->drift;
for (i = 0; i < 2; i++)
{
SINT8 size = 1;
UINT8 trail = 0;
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);
P_InitAngle(spark, 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 = P_GetMobjZMovement(player->mo)/2;
spark->color = K_DriftSparkColor(player, player->driftcharge);
if (player->driftcharge < 0)
{
// Stage 0: Yellow
size = 0;
}
else if (player->driftcharge >= dsfour)
{
// Stage 4: Rainbow
size = 2;
trail = 2;
if (player->driftcharge <= (dsfour)+(32*3))
{
// transition
P_SetScale(spark, (spark->destscale = spark->scale*3/2));
S_StartSound(player->mo, sfx_cock);
}
else
{
spark->colorized = true;
}
}
else if (player->driftcharge >= dsthree)
{
// Stage 3: Purple
size = 2;
trail = 1;
if (player->driftcharge <= dsthree+(32*3))
{
// transition
P_SetScale(spark, (spark->destscale = spark->scale*3/2));
}
}
else if (player->driftcharge >= dstwo)
{
// Stage 2: Blue
size = 2;
trail = 1;
if (player->driftcharge <= dstwo+(32*3))
{
// transition
P_SetScale(spark, (spark->destscale = spark->scale*3/2));
}
}
else
{
// Stage 1: Red
size = 1;
if (player->driftcharge <= dsone+(32*3))
{
// transition
P_SetScale(spark, (spark->destscale = spark->scale*2));
}
}
if ((player->drift > 0 && player->steering > 0) // Inward drifts
|| (player->drift < 0 && player->steering < 0))
{
if ((player->drift < 0 && (i & 1))
|| (player->drift > 0 && !(i & 1)))
{
size++;
}
else if ((player->drift < 0 && !(i & 1))
|| (player->drift > 0 && (i & 1)))
{
size--;
}
}
else if ((player->drift > 0 && player->steering < 0) // Outward drifts
|| (player->drift < 0 && player->steering > 0))
{
if ((player->drift < 0 && (i & 1))
|| (player->drift > 0 && !(i & 1)))
{
size--;
}
else if ((player->drift < 0 && !(i & 1))
|| (player->drift > 0 && (i & 1)))
{
size++;
}
}
if (size == 2)
P_SetMobjState(spark, S_DRIFTSPARK_A1);
else if (size < 1)
P_SetMobjState(spark, S_DRIFTSPARK_C1);
else if (size > 2)
P_SetMobjState(spark, S_DRIFTSPARK_D1);
if (trail > 0)
spark->tics += trail;
K_MatchGenericExtraFlags(spark, player->mo);
}
if (player->driftcharge >= dsthree)
{
K_SpawnDriftElectricity(player);
}
}
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))
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);
P_InitAngle(spark, 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->bumpers <= 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);
P_InitAngle(flame, 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*3)/FRACUNIT;
mobj_t *sparkle;
UINT8 invanimnum; // Current sparkle animation number
INT32 invtime;// Invincibility time left, in seconds
UINT8 index = 0;
fixed_t newx, newy, newz;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
if (leveltime & 2)
index = 1;
invtime = mo->player->invincibilitytimer/TICRATE+1;
//CONS_Printf("%d\n", index);
newx = mo->x + (P_RandomRange(-rad, rad)*FRACUNIT);
newy = mo->y + (P_RandomRange(-rad, rad)*FRACUNIT);
newz = mo->z + (P_RandomRange(0, mo->height>>FRACBITS)*FRACUNIT);
sparkle = P_SpawnMobj(newx, newy, newz, MT_SPARKLETRAIL);
P_InitAngle(sparkle, R_PointToAngle2(mo->x, mo->y, sparkle->x, sparkle->y));
sparkle->movefactor = R_PointToDist2(mo->x, mo->y, sparkle->x, sparkle->y); // Save the distance we spawned away from the player.
//CONS_Printf("movefactor: %d\n", sparkle->movefactor/FRACUNIT);
sparkle->extravalue1 = (sparkle->z - mo->z); // Keep track of our Z position relative to the player's, I suppose.
sparkle->extravalue2 = P_RandomRange(0, 1) ? 1 : -1; // Rotation direction?
sparkle->cvmem = P_RandomRange(-25, 25)*mo->scale; // Vertical "angle"
K_FlipFromObject(sparkle, mo);
P_SetTarget(&sparkle->target, mo);
sparkle->destscale = mo->destscale;
P_SetScale(sparkle, mo->scale);
invanimnum = (invtime >= 11) ? 11 : invtime;
//CONS_Printf("%d\n", invanimnum);
P_SetMobjState(sparkle, K_SparkleTrailStartStates[invanimnum][index]);
if (mo->player->invincibilitytimer > itemtime+(2*TICRATE))
{
sparkle->color = mo->color;
sparkle->colorized = true;
}
}
void K_SpawnWipeoutTrail(mobj_t *mo)
{
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);
P_InitAngle(dust, K_MomentumAngle(mo));
dust->destscale = mo->scale;
P_SetScale(dust, mo->scale);
K_FlipFromObject(dust, mo);
}
void K_SpawnDraftDust(mobj_t *mo)
{
UINT8 i;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
for (i = 0; i < 2; i++)
{
angle_t ang, aoff;
SINT8 sign = 1;
UINT8 foff = 0;
mobj_t *dust;
boolean drifting = false;
if (mo->player)
{
UINT8 leniency = (3*TICRATE)/4 + ((mo->player->kartweight-1) * (TICRATE/4));
ang = mo->player->drawangle;
if (mo->player->drift != 0)
{
drifting = true;
ang += (mo->player->drift * ((ANGLE_270 + ANGLE_22h) / 5)); // -112.5 doesn't work. I fucking HATE SRB2 angles
if (mo->player->drift < 0)
sign = 1;
else
sign = -1;
}
foff = 5 - ((mo->player->draftleeway * 5) / leniency);
// this shouldn't happen
if (foff > 4)
foff = 4;
}
else
ang = mo->angle;
if (!drifting)
{
if (i & 1)
sign = -1;
else
sign = 1;
}
aoff = (ang + ANGLE_180) + (ANGLE_45 * sign);
dust = P_SpawnMobj(mo->x + FixedMul(24*mo->scale, FINECOSINE(aoff>>ANGLETOFINESHIFT)),
mo->y + FixedMul(24*mo->scale, FINESINE(aoff>>ANGLETOFINESHIFT)),
mo->z, MT_DRAFTDUST);
P_SetMobjState(dust, S_DRAFTDUST1 + foff);
P_SetTarget(&dust->target, mo);
P_InitAngle(dust, ang - (ANGLE_90 * sign)); // point completely perpendicular from the player
dust->destscale = mo->scale;
P_SetScale(dust, mo->scale);
K_FlipFromObject(dust, mo);
if (leveltime & 1)
dust->tics++; // "randomize" animation
dust->momx = (4*mo->momx)/5;
dust->momy = (4*mo->momy)/5;
dust->momz = (4*P_GetMobjZMovement(mo))/5;
P_Thrust(dust, dust->angle, 4*mo->scale);
if (drifting) // only 1 trail while drifting
break;
}
}
// 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_FAULT)
{
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*spawner->scale)
return;
if (K_GetForwardMove(spawner->player) < 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*spawner->scale)
return;
anglediff = abs((signed)(spawner->angle - K_MomentumAngle(spawner)));
}
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) << FRACBITS;
fixed_t spawny = P_RandomRange(-spawnrange, spawnrange) << FRACBITS;
INT32 speedrange = 2;
mobj_t *dust = P_SpawnMobj(spawner->x + spawnx, spawner->y + spawny, spawner->z, MT_DRIFTDUST);
dust->momx = FixedMul(spawner->momx + (P_RandomRange(-speedrange, speedrange) * spawner->scale), 3*FRACUNIT/4);
dust->momy = FixedMul(spawner->momy + (P_RandomRange(-speedrange, speedrange) * spawner->scale), 3*FRACUNIT/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;
if (leveltime % 6 == 0)
S_StartSound(spawner, sfx_screec);
K_MatchGenericExtraFlags(dust, spawner);
// Sparkle-y warning for when you're about to change drift sparks!
if (spawner->player && spawner->player->drift)
{
INT32 driftval = K_GetKartDriftSparkValue(spawner->player);
INT32 warntime = driftval/3;
INT32 dc = spawner->player->driftcharge;
UINT8 c = SKINCOLOR_NONE;
boolean rainbow = false;
if (dc >= 0)
{
dc += warntime;
}
c = K_DriftSparkColor(spawner->player, dc);
if (dc > (4*driftval)+(32*3))
{
rainbow = true;
}
if (c != SKINCOLOR_NONE)
{
P_SetMobjState(dust, S_DRIFTWARNSPARK1);
dust->color = c;
dust->colorized = rainbow;
}
}
}
}
static void K_SpawnTripwireVFX(mobj_t *mo)
{
tic_t t = leveltime;
angle_t ang, aoff;
SINT8 sign = 1;
boolean altColor = false;
mobj_t *dust;
boolean drifting = false;
UINT8 i;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
if (!P_IsObjectOnGround(mo))
return;
if (mo->player)
{
ang = mo->player->drawangle;
if (mo->player->drift != 0)
{
drifting = true;
ang += (mo->player->drift * ((ANGLE_270 + ANGLE_22h) / 5)); // -112.5 doesn't work. I fucking HATE SRB2 angles
if (mo->player->drift < 0)
sign = 1;
else
sign = -1;
}
}
else
ang = mo->angle;
if (drifting == false)
{
i = (t & 1);
if (i & 1)
sign = -1;
else
sign = 1;
}
else
{
if (t & 1)
{
return;
}
t /= 2;
i = (t & 1);
}
aoff = (ang + ANGLE_180) + (ANGLE_45 * sign);
dust = P_SpawnMobj(mo->x + FixedMul(24*mo->scale, FINECOSINE(aoff>>ANGLETOFINESHIFT)),
mo->y + FixedMul(24*mo->scale, FINESINE(aoff>>ANGLETOFINESHIFT)),
mo->z, MT_DRIFTDUST);
P_SetTarget(&dust->target, mo);
P_InitAngle(dust, ang - (ANGLE_90 * sign)); // point completely perpendicular from the player
P_SetScale(dust, mo->scale);
dust->destscale = mo->scale * 6;
dust->scalespeed = mo->scale/12;
K_FlipFromObject(dust, mo);
altColor = (sign > 0);
if ((t / 2) & 1)
{
dust->tics++; // "randomize" animation
altColor = !altColor;
}
dust->colorized = true;
dust->color = altColor ? SKINCOLOR_BLOSSOM : SKINCOLOR_JAWZ;
dust->momx = (4*mo->momx)/5;
dust->momy = (4*mo->momy)/5;
dust->momz = (4*P_GetMobjZMovement(mo))/5;
P_Thrust(dust, dust->angle, 4*mo->scale);
}
void K_Squish(mobj_t *mo)
{
const fixed_t maxstretch = 4*FRACUNIT;
const fixed_t factor = 5 * mo->height / 4;
const fixed_t threshold = factor / 6;
fixed_t old3dspeed = abs(mo->lastmomz);
fixed_t new3dspeed = abs(mo->momz);
fixed_t delta = abs(old3dspeed - new3dspeed);
fixed_t grav = mo->height/3;
fixed_t add = abs(grav - new3dspeed);
if (R_ThingIsFloorSprite(mo))
return;
if (delta < 2 * add && new3dspeed > grav)
delta += add;
if (delta > threshold)
{
mo->spritexscale =
FRACUNIT + FixedDiv(delta, factor);
if (mo->spritexscale > maxstretch)
mo->spritexscale = maxstretch;
if (new3dspeed > old3dspeed || new3dspeed > grav)
{
mo->spritexscale =
FixedDiv(FRACUNIT, mo->spritexscale);
}
}
else
{
mo->spritexscale -=
(mo->spritexscale - FRACUNIT)
/ (mo->spritexscale < FRACUNIT ? 8 : 3);
}
mo->spriteyscale =
FixedDiv(FRACUNIT, mo->spritexscale);
}
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)
{
// Use info->speed for missiles
PROJSPEED = FixedMul(mobjinfo[mapthing].speed, K_GetKartGameSpeedScalar(gamespeed));
}
else
{
// Use pre-determined speed for tossing
PROJSPEED = FixedMul(82 * FRACUNIT, K_GetKartGameSpeedScalar(gamespeed));
}
// Scale to map scale
// Intentionally NOT player scale, that doesn't work.
PROJSPEED = FixedMul(PROJSPEED, mapobjectscale);
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
{
fixed_t finalscale = K_ItemScaleForPlayer(player);
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);
// 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->eflags |= MFE_VERTICALFLIP;
mo->flags2 |= (player->mo->flags2 & MF2_OBJECTFLIP);
}
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)) * FRACUNIT) + (FixedDiv(player->mo->momz, mapobjectscale)*P_MobjFlip(player->mo)); // Also intentionally not player scale
P_SetObjectMomZ(mo, HEIGHT, false);
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;
P_SetScale(mo, finalscale);
mo->destscale = finalscale;
}
// 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->eflags |= MFE_VERTICALFLIP;
mo->flags2 |= (player->mo->flags2 & MF2_OBJECTFLIP);
}
throwmo->movecount = 0; // above player
P_SetScale(throwmo, finalscale);
throwmo->destscale = finalscale;
}
else
{
mobj_t *lasttrail = K_FindLastTrailMobj(player);
if (mapthing == MT_BUBBLESHIELDTRAP) // Drop directly on top of you.
{
newangle = player->mo->angle;
newx = player->mo->x + player->mo->momx;
newy = player->mo->y + player->mo->momy;
newz = player->mo->z;
}
else if (lasttrail)
{
newangle = lasttrail->angle;
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);
P_SetScale(mo, finalscale);
mo->destscale = finalscale;
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;
if (punter->hitlag > 0)
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 || mine->hitlag > 0)
return;
spd = FixedMul(82 * punter->scale, K_GetKartGameSpeedScalar(gamespeed)); // Avg Speed is 41 in Normal
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);
//K_SetHitLagForObjects(punter, mine, 5);
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_DoLightningShield(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_LightningShieldAttack(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);
P_InitAngle(mo, 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);
P_InitAngle(mo, 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<<FRACBITS), MT_BOOSTSMOKE);
P_SetScale(smoke, src->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)<<FRACBITS), P_RandomRange(0, 8) * src->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;
if (gametype == GT_RACE)
hyu *= 2; // double in race
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].bumpers > 0))
// Has an item
&& (players[i].itemtype
&& players[i].itemamount
&& !(players[i].pflags & PF_ITEMOUT)))
{
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].stealingtimer = -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);
}
}
void K_DoSneaker(player_t *player, INT32 type)
{
const fixed_t intendedboost = FRACUNIT/2;
if (player->floorboost == 0 || player->floorboost == 3)
{
const sfxenum_t normalsfx = sfx_cdfm01;
const sfxenum_t smallsfx = sfx_cdfm40;
sfxenum_t sfx = normalsfx;
if (player->numsneakers)
{
// 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);
K_SpawnDashDustRelease(player);
if (intendedboost > player->speedboost)
player->karthud[khud_destboostcam] = FixedMul(FRACUNIT, FixedDiv((intendedboost - player->speedboost), intendedboost));
player->numsneakers++;
}
if (player->sneakertimer == 0)
{
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);
}
}
if (type != 0)
{
player->pflags |= PF_ATTACKDOWN;
K_PlayBoostTaunt(player->mo);
}
player->sneakertimer = sneakertime;
// 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!
user->pflags |= PF_ATTACKDOWN;
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);
fixed_t thrust = 0;
if (mo->player && mo->player->spectator)
return;
if (mo->eflags & MFE_SPRUNG)
return;
mo->standingslope = NULL;
mo->terrain = NULL;
mo->eflags |= MFE_SPRUNG;
if (vertispeed == 0)
{
thrust = P_AproxDistance(mo->momx, mo->momy) * P_MobjFlip(mo);
thrust = FixedMul(thrust, FINESINE(ANGLE_22h >> ANGLETOFINESHIFT));
}
else
{
thrust = vertispeed * P_MobjFlip(mo);
}
if (mo->player)
{
if (mo->player->sneakertimer)
{
thrust = FixedMul(thrust, 5*FRACUNIT/4);
}
else if (mo->player->invincibilitytimer)
{
thrust = FixedMul(thrust, 9*FRACUNIT/8);
}
mo->player->tricktime = 0; // Reset post-hitlag timer
// Setup the boost for potential upwards trick, at worse, make it your regular max speed. (boost = curr speed*1.25)
mo->player->trickboostpower = max(FixedDiv(mo->player->speed, K_GetKartSpeed(mo->player, false)) - FRACUNIT, 0)*125/100;
//CONS_Printf("Got boost: %d%\n", mo->player->trickboostpower*100 / FRACUNIT);
}
mo->momz = FixedMul(thrust, vscale);
if (mo->eflags & MFE_UNDERWATER)
{
mo->momz = FixedDiv(mo->momz, FixedSqrt(3*FRACUNIT));
}
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;
P_InitAngle(landMine, 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_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_GetShieldFromItem(player->itemtype);
if (work == NULL || P_MobjWasRemoved(work))
{
return;
}
flip = P_MobjFlip(player->mo);
ponground = P_IsObjectOnGround(player->mo);
if (shield != KSHIELD_NONE && !keepshields)
{
if (shield == KSHIELD_LIGHTNING)
{
K_DoLightningShield(player);
}
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;
P_SetScale(dropwork, work->scale);
dropwork->destscale = K_ItemScaleForPlayer(player); //work->destscale;
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
dropwork->hitlag = work->hitlag;
// 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
{
if (dropwork->destscale > work->destscale)
{
fixed_t radius = FixedMul(work->info->radius, dropwork->destscale);
radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(radius, radius); // mobj's distance from its Target, or Radius.
dropwork->flags |= MF_NOCLIPTHING;
work->momx = FixedMul(FINECOSINE(work->angle>>ANGLETOFINESHIFT), radius);
work->momy = FixedMul(FINESINE(work->angle>>ANGLETOFINESHIFT), radius);
P_MoveOrigin(dropwork, player->mo->x + work->momx, player->mo->y + work->momy, player->mo->z);
dropwork->flags &= ~MF_NOCLIPTHING;
}
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;
if (type == MT_DROPTARGET && dropwork->hitlag)
{
dropwork->momx = work->momx;
dropwork->momy = work->momy;
dropwork->momz = work->momz;
dropwork->reactiontime = work->reactiontime;
P_SetMobjState(dropwork, mobjinfo[type].painstate);
}
}
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->pflags & PF_EGGMANOUT)
{
player->pflags &= ~PF_EGGMANOUT;
}
else if ((player->pflags & PF_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;
P_InitAngle(drop, 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)
{
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->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);
}
static 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->flags & FF_EXISTS))
continue;
if ((!(((rover->flags & FF_BLOCKPLAYER && player)
|| (rover->flags & FF_BLOCKOTHERS && !player))
|| (rover->flags & FF_QUICKSAND))
|| (rover->flags & FF_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->flags & FF_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->flags & FF_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)
{
fixed_t finalscale = INT32_MAX;
if (!player->mo)
return;
if (!player->mo->hnext)
{
player->bananadrag = 0;
if (player->pflags & PF_EGGMANOUT)
player->pflags &= ~PF_EGGMANOUT;
else if (player->pflags & PF_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->pflags & PF_EGGMANOUT)
player->pflags &= ~PF_EGGMANOUT;
else if (player->pflags & PF_ITEMOUT)
{
player->itemamount = 0;
K_UnsetItemOut(player);
player->itemtype = KITEM_NONE;
}
return;
}
finalscale = K_ItemScaleForPlayer(player);
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), finalscale)));
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))
P_SlideMove(cur);
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, finalscale - 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), finalscale)));
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))/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
const fixed_t pi = (22<<FRACBITS) / 7; // loose approximation, this doesn't need to be incredibly precise
fixed_t sine = FixedMul(player->mo->scale, 8 * FINESINE((((2*pi*(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)
{
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->bumpers <= 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 (player->respawn.state == RESPAWNST_DROP) // Dropdashing
{
// Dropdashing
targetsnd = ((buttons & BT_ACCELERATE) ? 12 : 0);
}
else if (K_PlayerEBrake(player) == true)
{
// Spindashing
targetsnd = ((buttons & BT_DRIFT) ? 12 : 0);
}
else
{
// Average out the value of forwardmove and the speed that you're moving at.
targetsnd = (((6 * K_GetForwardMove(player)) / 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;
if (player->mo->health > 0 && !P_IsLocalPlayer(player)) // used to be !P_IsDisplayPlayer(player)
{
if (cv_kartinvinsfx.value)
{
if (player->invincibilitytimer > 0) // Prioritize invincibility
sfxnum = sfx_alarmi;
else if (player->growshrinktimer > 0)
sfxnum = sfx_alarmg;
}
else
{
if (player->invincibilitytimer > 0)
sfxnum = sfx_kinvnc;
else if (player->growshrinktimer > 0)
sfxnum = sfx_kgrow;
}
}
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 (player->karthud[khud_trickcool])
player->karthud[khud_trickcool]--;
if (!(player->pflags & PF_FAULT))
player->karthud[khud_fault] = 0;
else if (player->karthud[khud_fault] > 0 && player->karthud[khud_fault] < 2*TICRATE)
player->karthud[khud_fault]++;
if (player->karthud[khud_itemblink] && player->karthud[khud_itemblink]-- <= 0)
{
player->karthud[khud_itemblinkmode] = 0;
player->karthud[khud_itemblink] = 0;
}
if (gametype == GT_RACE)
{
// 0 is the fast spin animation, set at 30 tics of ring boost or higher!
if (player->ringboost >= 30)
player->karthud[khud_ringdelay] = 0;
else
player->karthud[khud_ringdelay] = ((RINGANIM_DELAYMAX+1) * (30 - player->ringboost)) / 30;
if (player->karthud[khud_ringframe] == 0 && player->karthud[khud_ringdelay] > RINGANIM_DELAYMAX)
{
player->karthud[khud_ringframe] = 0;
player->karthud[khud_ringtics] = 0;
}
else if ((player->karthud[khud_ringtics]--) <= 0)
{
if (player->karthud[khud_ringdelay] == 0) // fast spin animation
{
player->karthud[khud_ringframe] = ((player->karthud[khud_ringframe]+2) % RINGANIM_NUMFRAMES);
player->karthud[khud_ringtics] = 0;
}
else
{
player->karthud[khud_ringframe] = ((player->karthud[khud_ringframe]+1) % RINGANIM_NUMFRAMES);
player->karthud[khud_ringtics] = min(RINGANIM_DELAYMAX, player->karthud[khud_ringdelay])-1;
}
}
if (player->pflags & PF_RINGLOCK)
{
UINT8 normalanim = (leveltime % 14);
UINT8 debtanim = 14 + (leveltime % 2);
if (player->karthud[khud_ringspblock] >= 14) // debt animation
{
if ((player->rings > 0) // Get out of 0 ring animation
&& (normalanim == 3 || normalanim == 10)) // on these transition frames.
player->karthud[khud_ringspblock] = normalanim;
else
player->karthud[khud_ringspblock] = debtanim;
}
else // normal animation
{
if ((player->rings <= 0) // Go into 0 ring animation
&& (player->karthud[khud_ringspblock] == 1 || player->karthud[khud_ringspblock] == 8)) // on these transition frames.
player->karthud[khud_ringspblock] = debtanim;
else
player->karthud[khud_ringspblock] = normalanim;
}
}
else
player->karthud[khud_ringspblock] = (leveltime % 14); // reset to normal anim next time
}
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 (gametype == GT_RACE && 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 boolean PIT_AttractingRings(mobj_t *thing)
{
if (attractmo == NULL || P_MobjWasRemoved(attractmo) || attractmo->player == NULL)
{
return false;
}
if (thing == NULL || P_MobjWasRemoved(thing))
{
return true; // invalid
}
if (thing == attractmo)
{
return true; // invalid
}
if (!(thing->type == MT_RING || thing->type == MT_FLINGRING))
{
return true; // not a ring
}
if (thing->health <= 0)
{
return true; // dead
}
if (thing->extravalue1)
{
return true; // in special ring animation
}
if (thing->tracer != NULL && P_MobjWasRemoved(thing->tracer) == false)
{
return true; // already attracted
}
// see if it went over / under
if (attractmo->z - attractzdist > thing->z + thing->height)
{
return true; // overhead
}
if (attractmo->z + attractmo->height + attractzdist < thing->z)
{
return true; // underneath
}
if (P_AproxDistance(attractmo->x - thing->x, attractmo->y - thing->y) > attractdist + thing->radius)
{
return true; // 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<<FRACBITS, false);
thing->fuse = 120*TICRATE;
thing->cusval = 0; // Reset attraction flag
}
}
else
{
// set target
P_SetTarget(&thing->tracer, attractmo);
}
return true; // 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);
}
/** \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)
{
K_UpdateOffroad(player);
K_UpdateDraft(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
|| player->eggmanexplode || player->trickboost)
{
#if 0
if (player->invincibilitytimer)
K_SpawnInvincibilitySpeedLines(player->mo);
else
#endif
K_SpawnNormalSpeedLines(player);
}
if (player->numboosts > 0) // Boosting after images
{
mobj_t *ghost;
ghost = P_SpawnGhostMobj(player->mo);
ghost->extravalue1 = player->numboosts+1;
ghost->extravalue2 = (leveltime % ghost->extravalue1);
ghost->fuse = ghost->extravalue1;
ghost->renderflags |= RF_FULLBRIGHT;
ghost->colorized = true;
//ghost->color = player->skincolor;
//ghost->momx = (3*player->mo->momx)/4;
//ghost->momy = (3*player->mo->momy)/4;
//ghost->momz = (3*player->mo->momz)/4;
if (leveltime & 1)
ghost->renderflags |= RF_DONTDRAW;
}
// Could probably be moved somewhere else.
K_HandleFootstepParticles(player->mo);
if (P_IsObjectOnGround(player->mo))
{
// Draft dust
if (player->draftpower >= FRACUNIT)
{
K_SpawnDraftDust(player->mo);
/*if (leveltime % 23 == 0 || !S_SoundPlaying(player->mo, sfx_s265))
S_StartSound(player->mo, sfx_s265);*/
}
}
}
if (gametype == GT_RACE && player->rings <= 0) // spawn ring debt indicator
{
mobj_t *debtflag = P_SpawnMobj(player->mo->x + player->mo->momx, player->mo->y + player->mo->momy,
player->mo->z + P_GetMobjZMovement(player->mo) + player->mo->height + (24*player->mo->scale), MT_THOK);
P_SetMobjState(debtflag, S_RINGDEBT);
P_SetScale(debtflag, (debtflag->destscale = player->mo->scale));
K_MatchGenericExtraFlags(debtflag, player->mo);
debtflag->frame += (leveltime % 4);
if ((leveltime/12) & 1)
debtflag->frame += 4;
debtflag->color = player->skincolor;
debtflag->fuse = 2;
debtflag->renderflags = K_GetPlayerDontDrawFlag(player);
}
if (player->springstars && (leveltime & 1))
{
fixed_t randx = P_RandomRange(-40, 40) * player->mo->scale;
fixed_t randy = P_RandomRange(-40, 40) * player->mo->scale;
fixed_t randz = P_RandomRange(0, player->mo->height >> FRACBITS) << FRACBITS;
mobj_t *star = P_SpawnMobj(
player->mo->x + randx,
player->mo->y + randy,
player->mo->z + randz,
MT_KARMAFIREWORK);
star->color = player->springcolor;
star->flags |= MF_NOGRAVITY;
star->momx = player->mo->momx / 2;
star->momy = player->mo->momy / 2;
star->momz = P_GetMobjZMovement(player->mo) / 2;
star->fuse = 12;
star->scale = player->mo->scale;
star->destscale = star->scale / 2;
player->springstars--;
}
}
if (player->itemtype == KITEM_NONE)
player->pflags &= ~PF_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)
{
if (( player->spinouttype & KSPIN_IFRAMES ) == 0)
player->flashing = 0;
else
player->flashing = K_GetKartFlashing(player);
}
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 (player->rings > 20)
player->rings = 20;
else if (player->rings < -20)
player->rings = -20;
if (player->spheres > 40)
player->spheres = 40;
// where's the < 0 check? see below the following block!
if ((gametyperules & GTR_BUMPERS) && (player->bumpers <= 0))
{
// Deplete 1 every tic when removed from the game.
player->spheres--;
player->spheredigestion = 0;
}
else
{
tic_t spheredigestion = TICRATE; // Base rate of 1 every second when playing.
tic_t digestionpower = ((10 - player->kartspeed) + (10 - player->kartweight))-1; // 1 to 17
// currently 0-34
digestionpower = ((player->spheres*digestionpower)/20);
if (digestionpower >= spheredigestion)
{
spheredigestion = 1;
}
else
{
spheredigestion -= digestionpower;
}
if ((player->spheres > 0) && (player->spheredigestion > 0))
{
// If you got a massive boost in spheres, catch up digestion as necessary.
if (spheredigestion < player->spheredigestion)
{
player->spheredigestion = (spheredigestion + player->spheredigestion)/2;
}
player->spheredigestion--;
if (player->spheredigestion == 0)
{
player->spheres--;
player->spheredigestion = spheredigestion;
}
}
else
{
player->spheredigestion = spheredigestion;
}
}
// where's the > 40 check? see above the previous block!
if (player->spheres < 0)
player->spheres = 0;
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->bumpers <= 0 && player->karmadelay <= 0)
comebackshowninfo = true; // client has already seen the message
}
if (player->ringdelay)
player->ringdelay--;
if (P_PlayerInPain(player))
player->ringboost = 0;
else if (player->ringboost)
player->ringboost--;
if (player->sneakertimer)
{
player->sneakertimer--;
if (player->sneakertimer <= 0)
{
player->numsneakers = 0;
}
}
if (player->trickboost)
player->trickboost--;
if (player->flamedash)
player->flamedash--;
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--;
if (player->strongdriftboost)
player->strongdriftboost--;
if (player->startboost)
player->startboost--;
if (player->spindashboost)
{
player->spindashboost--;
if (player->spindashboost <= 0)
{
player->spindashspeed = player->spindashboost = 0;
}
}
if (player->invincibilitytimer)
player->invincibilitytimer--;
if ((player->respawn.state == RESPAWNST_NONE) && 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)
{
if (player->superring % 3 == 0)
{
mobj_t *ring = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RING);
ring->extravalue1 = 1; // Ring collect animation timer
P_InitAngle(ring, player->mo->angle); // animation angle
P_SetTarget(&ring->target, player->mo); // toucher for thinker
player->pickuprings++;
if (player->superring <= 3)
ring->cvmem = 1; // play caching when collected
}
player->superring--;
}
if (player->stealingtimer == 0
&& player->rocketsneakertimer)
player->rocketsneakertimer--;
if (player->hyudorotimer)
player->hyudorotimer--;
if (player->sadtimer)
player->sadtimer--;
if (player->stealingtimer > 0)
player->stealingtimer--;
else if (player->stealingtimer < 0)
player->stealingtimer++;
if (player->justbumped > 0)
player->justbumped--;
if (player->tiregrease)
player->tiregrease--;
if (player->tumbleBounces > 0)
{
K_HandleTumbleSound(player);
if (P_IsObjectOnGround(player->mo) && player->mo->momz * P_MobjFlip(player->mo) <= 0)
K_HandleTumbleBounce(player);
}
if (player->tripwireLeniency > 0)
{
player->tripwireLeniency--;
K_SpawnTripwireVFX(player->mo);
}
if (K_TripwirePassConditions(player) == true)
{
player->tripwireLeniency = max(player->tripwireLeniency, TICRATE);
}
K_KartPlayerHUDUpdate(player);
if (battleovertime.enabled && !(player->pflags & PF_ELIMINATED) && player->bumpers <= 0 && player->karmadelay <= 0)
{
if (player->overtimekarma)
player->overtimekarma--;
else
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_TIMEOVER);
}
if ((battleovertime.enabled >= 10*TICRATE) && !(player->pflags & PF_ELIMINATED) && !player->exiting)
{
fixed_t distanceToBarrier = 0;
if (battleovertime.radius > 0)
{
distanceToBarrier = R_PointToDist2(player->mo->x, player->mo->y, battleovertime.x, battleovertime.y) - (player->mo->radius * 2);
}
if (distanceToBarrier > battleovertime.radius)
{
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_TIMEOVER);
}
}
if (P_IsObjectOnGround(player->mo))
player->waterskip = 0;
if (player->instashield)
player->instashield--;
if (player->eggmanexplode)
{
if (player->spectator || (gametype == GT_BATTLE && !player->bumpers))
player->eggmanexplode = 0;
else
{
player->eggmanexplode--;
if (player->eggmanexplode <= 0)
{
mobj_t *eggsexplode;
//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;
}
if (player->itemtype != KITEM_FLAMESHIELD)
{
if (player->flamedash)
K_FlameDashLeftoverSmoke(player->mo);
}
if (P_IsObjectOnGround(player->mo) && player->trickpanel != 0)
{
if (P_MobjFlip(player->mo) * player->mo->momz <= 0)
{
player->trickpanel = 0;
}
}
if (cmd->buttons & BT_DRIFT)
{
// Only allow drifting while NOT trying to do an spindash input.
if ((K_GetKartButtons(player) & BT_EBRAKEMASK) != BT_EBRAKEMASK)
{
player->pflags |= PF_DRIFTINPUT;
}
// else, keep the previous value, because it might be brake-drifting.
}
else
{
player->pflags &= ~PF_DRIFTINPUT;
}
// Roulette Code
K_KartItemRoulette(player, cmd);
// Handle invincibility sfx
K_UpdateInvincibilitySounds(player); // Also thanks, VAda!
if (player->tripWireState != TRIP_NONE)
{
if (player->tripWireState == TRIP_PASSED)
S_StartSound(player->mo, sfx_cdfm63);
else if (player->tripWireState == TRIP_BLOCKED)
S_StartSound(player->mo, sfx_kc4c);
player->tripWireState = TRIP_NONE;
}
K_KartEbrakeVisuals(player);
if (K_GetKartButtons(player) & BT_BRAKE &&
P_IsObjectOnGround(player->mo) &&
K_GetKartSpeed(player, false) / 2 <= player->speed)
{
K_SpawnBrakeVisuals(player);
}
}
void K_KartPlayerAfterThink(player_t *player)
{
boolean fullbright = false;
if (player->playerstate == PST_DEAD || (player->respawn.state == RESPAWNST_MOVE)) // Ensure these are set correctly here
{
player->mo->colorized = (player->dye != 0);
player->mo->color = player->dye ? player->dye : player->skincolor;
}
else if (player->eggmanexplode) // You're gonna diiiiie
{
const INT32 flashtime = 4<<(player->eggmanexplode/TICRATE);
if (player->eggmanexplode == 1 || (player->eggmanexplode % (flashtime/2) != 0))
{
player->mo->colorized = (player->dye != 0);
player->mo->color = player->dye ? player->dye : player->skincolor;
}
else if (player->eggmanexplode % flashtime == 0)
{
player->mo->colorized = true;
player->mo->color = SKINCOLOR_BLACK;
fullbright = true;
}
else
{
player->mo->colorized = true;
player->mo->color = SKINCOLOR_CRIMSON;
fullbright = true;
}
}
else if (player->invincibilitytimer)
{
const tic_t defaultTime = itemtime+(2*TICRATE);
tic_t flicker = 2;
fullbright = true;
if (player->invincibilitytimer > defaultTime)
{
player->mo->color = K_RainbowColor(leveltime / 2);
player->mo->colorized = true;
}
else
{
player->mo->color = player->skincolor;
player->mo->colorized = false;
flicker += (defaultTime - player->invincibilitytimer) / TICRATE / 2;
}
if (leveltime % flicker == 0)
{
player->mo->color = SKINCOLOR_INVINCFLASH;
player->mo->colorized = true;
}
}
else 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;
}
else
{
player->mo->colorized = (player->dye != 0);
player->mo->color = player->dye ? player->dye : player->skincolor;
}
}
else if (player->ringboost && (leveltime & 1)) // ring boosting
{
player->mo->colorized = true;
fullbright = true;
}
else
{
player->mo->colorized = (player->dye != 0);
}
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;
}
// Move held objects (Bananas, Orbinaut, etc)
K_MoveHeldObjects(player);
// Jawz reticule (seeking)
if (player->itemtype == KITEM_JAWZ && (player->pflags & PF_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_LIGHTNINGSHIELD)
{
K_LookForRings(player->mo);
}
}
/*--------------------------------------------------
static waypoint_t *K_GetPlayerNextWaypoint(player_t *player)
Gets 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.
Input Arguments:-
player - The player the next waypoint is being found for
Return:-
The waypoint that is the player's next waypoint
--------------------------------------------------*/
static waypoint_t *K_GetPlayerNextWaypoint(player_t *player)
{
waypoint_t *bestwaypoint = NULL;
if ((player != NULL) && (player->mo != NULL) && (P_MobjWasRemoved(player->mo) == false))
{
waypoint_t *waypoint = K_GetBestWaypointForMobj(player->mo);
boolean updaterespawn = false;
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)
{
boolean finishlinehack = false;
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_MAX;
angle_t momdelta = ANGLE_MAX;
angledelta = playerangle - angletowaypoint;
if (angledelta > ANGLE_180)
{
angledelta = InvAngle(angledelta);
}
momdelta = momangle - angletowaypoint;
if (momdelta > ANGLE_180)
{
momdelta = InvAngle(momdelta);
}
if (bestwaypoint == K_GetFinishLineWaypoint())
{
waypoint_t *nextwaypoint = waypoint->nextwaypoints[0];
angle_t angletonextwaypoint =
R_PointToAngle2(waypoint->mobj->x, waypoint->mobj->y, nextwaypoint->mobj->x, nextwaypoint->mobj->y);
// facing towards the finishline
if (AngleDelta(angletonextwaypoint, angletowaypoint) <= ANGLE_90)
{
finishlinehack = true;
}
}
// 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 ((angledelta > ANGLE_45 || momdelta > ANGLE_45)
&& (finishlinehack == false))
{
angle_t nextbestdelta = angledelta;
angle_t nextbestmomdelta = momdelta;
size_t i = 0U;
if (K_PlayerUsesBotMovement(player))
{
// Try to force bots to use a next waypoint
nextbestdelta = ANGLE_MAX;
nextbestmomdelta = ANGLE_MAX;
}
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)
{
bestwaypoint = waypoint->nextwaypoints[i];
if (angledelta < nextbestdelta)
{
nextbestdelta = angledelta;
}
if (momdelta < nextbestmomdelta)
{
nextbestmomdelta = momdelta;
}
// Remove wrong way flag if we're using nextwaypoints
player->pflags &= ~PF_WRONGWAY;
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)
{
bestwaypoint = waypoint->prevwaypoints[i];
nextbestdelta = angledelta;
nextbestmomdelta = momdelta;
// Set wrong way flag if we're using prevwaypoints
player->pflags |= PF_WRONGWAY;
updaterespawn = false;
}
}
}
}
}
if (!P_IsObjectOnGround(player->mo))
{
updaterespawn = false;
}
// Respawn point should only be updated when we're going to a nextwaypoint
if ((updaterespawn) &&
(player->respawn.state == RESPAWNST_NONE) &&
(bestwaypoint != NULL) &&
(bestwaypoint != player->nextwaypoint) &&
(K_GetWaypointIsSpawnpoint(bestwaypoint)) &&
(K_GetWaypointIsEnabled(bestwaypoint) == true))
{
player->respawn.wp = bestwaypoint;
}
}
return bestwaypoint;
}
#if 0
static boolean K_PlayerCloserToNextWaypoints(waypoint_t *const waypoint, player_t *const player)
{
boolean nextiscloser = true;
if ((waypoint != NULL) && (player != NULL) && (player->mo != NULL))
{
size_t i = 0U;
waypoint_t *currentwpcheck = NULL;
angle_t angletoplayer = ANGLE_MAX;
angle_t currentanglecheck = ANGLE_MAX;
angle_t bestangle = ANGLE_MAX;
angletoplayer = R_PointToAngle2(waypoint->mobj->x, waypoint->mobj->y,
player->mo->x, player->mo->y);
for (i = 0U; i < waypoint->numnextwaypoints; i++)
{
currentwpcheck = waypoint->nextwaypoints[i];
currentanglecheck = R_PointToAngle2(
waypoint->mobj->x, waypoint->mobj->y, currentwpcheck->mobj->x, currentwpcheck->mobj->y);
// Get delta angle
currentanglecheck = currentanglecheck - angletoplayer;
if (currentanglecheck > ANGLE_180)
{
currentanglecheck = InvAngle(currentanglecheck);
}
if (currentanglecheck < bestangle)
{
bestangle = currentanglecheck;
}
}
for (i = 0U; i < waypoint->numprevwaypoints; i++)
{
currentwpcheck = waypoint->prevwaypoints[i];
currentanglecheck = R_PointToAngle2(
waypoint->mobj->x, waypoint->mobj->y, currentwpcheck->mobj->x, currentwpcheck->mobj->y);
// Get delta angle
currentanglecheck = currentanglecheck - angletoplayer;
if (currentanglecheck > ANGLE_180)
{
currentanglecheck = InvAngle(currentanglecheck);
}
if (currentanglecheck < bestangle)
{
bestangle = currentanglecheck;
nextiscloser = false;
break;
}
}
}
return nextiscloser;
}
#endif
/*--------------------------------------------------
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
--------------------------------------------------*/
void K_UpdateDistanceFromFinishLine(player_t *const player)
{
if ((player != NULL) && (player->mo != NULL))
{
waypoint_t *finishline = K_GetFinishLineWaypoint();
waypoint_t *nextwaypoint = NULL;
if (player->spectator)
{
// Don't update waypoints while spectating
nextwaypoint = finishline;
}
else
{
nextwaypoint = K_GetPlayerNextWaypoint(player);
}
if (nextwaypoint != NULL)
{
// 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.
player->nextwaypoint = nextwaypoint;
}
// nextwaypoint is now the waypoint that is in front of us
if (player->exiting || player->spectator)
{
// Player has finished, we don't need to calculate this
player->distancetofinish = 0U;
}
else if ((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)
{
// Add euclidean distance to the next waypoint to the distancetofinish
UINT32 adddist;
fixed_t disttowaypoint =
P_AproxDistance(
(player->mo->x >> FRACBITS) - (player->nextwaypoint->mobj->x >> FRACBITS),
(player->mo->y >> FRACBITS) - (player->nextwaypoint->mobj->y >> FRACBITS));
disttowaypoint = P_AproxDistance(disttowaypoint, (player->mo->z >> FRACBITS) - (player->nextwaypoint->mobj->z >> FRACBITS));
adddist = (UINT32)disttowaypoint;
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
if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) == 0U)
{
const UINT8 numfulllapsleft = ((UINT8)cv_numlaps.value - player->laps);
player->distancetofinish += numfulllapsleft * K_GetCircuitLength();
#if 0
// An additional HACK, to fix looking backwards towards the finish line
// If the player's next waypoint is the finishline and the angle distance from player to
// connectin waypoints implies they're closer to a next waypoint, add a full track distance
if (player->nextwaypoint == finishline)
{
if (K_PlayerCloserToNextWaypoints(player->nextwaypoint, player) == true)
{
player->distancetofinish += K_GetCircuitLength();
}
}
#endif
}
}
}
}
}
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) / MAXBOTDIFFICULTY;
}
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);
if (player->tiregrease > 0) // Buff drift-steering while in greasemode
{
basedrift += (basedrift / greasetics) * player->tiregrease;
}
#if 0
if (player->mo->eflags & MFE_UNDERWATER)
{
countersteer = FixedMul(countersteer, 3*FRACUNIT/2);
}
#endif
return basedrift + (FixedMul(driftadjust * FRACUNIT, countersteer) / FRACUNIT);
}
void K_UpdateSteeringValue(player_t *player, INT16 destSteering)
{
// player->steering is the turning value, but with easing applied.
// Keeps micro-turning from old easing, but isn't controller dependent.
const INT16 amount = KART_FULLTURN/4;
INT16 diff = destSteering - player->steering;
if (abs(diff) <= amount)
{
// Reached the intended value, set instantly.
player->steering = destSteering;
}
else
{
// Go linearly towards the value we wanted.
if (diff < 0)
{
player->steering -= amount;
}
else
{
player->steering += amount;
}
}
}
INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue)
{
fixed_t turnfixed = turnvalue * FRACUNIT;
fixed_t currentSpeed = 0;
fixed_t p_maxspeed = INT32_MAX, p_speed = INT32_MAX;
fixed_t weightadjust = INT32_MAX;
if (player->mo == NULL || P_MobjWasRemoved(player->mo) || player->spectator || objectplacing)
{
// Invalid object, or incorporeal player. Return the value exactly.
return turnvalue;
}
if (leveltime < introtime)
{
// No turning during the intro
return 0;
}
if (player->respawn.state == RESPAWNST_MOVE)
{
// No turning during respawn
return 0;
}
if (player->trickpanel != 0 && player->trickpanel < 4)
{
// No turning during trick panel unless you did the upwards trick (4)
return 0;
}
currentSpeed = FixedHypot(player->mo->momx, player->mo->momy);
if ((currentSpeed <= 0) // Not moving
&& ((K_GetKartButtons(player) & BT_EBRAKEMASK) != BT_EBRAKEMASK) // Not e-braking
&& (player->respawn.state == RESPAWNST_NONE) // Not respawning
&& (P_IsObjectOnGround(player->mo) == true)) // On the ground
{
return 0;
}
p_maxspeed = K_GetKartSpeed(player, false);
p_speed = min(currentSpeed, (p_maxspeed * 2));
weightadjust = FixedDiv((p_maxspeed * 3) - p_speed, (p_maxspeed * 3) + (player->kartweight * FRACUNIT));
if (K_PlayerUsesBotMovement(player))
{
turnfixed = FixedMul(turnfixed, 5*FRACUNIT/4); // Base increase to turning
turnfixed = FixedMul(turnfixed, K_BotRubberband(player));
}
if (player->drift != 0 && P_IsObjectOnGround(player->mo))
{
fixed_t countersteer = FixedDiv(turnfixed, KART_FULLTURN*FRACUNIT);
// If we're drifting we have a completely different turning value
if (player->pflags & PF_DRIFTEND)
{
countersteer = FRACUNIT;
}
return K_GetKartDriftValue(player, countersteer);
}
if (player->handleboost > 0)
{
turnfixed = FixedMul(turnfixed, FRACUNIT + player->handleboost);
}
if ((player->mo->eflags & MFE_UNDERWATER) &&
player->speed > 11 * player->mo->scale)
{
turnfixed /= 2;
}
// Weight has a small effect on turning
turnfixed = FixedMul(turnfixed, weightadjust);
return (turnfixed / FRACUNIT);
}
INT32 K_GetUnderwaterTurnAdjust(player_t *player)
{
if ((player->mo->eflags & MFE_UNDERWATER) &&
player->speed > 11 * player->mo->scale)
{
INT32 steer = (K_GetKartTurnValue(player,
player->steering) << TICCMD_REDUCE);
if (!player->drift)
steer = 9 * steer / 5;
return FixedMul(steer, 8 * FixedDiv(player->speed,
2 * K_GetKartSpeed(player, false) / 3));
}
else
return 0;
}
INT32 K_GetKartDriftSparkValue(player_t *player)
{
return (26*4 + player->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);
}
/*
Stage 1: red sparks
Stage 2: blue sparks
Stage 3: purple sparks
Stage 4: big large rainbow sparks
Stage 0: air failsafe
*/
void K_SpawnDriftBoostExplosion(player_t *player, int stage)
{
mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_DRIFTEXPLODE);
P_InitAngle(overlay, K_MomentumAngle(player->mo));
P_SetTarget(&overlay->target, player->mo);
P_SetScale(overlay, (overlay->destscale = player->mo->scale));
K_FlipFromObject(overlay, player->mo);
switch (stage)
{
case 1:
overlay->color = SKINCOLOR_KETCHUP;
overlay->fuse = 16;
break;
case 2:
overlay->color = SKINCOLOR_SAPPHIRE;
overlay->fuse = 32;
S_StartSound(player->mo, sfx_kc5b);
break;
case 3:
overlay->color = SKINCOLOR_PURPLE;
overlay->fuse = 48;
S_StartSound(player->mo, sfx_kc5b);
break;
case 4:
overlay->color = SKINCOLOR_SILVER;
overlay->fuse = 120;
S_StartSound(player->mo, sfx_kc5b);
S_StartSound(player->mo, sfx_s3kc4l);
break;
case 0:
overlay->color = SKINCOLOR_SILVER;
overlay->fuse = 16;
break;
}
overlay->extravalue1 = stage;
}
static void K_KartDrift(player_t *player, boolean onground)
{
const fixed_t minspeed = (10 * player->mo->scale);
const INT32 dsone = K_GetKartDriftSparkValueForStage(player, 1);
const INT32 dstwo = K_GetKartDriftSparkValueForStage(player, 2);
const INT32 dsthree = K_GetKartDriftSparkValueForStage(player, 3);
const INT32 dsfour = K_GetKartDriftSparkValueForStage(player, 4);
const UINT16 buttons = K_GetKartButtons(player);
// Drifting is actually straffing + automatic turning.
// Holding the Jump button will enable drifting.
// (This comment is extremely funny)
// Drift Release (Moved here so you can't "chain" drifts)
if (player->drift != -5 && player->drift != 5)
{
if (player->driftcharge < 0 || player->driftcharge >= dsone)
{
angle_t pushdir = K_MomentumAngle(player->mo);
S_StartSound(player->mo, sfx_s23c);
//K_SpawnDashDustRelease(player);
if (player->driftcharge < 0)
{
// Stage 0: Yellow sparks
if (!onground)
P_Thrust(player->mo, pushdir, player->speed / 8);
if (player->driftboost < 15)
player->driftboost = 15;
}
else if (player->driftcharge >= dsone && player->driftcharge < dstwo)
{
// Stage 1: Red sparks
if (!onground)
P_Thrust(player->mo, pushdir, player->speed / 4);
if (player->driftboost < 20)
player->driftboost = 20;
K_SpawnDriftBoostExplosion(player, 1);
}
else if (player->driftcharge < dsthree)
{
// Stage 2: Blue sparks
if (!onground)
P_Thrust(player->mo, pushdir, player->speed / 3);
if (player->driftboost < 50)
player->driftboost = 50;
K_SpawnDriftBoostExplosion(player, 2);
}
else if (player->driftcharge < dsfour)
{
// Stage 3: Purple sparks
if (!onground)
P_Thrust(player->mo, pushdir, ( 5 * player->speed ) / 12);
if (player->driftboost < 85)
player->driftboost = 85;
if (player->strongdriftboost < 85)
player->strongdriftboost = 85;
K_SpawnDriftBoostExplosion(player, 3);
K_SpawnDriftElectricSparks(player);
}
else if (player->driftcharge >= dsfour)
{
// Stage 4: Rainbow sparks
if (!onground)
P_Thrust(player->mo, pushdir, player->speed / 2);
if (player->driftboost < 125)
player->driftboost = 125;
if (player->strongdriftboost < 125)
player->strongdriftboost = 125;
K_SpawnDriftBoostExplosion(player, 4);
K_SpawnDriftElectricSparks(player);
}
}
// Remove charge
player->driftcharge = 0;
}
// Drifting: left or right?
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;
}
else if (player->speed > minspeed
&& (player->drift == 0 || (player->pflags & PF_DRIFTEND)))
{
// Uses turning over steering, since this is very binary.
// Using steering would cause a lot more "wrong drifts".
if (player->cmd.turning > 0)
{
// Starting left drift
player->drift = 1;
player->driftcharge = 0;
player->pflags &= ~PF_DRIFTEND;
}
else if (player->cmd.turning < 0)
{
// Starting right drift
player->drift = -1;
player->driftcharge = 0;
player->pflags &= ~PF_DRIFTEND;
}
}
if (P_PlayerInPain(player) || player->speed <= 0)
{
// Stop drifting
player->drift = player->driftcharge = player->aizdriftstrat = 0;
player->pflags &= ~(PF_BRAKEDRIFT|PF_GETSPARKS);
}
else if ((player->pflags & PF_DRIFTINPUT) && player->drift != 0)
{
// Incease/decrease the drift value to continue drifting in that direction
fixed_t driftadditive = 24;
boolean playsound = false;
if (onground)
{
if (player->drift >= 1) // Drifting to the left
{
player->drift++;
if (player->drift > 5)
player->drift = 5;
if (player->steering > 0) // Inward
driftadditive += abs(player->steering)/100;
if (player->steering < 0) // Outward
driftadditive -= abs(player->steering)/75;
}
else if (player->drift <= -1) // Drifting to the right
{
player->drift--;
if (player->drift < -5)
player->drift = -5;
if (player->steering < 0) // Inward
driftadditive += abs(player->steering)/100;
if (player->steering > 0) // Outward
driftadditive -= abs(player->steering)/75;
}
// Disable drift-sparks until you're going fast enough
if (!(player->pflags & PF_GETSPARKS)
|| (player->offroad && K_ApplyOffroad(player)))
driftadditive = 0;
// Inbetween minspeed and minspeed*2, it'll keep your previous drift-spark state.
if (player->speed > minspeed*2)
{
player->pflags |= PF_GETSPARKS;
if (player->driftcharge <= -1)
{
player->driftcharge = dsone; // Back to red
playsound = true;
}
}
else if (player->speed <= minspeed)
{
player->pflags &= ~PF_GETSPARKS;
driftadditive = 0;
if (player->driftcharge >= dsone)
{
player->driftcharge = -1; // Set yellow sparks
playsound = true;
}
}
}
else
{
driftadditive = 0;
}
// This spawns the drift sparks
if ((player->driftcharge + driftadditive >= dsone)
|| (player->driftcharge < 0))
{
K_SpawnDriftSparks(player);
}
if ((player->driftcharge < dsone && player->driftcharge+driftadditive >= dsone)
|| (player->driftcharge < dstwo && player->driftcharge+driftadditive >= dstwo)
|| (player->driftcharge < dsthree && player->driftcharge+driftadditive >= dsthree))
{
playsound = true;
}
// Sound whenever you get a different tier of sparks
if (playsound && P_IsDisplayPlayer(player))
{
if (player->driftcharge == -1)
S_StartSoundAtVolume(player->mo, sfx_sploss, 192); // Yellow spark sound
else
S_StartSoundAtVolume(player->mo, sfx_s3ka2, 192);
}
player->driftcharge += driftadditive;
player->pflags &= ~PF_DRIFTEND;
}
if ((player->handleboost == 0)
|| (!player->steering)
|| (!player->aizdriftstrat)
|| (player->steering > 0) != (player->aizdriftstrat > 0))
{
if (!player->drift)
player->aizdriftstrat = 0;
else
player->aizdriftstrat = ((player->drift > 0) ? 1 : -1);
}
else if (player->aizdriftstrat && !player->drift)
{
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;
}
//
// 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
{
UINT8 myEmeralds = K_NumEmeralds(player);
UINT8 yourEmeralds = K_NumEmeralds(&players[i]);
if (yourEmeralds > myEmeralds)
{
// Emeralds matter above all
position++;
}
else if (yourEmeralds == myEmeralds)
{
// Bumpers are a tie breaker
if (players[i].bumpers > player->bumpers)
{
position++;
}
else if (players[i].bumpers == player->bumpers)
{
// Score is the second tier tie breaker
if (players[i].roundscore > player->roundscore)
{
position++;
}
}
}
}
}
}
if (leveltime < starttime || oldposition == 0)
oldposition = position;
if (oldposition != position) // Changed places?
player->positiondelay = 10; // Position number growth
player->position = position;
}
//
// K_StripItems
//
void K_StripItems(player_t *player)
{
K_DropRocketSneaker(player);
K_DropKitchenSink(player);
player->itemtype = KITEM_NONE;
player->itemamount = 0;
player->pflags &= ~(PF_ITEMOUT|PF_EGGMANOUT);
if (!player->itemroulette || player->roulettetype != 2)
{
player->itemroulette = 0;
player->roulettetype = 0;
}
player->hyudorotimer = 0;
player->stealingtimer = 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;
}
}
static INT32 K_FlameShieldMax(player_t *player)
{
UINT32 disttofinish = 0;
UINT32 distv = DISTVAR;
UINT8 numplayers = 0;
UINT8 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && !players[i].spectator)
numplayers++;
if (players[i].position == 1)
disttofinish = players[i].distancetofinish;
}
if (numplayers <= 1)
{
return 16; // max when alone, for testing
}
else if (player->position == 1)
{
return 0; // minimum for first
}
disttofinish = player->distancetofinish - disttofinish;
distv = FixedMul(distv * FRACUNIT, mapobjectscale) / FRACUNIT;
return min(16, 1 + (disttofinish / distv));
}
boolean K_PlayerEBrake(player_t *player)
{
return (K_GetKartButtons(player) & BT_EBRAKEMASK) == BT_EBRAKEMASK
&& P_IsObjectOnGround(player->mo) == true
&& player->drift == 0
&& player->spinouttimer == 0
&& player->justbumped == 0
&& player->spindashboost == 0
&& player->nocontrol == 0;
}
SINT8 K_Sliptiding(player_t *player)
{
return player->drift ? 0 : player->aizdriftstrat;
}
INT32 K_StairJankFlip(INT32 value)
{
return P_AltFlip(value, 2);
}
// Ebraking visuals for mo
// we use mo->hprev for the hold bubble. If another hprev exists for some reason, remove it.
void K_KartEbrakeVisuals(player_t *p)
{
mobj_t *wave;
mobj_t *spdl;
fixed_t sx, sy;
if (K_PlayerEBrake(p))
{
if (p->ebrakefor % 20 == 0)
{
wave = P_SpawnMobj(p->mo->x, p->mo->y, p->mo->z, MT_SOFTLANDING);
P_SetScale(wave, p->mo->scale);
wave->momx = p->mo->momx;
wave->momy = p->mo->momy;
wave->momz = p->mo->momz;
wave->standingslope = p->mo->standingslope;
}
// sound
if (!S_SoundPlaying(p->mo, sfx_s3kd9s))
S_StartSound(p->mo, sfx_s3kd9s);
// HOLD! bubble.
if (!p->ebrakefor)
{
if (p->mo->hprev && !P_MobjWasRemoved(p->mo->hprev))
{
// for some reason, there's already an hprev. Remove it.
P_RemoveMobj(p->mo->hprev);
}
p->mo->hprev = P_SpawnMobj(p->mo->x, p->mo->y, p->mo->z, MT_HOLDBUBBLE);
p->mo->hprev->renderflags |= (RF_DONTDRAW & ~K_GetPlayerDontDrawFlag(p));
}
// Update HOLD bubble.
if (p->mo->hprev && !P_MobjWasRemoved(p->mo->hprev))
{
P_MoveOrigin(p->mo->hprev, p->mo->x, p->mo->y, p->mo->z);
p->mo->hprev->angle = p->mo->angle;
p->mo->hprev->fuse = TICRATE/2; // When we leave spindash for any reason, make sure this bubble goes away soon after.
K_FlipFromObject(p->mo->hprev, p->mo);
}
if (!p->spindash)
{
// Spawn downwards fastline
sx = p->mo->x + P_RandomRange(-48, 48)*p->mo->scale;
sy = p->mo->y + P_RandomRange(-48, 48)*p->mo->scale;
spdl = P_SpawnMobj(sx, sy, p->mo->z, MT_DOWNLINE);
spdl->colorized = true;
spdl->color = SKINCOLOR_WHITE;
K_MatchGenericExtraFlags(spdl, p->mo);
P_SetScale(spdl, p->mo->scale);
// squish the player a little bit.
p->mo->spritexscale = FRACUNIT*115/100;
p->mo->spriteyscale = FRACUNIT*85/100;
}
else
{
const UINT16 MAXCHARGETIME = K_GetSpindashChargeTime(p);
const fixed_t MAXSHAKE = FRACUNIT;
// update HOLD bubble with numbers based on charge.
if (p->mo->hprev && !P_MobjWasRemoved(p->mo->hprev))
{
UINT8 frame = min(1 + ((p->spindash*3) / MAXCHARGETIME), 4);
// ?! limit.
if (p->spindash >= MAXCHARGETIME +TICRATE)
frame = 5;
p->mo->hprev->frame = frame|FF_FULLBRIGHT;
}
// shake the player as they charge their spindash!
// "gentle" shaking as we start...
if (p->spindash < MAXCHARGETIME)
{
fixed_t shake = FixedMul(((p->spindash)*FRACUNIT/MAXCHARGETIME), MAXSHAKE);
SINT8 mult = leveltime & 1 ? 1 : -1;
p->mo->spritexoffset = shake*mult;
}
else // get VIOLENT on overcharge :)
{
fixed_t shake = MAXSHAKE + FixedMul(((p->spindash-MAXCHARGETIME)*FRACUNIT/TICRATE), MAXSHAKE)*3;
SINT8 mult = leveltime & 1 ? 1 : -1;
p->mo->spritexoffset = shake*mult;
}
// sqish them a little MORE....
p->mo->spritexscale = FRACUNIT*12/10;
p->mo->spriteyscale = FRACUNIT*8/10;
}
p->ebrakefor++;
}
else if (p->ebrakefor) // cancel effects
{
// reset scale
p->mo->spritexscale = FRACUNIT;
p->mo->spriteyscale = FRACUNIT;
// reset shake
p->mo->spritexoffset = 0;
// remove the bubble instantly unless it's in the !? state
if (p->mo->hprev && !P_MobjWasRemoved(p->mo->hprev) && (p->mo->hprev->frame & FF_FRAMEMASK) != 5)
{
P_RemoveMobj(p->mo->hprev);
p->mo->hprev = NULL;
}
p->ebrakefor = 0;
}
}
static void K_KartSpindashDust(mobj_t *parent)
{
fixed_t rad = FixedDiv(FixedHypot(parent->radius, parent->radius), parent->scale);
INT32 i;
for (i = 0; i < 2; i++)
{
fixed_t hmomentum = P_RandomRange(6, 12) * parent->scale;
fixed_t vmomentum = P_RandomRange(2, 6) * parent->scale;
angle_t ang = parent->player->drawangle + ANGLE_180;
SINT8 flip = 1;
mobj_t *dust;
if (i & 1)
ang -= ANGLE_45;
else
ang += ANGLE_45;
dust = P_SpawnMobjFromMobj(parent,
FixedMul(rad, FINECOSINE(ang >> ANGLETOFINESHIFT)),
FixedMul(rad, FINESINE(ang >> ANGLETOFINESHIFT)),
0, MT_SPINDASHDUST
);
flip = P_MobjFlip(dust);
dust->momx = FixedMul(hmomentum, FINECOSINE(ang >> ANGLETOFINESHIFT));
dust->momy = FixedMul(hmomentum, FINESINE(ang >> ANGLETOFINESHIFT));
dust->momz = vmomentum * flip;
}
}
static void K_KartSpindashWind(mobj_t *parent)
{
mobj_t *wind = P_SpawnMobjFromMobj(parent,
P_RandomRange(-36,36) * FRACUNIT,
P_RandomRange(-36,36) * FRACUNIT,
FixedDiv(parent->height / 2, parent->scale) + (P_RandomRange(-20,20) * FRACUNIT),
MT_SPINDASHWIND
);
P_SetTarget(&wind->target, parent);
if (parent->momx || parent->momy)
P_InitAngle(wind, R_PointToAngle2(0, 0, parent->momx, parent->momy));
else
P_InitAngle(wind, parent->player->drawangle);
wind->momx = 3 * parent->momx / 4;
wind->momy = 3 * parent->momy / 4;
wind->momz = 3 * P_GetMobjZMovement(parent) / 4;
K_MatchGenericExtraFlags(wind, parent);
}
// Time after which you get a thrust for releasing spindash
#define SPINDASHTHRUSTTIME 20
static void K_KartSpindash(player_t *player)
{
const INT16 MAXCHARGETIME = K_GetSpindashChargeTime(player);
ticcmd_t *cmd = &player->cmd;
boolean spawnWind = (leveltime % 2 == 0);
if (player->mo->hitlag > 0 || P_PlayerInPain(player))
{
player->spindash = 0;
}
if (player->spindash > 0 && (cmd->buttons & (BT_DRIFT|BT_BRAKE|BT_ACCELERATE)) != (BT_DRIFT|BT_BRAKE|BT_ACCELERATE))
{
player->spindashspeed = (player->spindash * FRACUNIT) / MAXCHARGETIME;
player->spindashboost = TICRATE;
// if spindash was charged enough, give a small thrust.
if (player->spindash >= SPINDASHTHRUSTTIME)
{
// Give a bit of a boost depending on charge.
P_InstaThrust(player->mo, player->mo->angle, FixedMul(player->mo->scale, player->spindash*FRACUNIT/5));
}
if (!player->tiregrease)
{
UINT8 i;
for (i = 0; i < 2; i++)
{
mobj_t *grease;
grease = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_TIREGREASE);
P_SetTarget(&grease->target, player->mo);
P_InitAngle(grease, K_MomentumAngle(player->mo));
grease->extravalue1 = i;
}
}
player->tiregrease = 2*TICRATE;
player->spindash = 0;
S_StartSound(player->mo, sfx_s23c);
}
if ((player->spindashboost > 0) && (spawnWind == true))
{
K_KartSpindashWind(player->mo);
}
if (player->spindashboost > (TICRATE/2))
{
K_KartSpindashDust(player->mo);
}
if (K_PlayerEBrake(player) == false)
{
player->spindash = 0;
return;
}
if (player->speed == 0 && player->steering != 0 && leveltime % 8 == 0)
{
// Rubber burn turn sfx
S_StartSound(player->mo, sfx_ruburn);
}
if (player->speed < 6*player->mo->scale)
{
if ((cmd->buttons & (BT_DRIFT|BT_BRAKE)) == (BT_DRIFT|BT_BRAKE))
{
UINT8 ringdropframes = 2 + (player->kartspeed + player->kartweight);
INT16 chargetime = MAXCHARGETIME - ++player->spindash;
boolean spawnOldEffect = true;
if (player->spindash >= SPINDASHTHRUSTTIME)
{
K_KartSpindashDust(player->mo);
spawnOldEffect = false;
}
if (chargetime <= (MAXCHARGETIME / 4) && spawnWind == true)
{
K_KartSpindashWind(player->mo);
}
if (player->flashing > 0 && (player->spindash % ringdropframes == 0) && player->hyudorotimer == 0)
{
// Every frame that you're invisible from flashing, spill a ring.
// Intentionally a lop-sided trade-off, so the game doesn't become
// Funky Kong's Ring Racers.
P_PlayerRingBurst(player, 1);
}
if (chargetime > 0)
{
UINT16 soundcharge = 0;
UINT8 add = 0;
while ((soundcharge += ++add) < chargetime);
if (soundcharge == chargetime)
{
if (spawnOldEffect == true)
K_SpawnDashDustRelease(player);
S_StartSound(player->mo, sfx_s3kab);
}
}
else if (chargetime < -TICRATE)
{
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_NORMAL);
}
}
}
else
{
if (leveltime % 4 == 0)
S_StartSound(player->mo, sfx_kc2b);
}
}
#undef SPINDASHTHRUSTTIME
static void K_AirFailsafe(player_t *player)
{
const fixed_t maxSpeed = 6*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.state != RESPAWNST_NONE) // Respawning, you don't need this AND drop dash :V
{
player->pflags &= ~PF_AIRFAILSAFE;
return;
}
if ((K_GetKartButtons(player) & BT_ACCELERATE) || K_GetForwardMove(player) != 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_SpawnDriftBoostExplosion(player, 0);
player->pflags &= ~PF_AIRFAILSAFE;
}
}
//
// K_AdjustPlayerFriction
//
void K_AdjustPlayerFriction(player_t *player)
{
fixed_t prevfriction = player->mo->friction;
if (P_IsObjectOnGround(player->mo) == false)
{
return;
}
// Reduce friction after hitting a spring
if (player->tiregrease)
{
player->mo->friction += ((FRACUNIT - prevfriction) / greasetics) * player->tiregrease;
}
/*
if (K_PlayerEBrake(player) == true)
{
player->mo->friction -= 1024;
}
else if (player->speed > 0 && K_GetForwardMove(player) < 0)
{
player->mo->friction -= 512;
}
*/
// Water gets ice physics too
if ((player->mo->eflags & MFE_TOUCHWATER) &&
!player->offroad)
{
player->mo->friction += 614;
}
else if (player->mo->eflags & MFE_UNDERWATER)
{
player->mo->friction += 312;
}
// Wipeout slowdown
if (player->spinouttimer && player->wipeoutslow)
{
if (player->offroad)
player->mo->friction -= 4912;
if (player->wipeoutslow == 1)
player->mo->friction -= 9824;
}
// Cap between intended values
if (player->mo->friction > FRACUNIT)
player->mo->friction = FRACUNIT;
if (player->mo->friction < 0)
player->mo->friction = 0;
// Friction was changed, so we must recalculate movefactor
if (player->mo->friction != prevfriction)
{
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;
}
// Don't go too far above your top speed when rubberbanding
// Down here, because we do NOT want to modify movefactor
if (K_PlayerUsesBotMovement(player))
{
player->mo->friction = K_BotFrictionRubberband(player, player->mo->friction);
}
}
//
// K_trickPanelTimingVisual
// Spawns the timing visual for trick panels depending on the given player's momz.
// If the player has tricked and is not in hitlag, this will send the half circles flying out.
// if you tumble, they'll fall off instead.
//
#define RADIUSSCALING 6
#define MINRADIUS 12
static void K_trickPanelTimingVisual(player_t *player, fixed_t momz)
{
fixed_t pos, tx, ty, tz;
mobj_t *flame;
angle_t hang = R_PointToAnglePlayer(player, player->mo->x, player->mo->y) + ANG1*90; // horizontal angle
angle_t vang = -FixedAngle(momz)*12 + (ANG1*45); // vertical angle dependant on momz, we want it to line up at 45 degrees at the perfect frame to trick at
fixed_t dist = FixedMul(max(MINRADIUS<<FRACBITS, abs(momz)*RADIUSSCALING), player->mo->scale); // distance.
UINT8 i;
// Do you like trig? cool, me neither.
for (i=0; i < 2; i++)
{
pos = FixedMul(dist, FINESINE(vang>>ANGLETOFINESHIFT));
tx = player->mo->x + FixedMul(pos, FINECOSINE(hang>>ANGLETOFINESHIFT));
ty = player->mo->y + FixedMul(pos, FINESINE(hang>>ANGLETOFINESHIFT));
tz = player->mo->z + player->mo->height/2 + FixedMul(dist, FINECOSINE(vang>>ANGLETOFINESHIFT));
// All coordinates set, spawn our fire, now.
flame = P_SpawnMobj(tx, ty, tz, MT_THOK);
P_SetScale(flame, player->mo->scale);
// Visuals
flame->sprite = SPR_TRCK;
flame->frame = i|FF_FULLBRIGHT;
if (player->trickpanel <= 1 && !player->tumbleBounces)
{
flame->tics = 2;
flame->momx = player->mo->momx;
flame->momy = player->mo->momy;
flame->momz = player->mo->momz;
}
else
{
flame->tics = TICRATE;
if (player->trickpanel > 1) // we tricked
{
// Send the thing outwards via ghetto maths which involves redoing the whole 3d sphere again, witht the "vertical" angle shifted by 90 degrees.
// There's probably a simplier way to do this the way I want to but this works.
pos = FixedMul(48*player->mo->scale, FINESINE((vang +ANG1*90)>>ANGLETOFINESHIFT));
tx = player->mo->x + FixedMul(pos, FINECOSINE(hang>>ANGLETOFINESHIFT));
ty = player->mo->y + FixedMul(pos, FINESINE(hang>>ANGLETOFINESHIFT));
tz = player->mo->z + player->mo->height/2 + FixedMul(48*player->mo->scale, FINECOSINE((vang +ANG1*90)>>ANGLETOFINESHIFT));
flame->momx = tx -player->mo->x;
flame->momy = ty -player->mo->y;
flame->momz = tz -(player->mo->z+player->mo->height/2);
}
else // we failed the trick, drop the half circles, it'll be funny I promise.
{
flame->flags &= ~MF_NOGRAVITY;
P_SetObjectMomZ(flame, 4<<FRACBITS, false);
P_InstaThrust(flame, R_PointToAngle2(player->mo->x, player->mo->y, flame->x, flame->y), 8*mapobjectscale);
flame->momx += player->mo->momx;
flame->momy += player->mo->momy;
flame->momz += player->mo->momz;
}
}
// make sure this is only drawn for our local player
flame->renderflags |= (RF_DONTDRAW & ~K_GetPlayerDontDrawFlag(player));
vang += FixedAngle(180<<FRACBITS); // Avoid overflow warnings...
}
}
#undef RADIUSSCALING
#undef MINRADIUS
void K_SetItemOut(player_t *player)
{
player->pflags |= PF_ITEMOUT;
if (player->mo->scale >= FixedMul(GROW_PHYSICS_SCALE, mapobjectscale))
{
player->itemscale = ITEMSCALE_GROW;
}
else if (player->mo->scale <= FixedMul(SHRINK_PHYSICS_SCALE, mapobjectscale))
{
player->itemscale = ITEMSCALE_SHRINK;
}
else
{
player->itemscale = ITEMSCALE_NORMAL;
}
}
void K_UnsetItemOut(player_t *player)
{
player->pflags &= ~PF_ITEMOUT;
player->itemscale = ITEMSCALE_NORMAL;
player->bananadrag = 0;
}
//
// K_MoveKartPlayer
//
void K_MoveKartPlayer(player_t *player, boolean onground)
{
ticcmd_t *cmd = &player->cmd;
boolean ATTACK_IS_DOWN = ((cmd->buttons & BT_ATTACK) && !(player->pflags & PF_ATTACKDOWN));
boolean HOLDING_ITEM = (player->pflags & (PF_ITEMOUT|PF_EGGMANOUT));
boolean NO_HYUDORO = (player->stealingtimer == 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 (!(cmd->buttons & BT_ATTACK))
{
if (player->itemtype == KITEM_NONE
&& NO_HYUDORO && !(HOLDING_ITEM
|| player->itemamount
|| player->itemroulette
|| player->rocketsneakertimer
|| player->eggmanexplode))
player->pflags |= PF_USERINGS;
else
player->pflags &= ~PF_USERINGS;
}
if ((player->pflags & PF_ATTACKDOWN) && !(cmd->buttons & BT_ATTACK))
player->pflags &= ~PF_ATTACKDOWN;
else if (cmd->buttons & BT_ATTACK)
player->pflags |= PF_ATTACKDOWN;
if (player && player->mo && player->mo->health > 0 && !player->spectator && !P_PlayerInPain(player) && !mapreset && leveltime > introtime)
{
// First, the really specific, finicky items that function without the item being directly in your item slot.
{
// Ring boosting
if (player->pflags & PF_USERINGS)
{
if ((player->pflags & PF_ATTACKDOWN) && !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);
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;
}
// Eggman Monitor throwing
else if (player->pflags & PF_EGGMANOUT)
{
if (ATTACK_IS_DOWN)
{
K_ThrowKartItem(player, false, MT_EGGMANITEM, -1, 0);
K_PlayAttackTaunt(player->mo);
player->pflags &= ~PF_EGGMANOUT;
K_UpdateHnextList(player, true);
}
}
// 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;
}
}
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--;
}
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;
P_InitAngle(mo, 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;
}
}
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
{
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 += itemtime+(2*TICRATE); // 10 seconds
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);
K_PlayPowerGloatSound(player->mo);
player->itemamount--;
}
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;
mo->cusval = player->itemscale;
P_SetTarget(&mo->target, player->mo);
P_SetTarget(&mo->hprev, prev);
P_SetTarget(&prev->hnext, mo);
prev = mo;
}
}
else if (ATTACK_IS_DOWN && (player->pflags & PF_ITEMOUT)) // Banana x3 thrown
{
K_ThrowKartItem(player, false, MT_BANANA, -1, 0);
K_PlayAttackTaunt(player->mo);
player->itemamount--;
K_UpdateHnextList(player, false);
}
break;
case KITEM_EGGMAN:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
mobj_t *mo;
player->itemamount--;
player->pflags |= PF_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;
mo->cusval = player->itemscale;
P_SetTarget(&mo->target, player->mo);
P_SetTarget(&player->mo->hnext, mo);
}
}
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;
P_InitAngle(mo, newangle);
mo->threshold = 10;
mo->movecount = player->itemamount;
mo->movedir = mo->lastlook = moloop+1;
mo->color = player->skincolor;
mo->cusval = player->itemscale;
P_SetTarget(&mo->target, player->mo);
P_SetTarget(&mo->hprev, prev);
P_SetTarget(&prev->hnext, mo);
prev = mo;
}
}
else if (ATTACK_IS_DOWN && (player->pflags & PF_ITEMOUT)) // Orbinaut x3 thrown
{
K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0);
K_PlayAttackTaunt(player->mo);
player->itemamount--;
K_UpdateHnextList(player, false);
}
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;
P_InitAngle(mo, newangle);
mo->threshold = 10;
mo->movecount = player->itemamount;
mo->movedir = mo->lastlook = moloop+1;
mo->cusval = player->itemscale;
P_SetTarget(&mo->target, player->mo);
P_SetTarget(&mo->hprev, prev);
P_SetTarget(&prev->hnext, mo);
prev = mo;
}
}
else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->pflags & PF_ITEMOUT)) // Jawz thrown
{
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);
player->itemamount--;
K_UpdateHnextList(player, false);
}
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;
mo->cusval = player->itemscale;
P_SetTarget(&mo->target, player->mo);
P_SetTarget(&player->mo->hnext, mo);
}
}
else if (ATTACK_IS_DOWN && (player->pflags & PF_ITEMOUT))
{
K_ThrowKartItem(player, false, MT_SSMINE, 1, 1);
K_PlayAttackTaunt(player->mo);
player->itemamount--;
player->pflags &= ~PF_ITEMOUT;
K_UpdateHnextList(player, true);
}
break;
case KITEM_LANDMINE:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
player->itemamount--;
K_ThrowLandMine(player);
K_PlayAttackTaunt(player->mo);
}
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;
mo->cusval = player->itemscale;
P_SetTarget(&mo->target, player->mo);
P_SetTarget(&player->mo->hnext, mo);
}
}
else if (ATTACK_IS_DOWN && (player->pflags & PF_ITEMOUT))
{
K_ThrowKartItem(player, (player->throwdir > 0), MT_DROPTARGET, -1, 0);
K_PlayAttackTaunt(player->mo);
player->itemamount--;
player->pflags &= ~PF_ITEMOUT;
K_UpdateHnextList(player, true);
}
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);
}
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);
}
break;
case KITEM_GROW:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
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);
}
player->growshrinktimer = itemtime+(4*TICRATE); // 12 seconds
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->itemamount--;
}
break;
case KITEM_SHRINK:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
K_DoShrink(player);
player->itemamount--;
K_PlayPowerGloatSound(player->mo);
}
break;
case KITEM_LIGHTNINGSHIELD:
if (player->curshield != KSHIELD_LIGHTNING)
{
mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_LIGHTNINGSHIELD);
P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2));
P_SetTarget(&shield->target, player->mo);
S_StartSound(player->mo, sfx_s3k41);
player->curshield = KSHIELD_LIGHTNING;
}
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
K_DoLightningShield(player);
if (player->itemamount > 0)
{
// Why is this a conditional?
// Lightning 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);
}
}
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 ((cmd->buttons & BT_ATTACK) && (player->pflags & PF_HOLDREADY))
{
if (player->bubbleblowup == 0)
S_StartSound(player->mo, sfx_s3k75);
player->bubbleblowup++;
player->bubblecool = player->bubbleblowup*4;
if (player->bubbleblowup > bubbletime*2)
{
K_ThrowKartItem(player, (player->throwdir > 0), MT_BUBBLESHIELDTRAP, -1, 0);
K_PlayAttackTaunt(player->mo);
player->bubbleblowup = 0;
player->bubblecool = 0;
player->pflags &= ~PF_HOLDREADY;
player->itemamount--;
}
}
else
{
if (player->bubbleblowup > bubbletime)
player->bubbleblowup = bubbletime;
if (player->bubbleblowup)
player->bubbleblowup--;
if (player->bubblecool)
player->pflags &= ~PF_HOLDREADY;
else
player->pflags |= PF_HOLDREADY;
}
}
break;
case KITEM_FLAMESHIELD:
if (player->curshield != KSHIELD_FLAME)
{
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;
}
if (!HOLDING_ITEM && NO_HYUDORO)
{
INT32 destlen = K_FlameShieldMax(player);
INT32 flamemax = 0;
if (player->flamelength < destlen)
player->flamelength++; // Can always go up!
flamemax = player->flamelength * flameseg;
if (flamemax > 0)
flamemax += TICRATE; // leniency period
if ((cmd->buttons & BT_ATTACK) && (player->pflags & PF_HOLDREADY))
{
if (player->flamedash == 0)
{
S_StartSound(player->mo, sfx_s3k43);
K_PlayBoostTaunt(player->mo);
}
player->flamedash += 2;
player->flamemeter += 2;
if (!onground)
{
P_Thrust(
player->mo, K_MomentumAngle(player->mo),
FixedMul(player->mo->scale, K_GetKartGameSpeedScalar(gamespeed))
);
}
if (player->flamemeter > flamemax)
{
P_Thrust(
player->mo, player->mo->angle,
FixedMul((50*player->mo->scale), K_GetKartGameSpeedScalar(gamespeed))
);
player->flamemeter = 0;
player->flamelength = 0;
player->pflags &= ~PF_HOLDREADY;
player->itemamount--;
}
}
else
{
player->pflags |= PF_HOLDREADY;
if (player->flamemeter > 0)
player->flamemeter--;
if (player->flamelength > destlen)
{
player->flamelength--; // Can ONLY go down if you're not using it
flamemax = player->flamelength * flameseg;
if (flamemax > 0)
flamemax += TICRATE; // leniency period
}
if (player->flamemeter > flamemax)
player->flamemeter = flamemax;
}
}
break;
case KITEM_HYUDORO:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
player->itemamount--;
K_DoHyudoroSteal(player); // yes. yes they do.
}
break;
case KITEM_POGOSPRING:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO && player->trickpanel == 0)
{
K_PlayBoostTaunt(player->mo);
K_DoPogoSpring(player->mo, 32<<FRACBITS, 2);
player->trickpanel = 1;
player->pflags |= PF_TRICKDELAY;
player->itemamount--;
}
break;
case KITEM_SUPERRING:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && player->superring < (UINT16_MAX - (10*3)))
{
player->superring += (10*3);
player->itemamount--;
}
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;
mo->cusval = player->itemscale;
P_SetTarget(&mo->target, player->mo);
P_SetTarget(&player->mo->hnext, mo);
}
}
else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->pflags & PF_ITEMOUT)) // Sink thrown
{
K_ThrowKartItem(player, false, MT_SINK, 1, 2);
K_PlayAttackTaunt(player->mo);
player->itemamount--;
player->pflags &= ~PF_ITEMOUT;
K_UpdateHnextList(player, true);
}
break;
case KITEM_SAD:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO
&& !player->sadtimer)
{
player->sadtimer = stealtime;
player->itemamount--;
}
break;
default:
break;
}
}
}
}
// No more!
if (!player->itemamount)
{
player->pflags &= ~PF_ITEMOUT;
player->itemtype = KITEM_NONE;
}
if (K_GetShieldFromItem(player->itemtype) == KSHIELD_NONE)
{
player->curshield = KSHIELD_NONE; // RESET shield type
player->bubbleblowup = 0;
player->bubblecool = 0;
player->flamelength = 0;
player->flamemeter = 0;
}
if (spbplace == -1 || player->position != spbplace)
player->pflags &= ~PF_RINGLOCK; // reset ring lock
if (player->itemtype == KITEM_SPB
|| player->itemtype == KITEM_SHRINK
|| player->growshrinktimer < 0)
indirectitemcooldown = 20*TICRATE;
if (player->hyudorotimer > 0)
{
INT32 hyu = hyudorotime;
if (gametype == GT_RACE)
hyu *= 2; // double in race
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->bumpers <= 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->bumpers > 0)
{
player->mo->renderflags &= ~(RF_TRANSMASK|RF_BRIGHTMASK);
}
if (player->trickpanel == 1)
{
const angle_t lr = ANGLE_45;
fixed_t momz = FixedDiv(player->mo->momz, mapobjectscale); // bring momz back to scale...
fixed_t speedmult = max(0, FRACUNIT - abs(momz)/TRICKMOMZRAMP); // TRICKMOMZRAMP momz is minimum speed (Should be 20)
fixed_t basespeed = P_AproxDistance(player->mo->momx, player->mo->momy); // at WORSE, keep your normal speed when tricking.
fixed_t speed = FixedMul(speedmult, P_AproxDistance(player->mo->momx, player->mo->momy));
K_trickPanelTimingVisual(player, momz);
// streaks:
if (momz*P_MobjFlip(player->mo) > 0) // only spawn those while you're going upwards relative to your current gravity
{
// these are all admittedly arbitrary numbers...
INT32 n;
INT32 maxlines = max(1, (momz/FRACUNIT)/16);
INT32 frequency = max(1, 5-(momz/FRACUNIT)/4);
fixed_t sx, sy, sz;
mobj_t *spdl;
if (!(leveltime % frequency))
{
for (n=0; n < maxlines; n++)
{
sx = player->mo->x + P_RandomRange(-24, 24)*player->mo->scale;
sy = player->mo->y + P_RandomRange(-24, 24)*player->mo->scale;
sz = player->mo->z + P_RandomRange(0, 48)*player->mo->scale;
spdl = P_SpawnMobj(sx, sy, sz, MT_FASTLINE);
P_SetTarget(&spdl->target, player->mo);
P_InitAngle(spdl, R_PointToAngle2(spdl->x, spdl->y, player->mo->x, player->mo->y));
spdl->rollangle = -ANG1*90*P_MobjFlip(player->mo); // angle them downwards relative to the player's gravity...
spdl->spriteyscale = player->trickboostpower+FRACUNIT;
spdl->momx = player->mo->momx;
spdl->momy = player->mo->momy;
}
}
}
// We'll never need to go above that.
if (player->tricktime <= TRICKDELAY)
player->tricktime++;
// debug shit
//CONS_Printf("%d\n", player->mo->momz / mapobjectscale);
if (momz < -10*FRACUNIT) // :youfuckedup:
{
// tumble if you let your chance pass!!
player->tumbleBounces = 1;
player->pflags &= ~PF_TUMBLESOUND;
player->tumbleHeight = 30; // Base tumble bounce height
player->trickpanel = 0;
K_trickPanelTimingVisual(player, momz); // fail trick visual
P_SetPlayerMobjState(player->mo, S_KART_SPINOUT);
}
else if (!(player->pflags & PF_TRICKDELAY)) // don't allow tricking at the same frame you tumble obv
{
// "COOL" timing n shit.
if (cmd->turning || player->throwdir)
{
if (abs(momz) < FRACUNIT*99) // Let's use that as baseline for PERFECT trick.
{
player->karthud[khud_trickcool] = TICRATE;
}
}
// Uses cmd->turning over steering intentionally.
if (cmd->turning > 0)
{
P_InstaThrust(player->mo, player->mo->angle + lr, max(basespeed, speed*5/2));
player->trickpanel = 2;
player->mo->hitlag = TRICKLAG;
player->mo->eflags &= ~MFE_DAMAGEHITLAG;
K_trickPanelTimingVisual(player, momz);
}
else if (cmd->turning < 0)
{
P_InstaThrust(player->mo, player->mo->angle - lr, max(basespeed, speed*5/2));
player->trickpanel = 3;
player->mo->hitlag = TRICKLAG;
player->mo->eflags &= ~MFE_DAMAGEHITLAG;
K_trickPanelTimingVisual(player, momz);
}
else if (player->throwdir == 1)
{
if (player->mo->momz * P_MobjFlip(player->mo) > 0)
{
player->mo->momz = 0;
}
P_InstaThrust(player->mo, player->mo->angle, max(basespeed, speed*3));
player->trickpanel = 2;
player->mo->hitlag = TRICKLAG;
player->mo->eflags &= ~MFE_DAMAGEHITLAG;
K_trickPanelTimingVisual(player, momz);
}
else if (player->throwdir == -1)
{
boolean relative = true;
player->mo->momx /= 3;
player->mo->momy /= 3;
if (player->mo->momz * P_MobjFlip(player->mo) <= 0)
{
relative = false;
}
// Calculate speed boost decay:
// Base speed boost duration is 35 tics.
// At most, lose 3/4th of your boost.
player->trickboostdecay = min(TICRATE*3/4, abs(momz/FRACUNIT));
//CONS_Printf("decay: %d\n", player->trickboostdecay);
P_SetObjectMomZ(player->mo, 48*FRACUNIT, relative);
player->trickpanel = 4;
player->mo->hitlag = TRICKLAG;
player->mo->eflags &= ~MFE_DAMAGEHITLAG;
K_trickPanelTimingVisual(player, momz);
}
}
}
else if (player->trickpanel == 4 && P_IsObjectOnGround(player->mo)) // Upwards trick landed!
{
//CONS_Printf("apply boost\n");
S_StartSound(player->mo, sfx_s23c);
K_SpawnDashDustRelease(player);
player->trickboost = TICRATE - player->trickboostdecay;
player->trickpanel = player->trickboostdecay = 0;
}
// Wait until we let go off the control stick to remove the delay
// buttons must be neutral after the initial trick delay. This prevents weirdness where slight nudges after blast off would send you flying.
if ((player->pflags & PF_TRICKDELAY) && !player->throwdir && !cmd->turning && (player->tricktime >= TRICKDELAY))
{
player->pflags &= ~PF_TRICKDELAY;
}
}
K_KartDrift(player, onground);
K_KartSpindash(player);
if (onground == false)
{
K_AirFailsafe(player);
}
else
{
player->pflags &= ~PF_AIRFAILSAFE;
}
// Play the starting countdown sounds
if (player == &players[g_localplayers[0]]) // Don't play louder in splitscreen
{
if ((leveltime == starttime-(3*TICRATE)) || (leveltime == starttime-(2*TICRATE)) || (leveltime == starttime-TICRATE))
S_StartSound(NULL, sfx_s3ka7);
if (leveltime == starttime-(3*TICRATE))
S_FadeOutStopMusic(3500);
else if (leveltime == starttime)
S_StartSound(NULL, sfx_s3kad);
}
}
void K_CheckSpectateStatus(void)
{
UINT8 respawnlist[MAXPLAYERS];
UINT8 i, j, numingame = 0, numjoiners = 0;
// Maintain spectate wait timer
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
if (players[i].spectator && (players[i].pflags & PF_WANTSTOJOIN))
players[i].spectatewait++;
else
players[i].spectatewait = 0;
}
// No one's allowed to join
if (!cv_allowteamchange.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)
{
numingame++;
if (cv_ingamecap.value && numingame >= cv_ingamecap.value) // DON'T allow if you've hit the in-game player cap
return;
if (gamestate != GS_LEVEL) // Allow if you're not in a level
continue;
if (players[i].exiting) // DON'T allow if anyone's exiting
return;
if (numingame < 2 || leveltime < starttime || mapreset) // Allow if the match hasn't started yet
continue;
if (leveltime > (starttime + 20*TICRATE)) // DON'T allow if the match is 20 seconds in
return;
if (gametype == GT_RACE && players[i].laps >= 2) // DON'T allow if the race is at 2 laps
return;
continue;
}
else if (!(players[i].pflags & PF_WANTSTOJOIN))
continue;
respawnlist[numjoiners++] = i;
}
// literally zero point in going any further if nobody is joining
if (!numjoiners)
return;
// Organize by spectate wait timer
if (cv_ingamecap.value)
{
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];
}
}
// Finally, we can de-spectate everyone!
for (i = 0; i < numjoiners; i++)
{
if (cv_ingamecap.value && numingame+i >= cv_ingamecap.value) // Hit the in-game player cap while adding people?
break;
//CONS_Printf("player %s is joining on tic %d\n", player_names[respawnlist[i]], leveltime);
P_SpectatorJoinGame(&players[respawnlist[i]]);
}
// Reset the match if you're in an empty server
if (!mapreset && gamestate == GS_LEVEL && leveltime >= starttime && (numingame < 2 && numingame+i >= 2)) // use previous i value
{
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_GetOrbinautItemFrame(UINT8 count)
{
return min(count - 1, 3);
}
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;
}
//}