mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
5064 lines
126 KiB
C
5064 lines
126 KiB
C
// DR. ROBOTNIK'S RING RACERS
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 2025 by Kart Krew.
|
|
// Copyright (C) 2020 by Sonic Team Junior.
|
|
// Copyright (C) 2000 by DooM Legacy Team.
|
|
// Copyright (C) 1996 by id Software, Inc.
|
|
//
|
|
// This program is free software distributed under the
|
|
// terms of the GNU General Public License, version 2.
|
|
// See the 'LICENSE' file for more details.
|
|
//-----------------------------------------------------------------------------
|
|
/// \file p_user.c
|
|
/// \brief New stuff?
|
|
/// Player related stuff.
|
|
/// Bobbing POV/weapon, movement.
|
|
/// Pending weapon.
|
|
|
|
#include "doomdef.h"
|
|
#include "i_system.h"
|
|
#include "d_event.h"
|
|
#include "d_net.h"
|
|
#include "g_game.h"
|
|
#include "p_local.h"
|
|
#include "r_fps.h"
|
|
#include "r_main.h"
|
|
#include "s_sound.h"
|
|
#include "r_skins.h"
|
|
#include "d_think.h"
|
|
#include "r_sky.h"
|
|
#include "p_setup.h"
|
|
#include "m_random.h"
|
|
#include "m_misc.h"
|
|
#include "i_video.h"
|
|
#include "i_joy.h"
|
|
#include "p_slopes.h"
|
|
#include "p_spec.h"
|
|
#include "r_splats.h"
|
|
#include "z_zone.h"
|
|
#include "w_wad.h"
|
|
#include "y_inter.h" // Y_DetermineIntermissionType
|
|
#include "hu_stuff.h"
|
|
// We need to affect the NiGHTS hud
|
|
#include "st_stuff.h"
|
|
#include "lua_script.h"
|
|
#include "lua_hook.h"
|
|
// Objectplace
|
|
#include "m_cheat.h"
|
|
// Thok camera snap (ctrl-f "chalupa")
|
|
#include "g_input.h"
|
|
|
|
// SRB2kart
|
|
#include "m_cond.h" // M_UpdateUnlockablesAndExtraEmblems
|
|
#include "k_kart.h"
|
|
#include "console.h" // CON_LogMessage
|
|
#include "k_respawn.h"
|
|
#include "k_bot.h"
|
|
#include "k_grandprix.h"
|
|
#include "k_boss.h"
|
|
#include "k_specialstage.h"
|
|
#include "k_terrain.h" // K_SpawnSplashForMobj
|
|
#include "k_color.h"
|
|
#include "k_follower.h"
|
|
#include "k_battle.h"
|
|
#include "k_rank.h"
|
|
#include "k_director.h"
|
|
#include "g_party.h"
|
|
#include "k_profiles.h"
|
|
#include "music.h"
|
|
#include "k_tally.h"
|
|
#include "k_objects.h"
|
|
#include "k_endcam.h"
|
|
#include "k_credits.h"
|
|
#include "k_hud.h" // K_AddMessage
|
|
#include "m_easing.h"
|
|
#include "acs/interface.h"
|
|
#include "byteptr.h"
|
|
|
|
#ifdef HWRENDER
|
|
#include "hardware/hw_light.h"
|
|
#include "hardware/hw_main.h"
|
|
#endif
|
|
|
|
#if 0
|
|
static void P_NukeAllPlayers(player_t *player);
|
|
#endif
|
|
|
|
//
|
|
// Movement.
|
|
//
|
|
|
|
// 16 pixels of bob
|
|
//#define MAXBOB (0x10 << FRACBITS)
|
|
|
|
static boolean onground;
|
|
|
|
//
|
|
// P_Thrust
|
|
// Moves the given origin along a given angle.
|
|
//
|
|
void P_Thrust(mobj_t *mo, angle_t angle, fixed_t move)
|
|
{
|
|
angle >>= ANGLETOFINESHIFT;
|
|
|
|
mo->momx += FixedMul(move, FINECOSINE(angle));
|
|
mo->momy += FixedMul(move, FINESINE(angle));
|
|
}
|
|
|
|
#if 0
|
|
static inline void P_VectorInstaThrust(fixed_t xa, fixed_t xb, fixed_t xc, fixed_t ya, fixed_t yb, fixed_t yc,
|
|
fixed_t za, fixed_t zb, fixed_t zc, fixed_t momentum, mobj_t *mo)
|
|
{
|
|
fixed_t a1, b1, c1, a2, b2, c2, i, j, k;
|
|
|
|
a1 = xb - xa;
|
|
b1 = yb - ya;
|
|
c1 = zb - za;
|
|
a2 = xb - xc;
|
|
b2 = yb - yc;
|
|
c2 = zb - zc;
|
|
/*
|
|
// Convert to unit vectors...
|
|
a1 = FixedDiv(a1,FixedSqrt(FixedMul(a1,a1) + FixedMul(b1,b1) + FixedMul(c1,c1)));
|
|
b1 = FixedDiv(b1,FixedSqrt(FixedMul(a1,a1) + FixedMul(b1,b1) + FixedMul(c1,c1)));
|
|
c1 = FixedDiv(c1,FixedSqrt(FixedMul(c1,c1) + FixedMul(c1,c1) + FixedMul(c1,c1)));
|
|
|
|
a2 = FixedDiv(a2,FixedSqrt(FixedMul(a2,a2) + FixedMul(c2,c2) + FixedMul(c2,c2)));
|
|
b2 = FixedDiv(b2,FixedSqrt(FixedMul(a2,a2) + FixedMul(c2,c2) + FixedMul(c2,c2)));
|
|
c2 = FixedDiv(c2,FixedSqrt(FixedMul(a2,a2) + FixedMul(c2,c2) + FixedMul(c2,c2)));
|
|
*/
|
|
// Calculate the momx, momy, and momz
|
|
i = FixedMul(momentum, FixedMul(b1, c2) - FixedMul(c1, b2));
|
|
j = FixedMul(momentum, FixedMul(c1, a2) - FixedMul(a1, c2));
|
|
k = FixedMul(momentum, FixedMul(a1, b2) - FixedMul(a1, c2));
|
|
|
|
mo->momx = i;
|
|
mo->momy = j;
|
|
mo->momz = k;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// P_InstaThrust
|
|
// Moves the given origin along a given angle instantly.
|
|
//
|
|
// FIXTHIS: belongs in another file, not here
|
|
//
|
|
void P_InstaThrust(mobj_t *mo, angle_t angle, fixed_t move)
|
|
{
|
|
angle >>= ANGLETOFINESHIFT;
|
|
|
|
mo->momx = FixedMul(move, FINECOSINE(angle));
|
|
mo->momy = FixedMul(move,FINESINE(angle));
|
|
}
|
|
|
|
// Returns a location (hard to explain - go see how it is used)
|
|
fixed_t P_ReturnThrustX(mobj_t *mo, angle_t angle, fixed_t move)
|
|
{
|
|
(void)mo;
|
|
angle >>= ANGLETOFINESHIFT;
|
|
return FixedMul(move, FINECOSINE(angle));
|
|
}
|
|
fixed_t P_ReturnThrustY(mobj_t *mo, angle_t angle, fixed_t move)
|
|
{
|
|
(void)mo;
|
|
angle >>= ANGLETOFINESHIFT;
|
|
return FixedMul(move, FINESINE(angle));
|
|
}
|
|
|
|
//
|
|
// P_AutoPause
|
|
// Returns true when gameplay should be halted even if the game isn't necessarily paused.
|
|
//
|
|
boolean P_AutoPause(void)
|
|
{
|
|
// Don't pause even on menu-up or focus-lost in netgames or record attack
|
|
if (netgame || modeattacking || gamestate == GS_TITLESCREEN || gamestate == GS_MENU || con_startup)
|
|
return false;
|
|
|
|
return ((menuactive && !demo.playback) || ( window_notinfocus && cv_pauseifunfocused.value ));
|
|
}
|
|
|
|
//
|
|
// P_CalcHeight
|
|
// Calculate the walking / running height adjustment
|
|
//
|
|
void P_CalcHeight(player_t *player)
|
|
{
|
|
fixed_t bob = 0;
|
|
fixed_t pviewheight;
|
|
mobj_t *mo = player->mo;
|
|
fixed_t bobmul = FRACUNIT - FixedDiv(FixedHypot(player->rmomx, player->rmomy), K_GetKartSpeed(player, false, false));
|
|
|
|
// Regular movement bobbing.
|
|
// Should not be calculated when not on ground (FIXTHIS?)
|
|
// OPTIMIZE: tablify angle
|
|
// Note: a LUT allows for effects
|
|
// like a ramp with low health.
|
|
|
|
if (bobmul > FRACUNIT) bobmul = FRACUNIT;
|
|
if (bobmul < FRACUNIT/8) bobmul = FRACUNIT/8;
|
|
|
|
player->bob = FixedMul(cv_movebob.value, bobmul);
|
|
|
|
if (!P_IsObjectOnGround(mo) || player->spectator)
|
|
{
|
|
if (mo->eflags & MFE_VERTICALFLIP)
|
|
{
|
|
player->viewz = mo->z + mo->height - player->viewheight;
|
|
|
|
if (mo->flags & MF_NOCLIPHEIGHT)
|
|
return;
|
|
|
|
if (player->viewz < mo->floorz + FixedMul(FRACUNIT, mo->scale))
|
|
player->viewz = mo->floorz + FixedMul(FRACUNIT, mo->scale);
|
|
}
|
|
else
|
|
{
|
|
player->viewz = mo->z + player->viewheight;
|
|
|
|
if (mo->flags & MF_NOCLIPHEIGHT)
|
|
return;
|
|
|
|
if (player->viewz > mo->ceilingz - FixedMul(FRACUNIT, mo->scale))
|
|
player->viewz = mo->ceilingz - FixedMul(FRACUNIT, mo->scale);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// First person move bobbing in Kart is more of a "move jitter", to match how a go-kart would feel :p
|
|
if (leveltime & 1)
|
|
{
|
|
bob = player->bob;
|
|
}
|
|
|
|
// move viewheight
|
|
pviewheight = P_GetPlayerViewHeight(player); // default eye view height
|
|
|
|
if (player->playerstate == PST_LIVE)
|
|
{
|
|
player->viewheight += player->deltaviewheight;
|
|
|
|
if (player->viewheight > pviewheight)
|
|
{
|
|
player->viewheight = pviewheight;
|
|
player->deltaviewheight = 0;
|
|
}
|
|
|
|
if (player->viewheight < pviewheight/2)
|
|
{
|
|
player->viewheight = pviewheight/2;
|
|
if (player->deltaviewheight <= 0)
|
|
player->deltaviewheight = 1;
|
|
}
|
|
|
|
if (player->deltaviewheight)
|
|
{
|
|
player->deltaviewheight += FixedMul(FRACUNIT/4, mo->scale);
|
|
if (!player->deltaviewheight)
|
|
player->deltaviewheight = 1;
|
|
}
|
|
}
|
|
|
|
if (player->mo->eflags & MFE_VERTICALFLIP)
|
|
player->viewz = mo->z + mo->height - player->viewheight - bob;
|
|
else
|
|
player->viewz = mo->z + player->viewheight + bob;
|
|
|
|
if (player->viewz > mo->ceilingz-FixedMul(4*FRACUNIT, mo->scale))
|
|
player->viewz = mo->ceilingz-FixedMul(4*FRACUNIT, mo->scale);
|
|
if (player->viewz < mo->floorz+FixedMul(4*FRACUNIT, mo->scale))
|
|
player->viewz = mo->floorz+FixedMul(4*FRACUNIT, mo->scale);
|
|
}
|
|
|
|
/** Decides if a player is moving.
|
|
* \param pnum The player number to test.
|
|
* \return True if the player is considered to be moving.
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
boolean P_PlayerMoving(INT32 pnum)
|
|
{
|
|
player_t *p = &players[pnum];
|
|
|
|
if (!Playing())
|
|
return false;
|
|
|
|
if (p->jointime < 5*TICRATE || p->playerstate == PST_DEAD || p->playerstate == PST_REBORN || p->spectator)
|
|
return false;
|
|
|
|
return gamestate == GS_LEVEL && p->mo && p->mo->health > 0
|
|
&& (abs(p->rmomx) >= FixedMul(FRACUNIT/2, p->mo->scale)
|
|
|| abs(p->rmomy) >= FixedMul(FRACUNIT/2, p->mo->scale)
|
|
|| abs(p->mo->momz) >= FixedMul(FRACUNIT/2, p->mo->scale));
|
|
}
|
|
|
|
// P_GetNextEmerald
|
|
//
|
|
// Gets the number (0 based) of the next emerald to obtain
|
|
//
|
|
UINT8 P_GetNextEmerald(void)
|
|
{
|
|
cupheader_t *cup = NULL;
|
|
|
|
if (grandprixinfo.gp == true && grandprixinfo.cup)
|
|
{
|
|
if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found
|
|
|| grandprixinfo.cup->id >= basenumkartcupheaders // custom content
|
|
|| M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order
|
|
{
|
|
cup = grandprixinfo.cup;
|
|
}
|
|
else
|
|
{
|
|
// Determine order from sealedswaps.
|
|
UINT8 i;
|
|
for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++)
|
|
{
|
|
if (gamedata->sealedswaps[i] != grandprixinfo.cup)
|
|
continue;
|
|
|
|
// Repeat visit, grab the same ID.
|
|
break;
|
|
}
|
|
|
|
return i+1;
|
|
}
|
|
}
|
|
|
|
if (cup == NULL)
|
|
{
|
|
INT16 mapnum = gamemap-1;
|
|
|
|
if (mapnum < nummapheaders && mapheaderinfo[mapnum])
|
|
{
|
|
cup = mapheaderinfo[mapnum]->cup;
|
|
}
|
|
}
|
|
|
|
if (cup == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return cup->emeraldnum;
|
|
}
|
|
|
|
//
|
|
// P_FindLowestLap
|
|
//
|
|
// SRB2Kart, a similar function as above for finding the lowest lap
|
|
//
|
|
UINT8 P_FindLowestLap(void)
|
|
{
|
|
INT32 i;
|
|
UINT8 lowest = UINT8_MAX;
|
|
|
|
if (!(gametyperules & GTR_CIRCUIT))
|
|
return 0;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i] || players[i].spectator)
|
|
continue;
|
|
|
|
if (lowest == UINT8_MAX || players[i].laps < lowest)
|
|
{
|
|
lowest = players[i].laps;
|
|
}
|
|
}
|
|
|
|
CONS_Debug(DBG_GAMELOGIC, "Lowest laps found: %d\n", lowest);
|
|
|
|
return lowest;
|
|
}
|
|
|
|
//
|
|
// P_FindHighestLap
|
|
//
|
|
UINT8 P_FindHighestLap(void)
|
|
{
|
|
INT32 i;
|
|
UINT8 highest = 0;
|
|
|
|
if (!(gametyperules & GTR_CIRCUIT))
|
|
return 0;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i] || players[i].spectator)
|
|
continue;
|
|
|
|
if (players[i].laps > highest)
|
|
highest = players[i].laps;
|
|
}
|
|
|
|
CONS_Debug(DBG_GAMELOGIC, "Highest laps found: %d\n", highest);
|
|
|
|
return highest;
|
|
}
|
|
|
|
//
|
|
// P_PlayerInPain
|
|
//
|
|
// Is player in pain??
|
|
// Checks for painstate and flashing, if both found return true
|
|
//
|
|
boolean P_PlayerInPain(const player_t *player)
|
|
{
|
|
if (player->spinouttimer || (player->tumbleBounces > 0) || (player->pflags & PF_FAULT))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// P_ResetPlayer
|
|
//
|
|
// Useful when you want to kill everything the player is doing.
|
|
void P_ResetPlayer(player_t *player)
|
|
{
|
|
//player->pflags &= ~(PF_);
|
|
|
|
if (player->carry != CR_TRAPBUBBLE)
|
|
player->carry = CR_NONE;
|
|
player->onconveyor = 0;
|
|
|
|
//player->drift = player->driftcharge = 0;
|
|
player->trickpanel = TRICKSTATE_NONE;
|
|
player->glanceDir = 0;
|
|
player->fastfall = 0;
|
|
player->turbine = 0;
|
|
player->bubbledrag = false;
|
|
Obj_EndBungee(player);
|
|
|
|
if (player->mo != NULL && P_MobjWasRemoved(player->mo) == false)
|
|
{
|
|
P_ResetPitchRoll(player->mo);
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_GivePlayerRings
|
|
//
|
|
// Gives rings to the player, and does any special things required.
|
|
// Call this function when you want to increment the player's health.
|
|
// Returns the number of rings successfully given (or taken).
|
|
//
|
|
|
|
INT32 P_GivePlayerRings(player_t *player, INT32 num_rings)
|
|
{
|
|
INT32 test;
|
|
|
|
if (!player->mo)
|
|
return 0;
|
|
|
|
if ((gametyperules & GTR_SPHERES)) // No rings in Battle Mode
|
|
return 0;
|
|
|
|
if (gamedata && num_rings > 0 && P_IsPartyPlayer(player) && gamedata->totalrings <= GDMAX_RINGS)
|
|
{
|
|
gamedata->totalrings += num_rings;
|
|
}
|
|
|
|
test = player->rings + num_rings;
|
|
if (test > 20) // Caps at 20 rings, sorry!
|
|
num_rings -= (test-20);
|
|
else if (test < -20) // Chaotix ring debt!
|
|
num_rings -= (test+20);
|
|
|
|
player->rings += num_rings;
|
|
|
|
if (player->roundconditions.debt_rings == false
|
|
&& !(player->exiting || (player->pflags & PF_NOCONTEST))
|
|
&& player->rings < 0)
|
|
{
|
|
player->roundconditions.debt_rings = true;
|
|
player->roundconditions.checkthisframe = true;
|
|
}
|
|
|
|
return num_rings;
|
|
}
|
|
|
|
INT32 P_GivePlayerSpheres(player_t *player, INT32 num_spheres)
|
|
{
|
|
num_spheres += player->spheres;
|
|
|
|
if (!(gametyperules & GTR_SPHERES)) // No spheres in Race mode)
|
|
return 0;
|
|
|
|
if (num_spheres > 40) // Reached the cap, don't waste 'em!
|
|
num_spheres = 40;
|
|
else if (num_spheres < 0)
|
|
num_spheres = 0;
|
|
|
|
num_spheres -= player->spheres;
|
|
|
|
player->spheres += num_spheres;
|
|
|
|
return num_spheres;
|
|
}
|
|
|
|
//
|
|
// P_GivePlayerLives
|
|
//
|
|
// Gives the player an extra life.
|
|
// Call this function when you want to add lives to the player.
|
|
//
|
|
void P_GivePlayerLives(player_t *player, INT32 numlives)
|
|
{
|
|
player->lives += numlives;
|
|
|
|
if (player->lives > 10)
|
|
player->lives = 10;
|
|
else if (player->lives < 1)
|
|
player->lives = 1;
|
|
}
|
|
|
|
// Adds to the player's score
|
|
void P_AddPlayerScore(player_t *player, INT32 amount)
|
|
{
|
|
if ((gametyperules & GTR_POINTLIMIT) == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (player->exiting) // srb2kart
|
|
{
|
|
return;
|
|
}
|
|
|
|
const boolean teams = G_GametypeHasTeams();
|
|
|
|
// Don't underflow.
|
|
// Don't go above MAXSCORE.
|
|
if (amount < 0 && (UINT32)-amount > player->roundscore)
|
|
{
|
|
player->roundscore = 0;
|
|
}
|
|
else if (player->roundscore + amount < MAXSCORE)
|
|
{
|
|
if (player->roundscore < g_pointlimit
|
|
&& g_pointlimit <= player->roundscore + amount
|
|
&& teams == false) // We want the normal scoring function to update roundscore, but this notification will be done by G_AddTeamScore.
|
|
{
|
|
HU_DoTitlecardCEchoForDuration(player, "K.O. READY!", true, 5*TICRATE/2);
|
|
}
|
|
|
|
player->roundscore += amount;
|
|
}
|
|
else
|
|
{
|
|
player->roundscore = MAXSCORE;
|
|
}
|
|
|
|
if (teams == true)
|
|
{
|
|
G_AddTeamScore(player->team, amount, player);
|
|
}
|
|
}
|
|
|
|
void P_PlayRinglossSound(mobj_t *source)
|
|
{
|
|
if (source->player && K_GetShieldFromItem(source->player->itemtype) != KSHIELD_NONE)
|
|
S_StartSound(source, sfx_s1a3); // Shield hit (no ring loss)
|
|
else if (source->player && source->player->rings <= 0)
|
|
S_StartSound(source, sfx_s1a6); // Ring debt (lessened ring loss)
|
|
else
|
|
S_StartSound(source, sfx_s1c6); // Normal ring loss sound
|
|
}
|
|
|
|
void P_PlayDeathSound(mobj_t *source)
|
|
{
|
|
S_StartSound(source, sfx_s3k35);
|
|
}
|
|
|
|
//
|
|
// P_StartPositionMusic
|
|
//
|
|
// Consistently sets starting music!
|
|
//
|
|
void P_StartPositionMusic(boolean exact)
|
|
{
|
|
if (encoremode)
|
|
{
|
|
if (exact
|
|
? (leveltime != 1)
|
|
: (leveltime < 1))
|
|
return;
|
|
|
|
Music_Remap("position", "encore");
|
|
}
|
|
else
|
|
{
|
|
if (exact
|
|
? (leveltime != introtime)
|
|
: (leveltime < introtime))
|
|
return;
|
|
|
|
Music_Remap("position",
|
|
(mapheaderinfo[gamemap-1]->positionmus[0]
|
|
? mapheaderinfo[gamemap-1]->positionmus
|
|
: "postn"
|
|
));
|
|
}
|
|
|
|
Music_Play("position");
|
|
Music_DelayEnd("position", (starttime + (TICRATE/2)) - leveltime);
|
|
}
|
|
|
|
//
|
|
// P_EndingMusic
|
|
//
|
|
// Consistently sets ending music!
|
|
//
|
|
void P_EndingMusic(void)
|
|
{
|
|
const char *jingle = NULL;
|
|
UINT8 bestPos = UINT8_MAX;
|
|
player_t *bestPlayer = NULL;
|
|
|
|
SINT8 i = MAXPLAYERS;
|
|
|
|
// See G_DoCompleted and Y_DetermineIntermissionType
|
|
boolean nointer = ((modeattacking && (players[consoleplayer].pflags & PF_NOCONTEST))
|
|
|| (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE));
|
|
|
|
if (K_CheckBossIntro())
|
|
;
|
|
else for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i]
|
|
|| players[i].spectator)
|
|
continue;
|
|
|
|
// Battle powerstone win
|
|
if ((gametyperules & GTR_POWERSTONES)
|
|
&& ALLCHAOSEMERALDS(players[i].emeralds))
|
|
break;
|
|
|
|
// Special round?
|
|
if (((gametyperules & GTR_SPECIALSTART)
|
|
|| (grandprixinfo.gp == true
|
|
&& grandprixinfo.eventmode == GPEVENT_SPECIAL)
|
|
) == false)
|
|
continue;
|
|
|
|
// Any player has completed well?
|
|
if (!players[i].exiting
|
|
|| players[i].bot
|
|
|| K_IsPlayerLosing(&players[i]))
|
|
continue;
|
|
|
|
// Special win
|
|
break;
|
|
}
|
|
|
|
// Event - Emerald Finish
|
|
if (i != MAXPLAYERS)
|
|
{
|
|
jingle = "EMRLD";
|
|
goto skippingposition;
|
|
}
|
|
|
|
// Event - Level Finish
|
|
// Check for if this is valid or not
|
|
for (i = 0; i <= r_splitscreen; i++)
|
|
{
|
|
UINT8 pos = UINT8_MAX;
|
|
player_t *checkPlayer = NULL;
|
|
|
|
checkPlayer = &players[displayplayers[i]];
|
|
if (!checkPlayer || checkPlayer->spectator == true)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (checkPlayer->exiting)
|
|
{
|
|
// Standard exit, use their position
|
|
pos = checkPlayer->position;
|
|
}
|
|
else if (checkPlayer->pflags & PF_NOCONTEST)
|
|
{
|
|
// No Contest without finishing, use special value
|
|
;
|
|
}
|
|
else
|
|
{
|
|
// Not finished, ignore
|
|
continue;
|
|
}
|
|
|
|
if (pos <= bestPos)
|
|
{
|
|
bestPlayer = checkPlayer;
|
|
bestPos = pos;
|
|
}
|
|
}
|
|
|
|
if (bestPlayer == NULL)
|
|
{
|
|
// No jingle for you
|
|
return;
|
|
}
|
|
|
|
if (bestPos == UINT8_MAX)
|
|
{
|
|
jingle = "RETIRE";
|
|
|
|
if (G_GametypeAllowsRetrying() == true)
|
|
{
|
|
// A retry will be happening
|
|
nointer = true;
|
|
}
|
|
}
|
|
else if (K_IsPlayerLosing(bestPlayer) == true)
|
|
{
|
|
jingle = "_lose";
|
|
|
|
// Sort of ugly, this composite check is effictively "does gametype force retry": Relaxed allows you to place low.
|
|
if (G_GametypeAllowsRetrying() == true && !(grandprixinfo.gp && grandprixinfo.gamespeed == KARTSPEED_EASY && gametyperules & GTR_CIRCUIT))
|
|
{
|
|
// A retry will be happening
|
|
nointer = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (K_CheckBossIntro() == true)
|
|
{
|
|
jingle = "VSENT";
|
|
}
|
|
else if (modeattacking)
|
|
{
|
|
if (players[consoleplayer].realtime < oldbest && oldbest != (tic_t)UINT32_MAX && !demo.playback)
|
|
jingle = "newrec";
|
|
else
|
|
jingle = "norec";
|
|
}
|
|
else if (bestPlayer->position == 1)
|
|
{
|
|
jingle = "_first";
|
|
}
|
|
else
|
|
{
|
|
jingle = "_win";
|
|
}
|
|
}
|
|
|
|
skippingposition:
|
|
|
|
if (nointer == true)
|
|
{
|
|
// Do not set "racent" in G_Ticker
|
|
musiccountdown = 1;
|
|
}
|
|
|
|
if (jingle == NULL)
|
|
return;
|
|
|
|
Music_Remap("finish", jingle);
|
|
Music_Play("finish");
|
|
}
|
|
|
|
void P_InvincGrowMusic(void)
|
|
{
|
|
INT32 invinc = 0;
|
|
INT32 grow = 0;
|
|
|
|
UINT8 i;
|
|
|
|
for (i = 0; i <= r_splitscreen; ++i)
|
|
{
|
|
player_t *player = &players[displayplayers[i]];
|
|
|
|
if (!P_IsPartyPlayer(player))
|
|
{
|
|
// Director cam on another player? Don't play
|
|
// this.
|
|
continue;
|
|
}
|
|
|
|
// Find the longest running timer among splitscreen
|
|
// players and use that.
|
|
|
|
if (player->invincibilitytimer > invinc)
|
|
{
|
|
invinc = player->invincibilitytimer;
|
|
}
|
|
|
|
if (player->growshrinktimer > grow)
|
|
{
|
|
grow = player->growshrinktimer;
|
|
}
|
|
}
|
|
|
|
if (invinc && !Music_Playing("invinc"))
|
|
{
|
|
Music_Play("invinc");
|
|
}
|
|
|
|
if (grow && !Music_Playing("grow"))
|
|
{
|
|
Music_Play("grow");
|
|
}
|
|
|
|
Music_DelayEnd("invinc", invinc);
|
|
Music_DelayEnd("grow", grow);
|
|
}
|
|
|
|
//
|
|
// P_IsObjectInGoop
|
|
//
|
|
// Returns true if the object is inside goop water.
|
|
// (Spectators and objects otherwise without gravity cannot have goop gravity!)
|
|
//
|
|
boolean P_IsObjectInGoop(const mobj_t *mo)
|
|
{
|
|
if (mo->player && mo->player->spectator)
|
|
return false;
|
|
|
|
if (mo->flags & MF_NOGRAVITY)
|
|
return false;
|
|
|
|
return ((mo->eflags & (MFE_UNDERWATER|MFE_GOOWATER)) == (MFE_UNDERWATER|MFE_GOOWATER));
|
|
}
|
|
|
|
//
|
|
// P_IsObjectOnGround
|
|
//
|
|
// Returns true if the player is
|
|
// on the ground. Takes reverse
|
|
// gravity and FOFs into account.
|
|
//
|
|
boolean P_IsObjectOnGround(const mobj_t *mo)
|
|
{
|
|
if (P_IsObjectInGoop(mo))
|
|
{
|
|
/*
|
|
// It's a crazy hack that checking if you're on the ground
|
|
// would actually CHANGE your position and momentum,
|
|
if (mo->z < mo->floorz)
|
|
{
|
|
mo->z = mo->floorz;
|
|
mo->momz = 0;
|
|
}
|
|
else if (mo->z + mo->height > mo->ceilingz)
|
|
{
|
|
mo->z = mo->ceilingz - mo->height;
|
|
mo->momz = 0;
|
|
}
|
|
*/
|
|
// but I don't want you to ever 'stand' while submerged in goo.
|
|
// You're in constant vertical momentum, even if you get stuck on something.
|
|
// No exceptions.
|
|
return false;
|
|
}
|
|
|
|
if (mo->eflags & MFE_VERTICALFLIP)
|
|
{
|
|
if (mo->z+mo->height >= mo->ceilingz)
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (mo->z <= mo->floorz)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// P_IsObjectOnGroundIn
|
|
//
|
|
// Returns true if the player is
|
|
// on the ground in a specific sector. Takes reverse
|
|
// gravity and FOFs into account.
|
|
//
|
|
boolean P_IsObjectOnGroundIn(const mobj_t *mo, const sector_t *sec)
|
|
{
|
|
ffloor_t *rover;
|
|
|
|
// Is the object in reverse gravity?
|
|
if (mo->eflags & MFE_VERTICALFLIP)
|
|
{
|
|
// Detect if the player is on the ceiling.
|
|
if (mo->z+mo->height >= P_GetSpecialTopZ(mo, sec, sec))
|
|
return true;
|
|
// Otherwise, detect if the player is on the bottom of a FOF.
|
|
else
|
|
{
|
|
for (rover = sec->ffloors; rover; rover = rover->next)
|
|
{
|
|
// If the FOF doesn't exist, continue.
|
|
if (!(rover->fofflags & FOF_EXISTS))
|
|
continue;
|
|
|
|
// If the FOF is configured to let the object through, continue.
|
|
if (!((rover->fofflags & FOF_BLOCKPLAYER && mo->player)
|
|
|| (rover->fofflags & FOF_BLOCKOTHERS && !mo->player)))
|
|
continue;
|
|
|
|
// If the the platform is intangible from below, continue.
|
|
if (rover->fofflags & FOF_PLATFORM)
|
|
continue;
|
|
|
|
// If the FOF is a water block, continue. (Unnecessary check?)
|
|
if (rover->fofflags & FOF_SWIMMABLE)
|
|
continue;
|
|
|
|
// Actually check if the player is on the suitable FOF.
|
|
if (mo->z+mo->height == P_GetSpecialBottomZ(mo, sectors + rover->secnum, sec))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
// Nope!
|
|
else
|
|
{
|
|
// Detect if the player is on the floor.
|
|
if (mo->z <= P_GetSpecialBottomZ(mo, sec, sec))
|
|
return true;
|
|
// Otherwise, detect if the player is on the top of a FOF.
|
|
else
|
|
{
|
|
for (rover = sec->ffloors; rover; rover = rover->next)
|
|
{
|
|
// If the FOF doesn't exist, continue.
|
|
if (!(rover->fofflags & FOF_EXISTS))
|
|
continue;
|
|
|
|
// If the FOF is configured to let the object through, continue.
|
|
if (!((rover->fofflags & FOF_BLOCKPLAYER && mo->player)
|
|
|| (rover->fofflags & FOF_BLOCKOTHERS && !mo->player)))
|
|
continue;
|
|
|
|
// If the the platform is intangible from above, continue.
|
|
if (rover->fofflags & FOF_REVERSEPLATFORM)
|
|
continue;
|
|
|
|
// If the FOF is a water block, continue. (Unnecessary check?)
|
|
if (rover->fofflags & FOF_SWIMMABLE)
|
|
continue;
|
|
|
|
// Actually check if the player is on the suitable FOF.
|
|
if (mo->z == P_GetSpecialTopZ(mo, sectors + rover->secnum, sec))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// P_IsObjectOnRealGround
|
|
//
|
|
// Helper function for T_EachTimeThinker
|
|
// Like P_IsObjectOnGroundIn, except ONLY THE REAL GROUND IS CONSIDERED, NOT FOFS
|
|
// I'll consider whether to make this a more globally accessible function or whatever in future
|
|
// -- Monster Iestyn
|
|
//
|
|
// Really simple, but personally I think it's also incredibly helpful. I think this is fine in p_user.c
|
|
// -- Sal
|
|
|
|
boolean P_IsObjectOnRealGround(const mobj_t *mo, const sector_t *sec)
|
|
{
|
|
// Is the object in reverse gravity?
|
|
if (mo->eflags & MFE_VERTICALFLIP)
|
|
{
|
|
// Detect if the player is on the ceiling.
|
|
if (mo->z+mo->height >= P_GetSpecialTopZ(mo, sec, sec))
|
|
return true;
|
|
}
|
|
// Nope!
|
|
else
|
|
{
|
|
// Detect if the player is on the floor.
|
|
if (mo->z <= P_GetSpecialBottomZ(mo, sec, sec))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// P_SetObjectMomZ
|
|
//
|
|
// Sets the player momz appropriately.
|
|
// Takes reverse gravity into account.
|
|
//
|
|
void P_SetObjectMomZ(mobj_t *mo, fixed_t value, boolean relative)
|
|
{
|
|
if (mo->eflags & MFE_VERTICALFLIP)
|
|
value = -value;
|
|
|
|
if (mo->scale != FRACUNIT)
|
|
value = FixedMul(value, mo->scale);
|
|
|
|
if (relative)
|
|
mo->momz += value;
|
|
else
|
|
mo->momz = value;
|
|
}
|
|
|
|
//
|
|
// P_IsMachineLocalPlayer
|
|
//
|
|
// Returns true if player is
|
|
// ACTUALLY on the local machine
|
|
//
|
|
boolean P_IsMachineLocalPlayer(const player_t *player)
|
|
{
|
|
UINT8 i;
|
|
|
|
if (player == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i <= splitscreen; i++)
|
|
{
|
|
if (player == &players[g_localplayers[i]])
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// P_IsPartyPlayer
|
|
//
|
|
// Returns true if player is
|
|
// on the local machine
|
|
// (or simulated party)
|
|
//
|
|
boolean P_IsPartyPlayer(const player_t *player)
|
|
{
|
|
if (player == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// nobody is ever local when watching something back - you're a spectator there, even if your g_localplayers might say otherwise
|
|
if (demo.playback)
|
|
return false;
|
|
|
|
// handles both online parties and local players (no need to call P_IsMachineLocalPlayer here)
|
|
return G_IsPartyLocal(player-players);
|
|
}
|
|
|
|
//
|
|
// P_IsDisplayPlayer
|
|
//
|
|
// Returns true if player is
|
|
// currently being watched.
|
|
//
|
|
boolean P_IsDisplayPlayer(const player_t *player)
|
|
{
|
|
UINT8 i;
|
|
|
|
if (player == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i <= r_splitscreen; i++) // DON'T skip P1
|
|
{
|
|
if (camera[i].freecam)
|
|
{
|
|
// Freecam still techically has a player in
|
|
// displayplayers. But since the camera is
|
|
// detached, it would be weird if sounds were
|
|
// heard from that player's perspective.
|
|
continue;
|
|
}
|
|
|
|
if (player == &players[displayplayers[i]])
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// P_SpawnGhostMobj
|
|
//
|
|
// Spawns a ghost object on the player
|
|
//
|
|
mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
|
|
{
|
|
mobj_t *ghost = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_GHOST);
|
|
|
|
P_SetTarget(&ghost->target, mobj);
|
|
|
|
P_SetScale(ghost, mobj->scale);
|
|
ghost->scalespeed = mobj->scalespeed;
|
|
ghost->destscale = mobj->scale;
|
|
|
|
if (mobj->eflags & MFE_VERTICALFLIP)
|
|
{
|
|
ghost->eflags |= MFE_VERTICALFLIP;
|
|
ghost->z += mobj->height - ghost->height;
|
|
}
|
|
|
|
ghost->color = mobj->color;
|
|
ghost->colorized = mobj->colorized; // Kart: they should also be colorized if their origin is
|
|
|
|
ghost->angle = (mobj->player ? mobj->player->drawangle : mobj->angle);
|
|
ghost->roll = mobj->roll;
|
|
ghost->pitch = mobj->pitch;
|
|
ghost->sprite = mobj->sprite;
|
|
ghost->sprite2 = mobj->sprite2;
|
|
ghost->frame = mobj->frame;
|
|
ghost->tics = -1;
|
|
ghost->renderflags = (mobj->renderflags & ~RF_TRANSMASK)|RF_TRANS50;
|
|
ghost->fuse = ghost->info->damage;
|
|
ghost->skin = mobj->skin;
|
|
ghost->standingslope = mobj->standingslope;
|
|
|
|
ghost->sprxoff = mobj->sprxoff;
|
|
ghost->spryoff = mobj->spryoff;
|
|
ghost->sprzoff = mobj->sprzoff;
|
|
|
|
// baked offsets
|
|
ghost->bakexoff = mobj->bakexoff;
|
|
ghost->bakeyoff = mobj->bakeyoff;
|
|
ghost->bakezoff = mobj->bakezoff;
|
|
ghost->bakexpiv = mobj->bakexpiv;
|
|
ghost->bakeypiv = mobj->bakeypiv;
|
|
ghost->bakezpiv = mobj->bakezpiv;
|
|
|
|
ghost->rollangle = mobj->rollangle;
|
|
|
|
ghost->spritexscale = mobj->spritexscale;
|
|
ghost->spriteyscale = mobj->spriteyscale;
|
|
ghost->spritexoffset = mobj->spritexoffset;
|
|
ghost->spriteyoffset = mobj->spriteyoffset;
|
|
|
|
if (mobj->flags2 & MF2_OBJECTFLIP)
|
|
ghost->flags2 |= MF2_OBJECTFLIP;
|
|
|
|
if (!(mobj->flags & MF_DONTENCOREMAP))
|
|
ghost->flags &= ~MF_DONTENCOREMAP;
|
|
|
|
if (mobj->player && mobj->player->followmobj)
|
|
{
|
|
mobj_t *ghost2 = P_SpawnGhostMobj(mobj->player->followmobj);
|
|
P_SetTarget(&ghost2->tracer, ghost);
|
|
P_SetTarget(&ghost->tracer, ghost2);
|
|
ghost2->flags2 |= (mobj->player->followmobj->flags2 & MF2_LINKDRAW);
|
|
}
|
|
|
|
// Copy interpolation data :)
|
|
ghost->old_x = mobj->old_x2;
|
|
ghost->old_y = mobj->old_y2;
|
|
ghost->old_z = mobj->old_z2;
|
|
ghost->old_angle = (mobj->player ? mobj->player->old_drawangle2 : mobj->old_angle2);
|
|
ghost->old_pitch = mobj->old_pitch2;
|
|
ghost->old_roll = mobj->old_roll2;
|
|
ghost->old_scale = mobj->old_scale2;
|
|
|
|
P_SetTarget(&ghost->owner, mobj);
|
|
ghost->renderflags |= RF_REDUCEVFX;
|
|
|
|
ghost->reappear = mobj->reappear;
|
|
P_SetTarget(&ghost->punt_ref, mobj->punt_ref);
|
|
|
|
return ghost;
|
|
}
|
|
|
|
//
|
|
// P_SpawnFakeShadow
|
|
//
|
|
// Spawns a silhouette that copies its tracer and copies RF_DONTDRAW
|
|
//
|
|
mobj_t *P_SpawnFakeShadow(mobj_t *mobj, UINT8 offset)
|
|
{
|
|
mobj_t *ghost = P_SpawnMobj(mobj->x, mobj->y, mobj->z - offset*mapobjectscale, MT_FAKESHADOW);
|
|
ghost->threshold = offset;
|
|
|
|
P_SetTarget(&ghost->target, mobj);
|
|
|
|
P_SetScale(ghost, mobj->scale);
|
|
ghost->destscale = mobj->scale;
|
|
|
|
if (mobj->eflags & MFE_VERTICALFLIP)
|
|
{
|
|
ghost->eflags |= MFE_VERTICALFLIP;
|
|
ghost->z += mobj->height - ghost->height;
|
|
}
|
|
|
|
ghost->color = mobj->color;
|
|
ghost->colorized = mobj->colorized; // Kart: they should also be colorized if their origin is
|
|
|
|
ghost->angle = (mobj->player ? mobj->player->drawangle : mobj->angle);
|
|
ghost->roll = mobj->roll;
|
|
ghost->pitch = mobj->pitch;
|
|
ghost->sprite = mobj->sprite;
|
|
ghost->sprite2 = mobj->sprite2;
|
|
ghost->frame = mobj->frame;
|
|
ghost->tics = -1;
|
|
ghost->renderflags = mobj->renderflags;
|
|
ghost->fuse = -1;
|
|
ghost->skin = mobj->skin;
|
|
ghost->standingslope = mobj->standingslope;
|
|
|
|
ghost->sprxoff = mobj->sprxoff;
|
|
ghost->spryoff = mobj->spryoff;
|
|
ghost->sprzoff = mobj->sprzoff;
|
|
ghost->rollangle = mobj->rollangle;
|
|
|
|
ghost->spritexscale = mobj->spritexscale;
|
|
ghost->spriteyscale = mobj->spriteyscale;
|
|
ghost->spritexoffset = mobj->spritexoffset;
|
|
ghost->spriteyoffset = mobj->spriteyoffset;
|
|
|
|
if (mobj->flags2 & MF2_OBJECTFLIP)
|
|
ghost->flags |= MF2_OBJECTFLIP;
|
|
|
|
if (!(mobj->flags & MF_DONTENCOREMAP))
|
|
ghost->flags &= ~MF_DONTENCOREMAP;
|
|
|
|
if (mobj->player && mobj->player->followmobj)
|
|
{
|
|
mobj_t *ghost2 = P_SpawnGhostMobj(mobj->player->followmobj);
|
|
P_SetTarget(&ghost2->tracer, ghost);
|
|
P_SetTarget(&ghost->tracer, ghost2);
|
|
ghost2->flags2 |= (mobj->player->followmobj->flags2 & MF2_LINKDRAW);
|
|
}
|
|
|
|
// Copy interpolation data :)
|
|
ghost->old_x = mobj->old_x2;
|
|
ghost->old_y = mobj->old_y2;
|
|
ghost->old_z = mobj->old_z2;
|
|
ghost->old_angle = (mobj->player ? mobj->player->old_drawangle2 : mobj->old_angle2);
|
|
ghost->old_pitch = mobj->old_pitch2;
|
|
ghost->old_roll = mobj->old_roll2;
|
|
|
|
ghost->renderflags &= ~(RF_TRANSMASK|RF_FULLBRIGHT);
|
|
ghost->renderflags |= RF_ABSOLUTELIGHTLEVEL;
|
|
ghost->lightlevel = 0;
|
|
|
|
ghost->flags2 |= MF2_LINKDRAW;
|
|
P_SetTarget(&ghost->tracer, mobj);
|
|
|
|
return ghost;
|
|
}
|
|
|
|
|
|
//
|
|
// P_DoPlayerExit
|
|
//
|
|
// Player exits the map via sector trigger
|
|
void P_DoPlayerExit(player_t *player, pflags_t flags)
|
|
{
|
|
if (player->exiting || mapreset)
|
|
{
|
|
return;
|
|
}
|
|
|
|
player->pflags |= flags;
|
|
|
|
if (P_IsPartyPlayer(player) && (!player->spectator && !demo.playback))
|
|
{
|
|
legitimateexit = true;
|
|
player->roundconditions.checkthisframe = true;
|
|
gamedata->deferredconditioncheck = true;
|
|
}
|
|
|
|
player->exiting = 1;
|
|
|
|
if (!player->spectator && (gametyperules & GTR_CIRCUIT)) // Special Race-like handling
|
|
{
|
|
K_UpdateAllPlayerPositions();
|
|
player->mfdfinish = player->markedfordeath;
|
|
}
|
|
|
|
if (!(gametyperules & GTR_SPHERES) && (player->pflags & PF_RINGLOCK) && grandprixinfo.gp)
|
|
{
|
|
if (P_IsDisplayPlayer(player))
|
|
S_StartSound(NULL, sfx_s3kb0);
|
|
player->rings = max(20, player->rings + 20);
|
|
}
|
|
|
|
extern boolean blockreset;
|
|
if (modeattacking && !K_IsPlayerLosing(player) && player->realtime < oldbest)
|
|
blockreset = true;
|
|
|
|
const boolean losing = K_IsPlayerLosing(player); // HEY!!!! Set it AFTER K_UpdateAllPlayerPositions!!!!
|
|
const boolean specialout = (specialstageinfo.valid == true && losing == true);
|
|
|
|
if (losing)
|
|
{
|
|
// Remove a life from the losing player
|
|
K_PlayerLoseLife(player);
|
|
}
|
|
|
|
if (!player->spectator)
|
|
{
|
|
ClearFakePlayerSkin(player);
|
|
|
|
if ((gametyperules & GTR_CIRCUIT)) // Special Race-like handling
|
|
{
|
|
if (P_CheckRacers() && !exitcountdown)
|
|
{
|
|
G_BeginLevelExit();
|
|
}
|
|
}
|
|
else if (!exitcountdown) // All other gametypes
|
|
{
|
|
G_BeginLevelExit();
|
|
}
|
|
}
|
|
|
|
K_UpdatePowerLevelsFinalize(player, false);
|
|
|
|
if (G_IsPartyLocal(player - players) && !specialout && musiccountdown == 0)
|
|
{
|
|
Music_Play("finish_silence");
|
|
musiccountdown = MUSIC_COUNTDOWN_MAX;
|
|
}
|
|
|
|
if (!player->spectator)
|
|
{
|
|
if (!(gametyperules & GTR_SPHERES))
|
|
{
|
|
player->hudrings = RINGTOTAL(player);
|
|
if (player->hudrings > 20)
|
|
player->hudrings = 20;
|
|
|
|
if (grandprixinfo.gp == true
|
|
&& grandprixinfo.eventmode != GPEVENT_SPECIAL
|
|
&& player->bot == false && losing == false && player->hudrings > 0)
|
|
{
|
|
const UINT8 lifethreshold = 20;
|
|
|
|
const UINT8 oldExtra = player->totalring / lifethreshold;
|
|
const UINT8 extra = (player->totalring + player->hudrings) / lifethreshold;
|
|
|
|
if (extra > oldExtra)
|
|
{
|
|
player->xtralife = (extra - oldExtra);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (specialstageinfo.valid == true && losing == false && P_MobjWasRemoved(player->mo) == false)
|
|
{
|
|
K_MakeObjectReappear(player->mo);
|
|
}
|
|
}
|
|
|
|
//K_InitPlayerTally(player); -- we defer this to P_PlayerAfterThink
|
|
|
|
if (demo.playback == false)
|
|
{
|
|
if (modeattacking && !K_LegacyRingboost(player))
|
|
{
|
|
G_UpdateRecords();
|
|
}
|
|
|
|
// Profile and skin record updates
|
|
if (P_IsMachineLocalPlayer(player))
|
|
{
|
|
profile_t *pr = PR_GetPlayerProfile(player);
|
|
|
|
// Profile records
|
|
if (pr != NULL)
|
|
{
|
|
if (!losing)
|
|
{
|
|
pr->wins++;
|
|
}
|
|
pr->rounds++;
|
|
PR_SaveProfiles();
|
|
}
|
|
|
|
// Skin records (saved to gamedata)
|
|
if (player->skin < numskins)
|
|
{
|
|
skin_t *playerskin = skins[player->skin];
|
|
if (!losing)
|
|
{
|
|
playerskin->records.wins++;
|
|
}
|
|
playerskin->records.rounds++;
|
|
}
|
|
}
|
|
|
|
if (!demo.savebutton)
|
|
{
|
|
UINT8 outstanding = splitscreen + 1;
|
|
for (UINT8 i = 0; i <= splitscreen; ++i)
|
|
{
|
|
if (players[g_localplayers[i]].exiting)
|
|
outstanding--;
|
|
}
|
|
// Once the entire local party finishes (not
|
|
// online party), show the "Save Replay" button.
|
|
if (!outstanding)
|
|
demo.savebutton = leveltime;
|
|
}
|
|
}
|
|
|
|
ACS_RunPlayerFinishScript(player);
|
|
}
|
|
|
|
//
|
|
// P_DoAllPlayersExit
|
|
//
|
|
// All players exit the map via event
|
|
void P_DoAllPlayersExit(pflags_t flags, boolean trygivelife)
|
|
{
|
|
UINT8 i;
|
|
const boolean dofinishsound = (musiccountdown == 0) && (!K_InRaceDuel());
|
|
|
|
if (grandprixinfo.gp == false
|
|
|| grandprixinfo.eventmode == GPEVENT_SPECIAL
|
|
|| (flags & PF_NOCONTEST))
|
|
{
|
|
trygivelife = false;
|
|
}
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i] || players[i].spectator)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (players[i].exiting)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
P_DoPlayerExit(&players[i], flags);
|
|
|
|
if (trygivelife == false)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
players[i].xtralife++;
|
|
}
|
|
|
|
if (!dofinishsound)
|
|
{
|
|
// You've already finished, don't play again
|
|
;
|
|
}
|
|
else if (gametyperules & GTR_SPECIALSTART)
|
|
{
|
|
// Warp out
|
|
S_StartSound(NULL, sfx_s3kb3);
|
|
}
|
|
else if (musiccountdown == 0)
|
|
{
|
|
// Other people finish
|
|
S_StartSound(NULL, sfx_s253);
|
|
}
|
|
else if (musiccountdown > 1)
|
|
{
|
|
// Everyone finish sound
|
|
S_StartSound(NULL, sfx_s3k6a);
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_PlayerHitFloor
|
|
//
|
|
// Handles player hitting floor surface.
|
|
// Returns whether to clip momz.
|
|
boolean P_PlayerHitFloor(player_t *player, boolean fromAir, angle_t oldPitch, angle_t oldRoll)
|
|
{
|
|
boolean clipmomz;
|
|
|
|
I_Assert(player->mo != NULL);
|
|
|
|
clipmomz = !(P_CheckDeathPitCollide(player->mo));
|
|
|
|
if (clipmomz == true)
|
|
{
|
|
if (fromAir == true)
|
|
{
|
|
K_SpawnSplashForMobj(player->mo, abs(player->mo->momz));
|
|
}
|
|
|
|
if (player->mo->health > 0)
|
|
{
|
|
boolean air = fromAir;
|
|
|
|
if (P_IsObjectOnGround(player->mo) && (player->mo->eflags & MFE_JUSTHITFLOOR))
|
|
{
|
|
air = true;
|
|
}
|
|
|
|
if (K_CheckStumble(player, oldPitch, oldRoll, air) == true)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (air == false && K_FastFallBounce(player) == true)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return clipmomz;
|
|
}
|
|
|
|
boolean P_InQuicksand(const mobj_t *mo) // Returns true if you are in quicksand
|
|
{
|
|
sector_t *sector = mo->subsector->sector;
|
|
fixed_t topheight, bottomheight;
|
|
|
|
fixed_t flipoffset = ((mo->eflags & MFE_VERTICALFLIP) ? (mo->height/2) : 0);
|
|
|
|
if (sector->ffloors)
|
|
{
|
|
ffloor_t *rover;
|
|
|
|
for (rover = sector->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (!(rover->fofflags & FOF_EXISTS))
|
|
continue;
|
|
|
|
if (!(rover->fofflags & FOF_QUICKSAND))
|
|
continue;
|
|
|
|
topheight = P_GetFFloorTopZAt (rover, mo->x, mo->y);
|
|
bottomheight = P_GetFFloorBottomZAt(rover, mo->x, mo->y);
|
|
|
|
if (mo->z + flipoffset > topheight)
|
|
continue;
|
|
|
|
if (mo->z + (mo->height/2) + flipoffset < bottomheight)
|
|
continue;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false; // No sand here, Captain!
|
|
}
|
|
|
|
static boolean P_PlayerCanBust(player_t *player, ffloor_t *rover)
|
|
{
|
|
// TODO: Make these act like the Lua SA2 boxes.
|
|
(void)player;
|
|
(void)rover;
|
|
|
|
if (!(rover->fofflags & FOF_EXISTS))
|
|
return false;
|
|
|
|
if (!(rover->fofflags & FOF_BUSTUP))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void P_CheckBustableBlocks(player_t *player)
|
|
{
|
|
msecnode_t *node;
|
|
fixed_t oldx;
|
|
fixed_t oldy;
|
|
|
|
if ((netgame || multiplayer) && player->spectator)
|
|
return;
|
|
|
|
oldx = player->mo->x;
|
|
oldy = player->mo->y;
|
|
|
|
P_UnsetThingPosition(player->mo);
|
|
player->mo->x += player->mo->momx;
|
|
player->mo->y += player->mo->momy;
|
|
P_SetThingPosition(player->mo);
|
|
|
|
for (node = player->mo->touching_sectorlist; node; node = node->m_sectorlist_next)
|
|
{
|
|
ffloor_t *rover;
|
|
fixed_t topheight, bottomheight;
|
|
|
|
if (!node->m_sector)
|
|
break;
|
|
|
|
if (!node->m_sector->ffloors)
|
|
continue;
|
|
|
|
for (rover = node->m_sector->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (!P_PlayerCanBust(player, rover))
|
|
continue;
|
|
|
|
topheight = P_GetFOFTopZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
|
|
bottomheight = P_GetFOFBottomZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
|
|
|
|
// Height checks
|
|
if (rover->bustflags & FB_ONLYBOTTOM)
|
|
{
|
|
if (player->mo->z + player->mo->momz + player->mo->height < bottomheight)
|
|
continue;
|
|
|
|
if (player->mo->z + player->mo->height > bottomheight)
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
switch (rover->busttype)
|
|
{
|
|
case BT_TOUCH:
|
|
if (player->mo->z + player->mo->momz > topheight)
|
|
continue;
|
|
|
|
if (player->mo->z + player->mo->momz + player->mo->height < bottomheight)
|
|
continue;
|
|
|
|
break;
|
|
case BT_SPINBUST:
|
|
if (player->mo->z + player->mo->momz > topheight)
|
|
continue;
|
|
|
|
if (player->mo->z + player->mo->height < bottomheight)
|
|
continue;
|
|
|
|
break;
|
|
default:
|
|
if (player->mo->z >= topheight)
|
|
continue;
|
|
|
|
if (player->mo->z + player->mo->height < bottomheight)
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Impede the player's fall a bit
|
|
if (((rover->busttype == BT_TOUCH) || (rover->busttype == BT_SPINBUST)) && player->mo->z >= topheight)
|
|
player->mo->momz >>= 1;
|
|
else if (rover->busttype == BT_TOUCH)
|
|
{
|
|
player->mo->momx >>= 1;
|
|
player->mo->momy >>= 1;
|
|
}
|
|
|
|
EV_CrumbleChain(NULL, rover); // node->m_sector
|
|
|
|
// Run a linedef executor??
|
|
if (rover->bustflags & FB_EXECUTOR)
|
|
P_LinedefExecute(rover->busttag, player->mo, node->m_sector);
|
|
|
|
goto bustupdone;
|
|
}
|
|
}
|
|
|
|
bustupdone:
|
|
P_UnsetThingPosition(player->mo);
|
|
player->mo->x = oldx;
|
|
player->mo->y = oldy;
|
|
P_SetThingPosition(player->mo);
|
|
}
|
|
|
|
static void P_CheckBouncySectors(player_t *player)
|
|
{
|
|
msecnode_t *node;
|
|
fixed_t oldx;
|
|
fixed_t oldy;
|
|
fixed_t oldz;
|
|
vector3_t momentum;
|
|
|
|
oldx = player->mo->x;
|
|
oldy = player->mo->y;
|
|
oldz = player->mo->z;
|
|
|
|
P_UnsetThingPosition(player->mo);
|
|
player->mo->x += player->mo->momx;
|
|
player->mo->y += player->mo->momy;
|
|
player->mo->z += player->mo->momz;
|
|
P_SetThingPosition(player->mo);
|
|
|
|
for (node = player->mo->touching_sectorlist; node; node = node->m_sectorlist_next)
|
|
{
|
|
ffloor_t *rover;
|
|
|
|
if (!node->m_sector)
|
|
break;
|
|
|
|
if (!node->m_sector->ffloors)
|
|
continue;
|
|
|
|
for (rover = node->m_sector->ffloors; rover; rover = rover->next)
|
|
{
|
|
fixed_t topheight, bottomheight;
|
|
|
|
if (!(rover->fofflags & FOF_EXISTS))
|
|
continue; // FOFs should not be bouncy if they don't even "exist"
|
|
|
|
// Handle deprecated bouncy FOF sector type
|
|
if (!udmf && GETSECSPECIAL(rover->master->frontsector->special, 1) == 15)
|
|
{
|
|
rover->fofflags |= FOF_BOUNCY;
|
|
rover->bouncestrength = P_AproxDistance(rover->master->dx, rover->master->dy)/100;
|
|
}
|
|
|
|
if (!(rover->fofflags & FOF_BOUNCY))
|
|
continue;
|
|
|
|
topheight = P_GetFOFTopZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
|
|
bottomheight = P_GetFOFBottomZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
|
|
|
|
if (player->mo->z > topheight)
|
|
continue;
|
|
|
|
if (player->mo->z + player->mo->height < bottomheight)
|
|
continue;
|
|
|
|
if (oldz < P_GetFOFTopZ(player->mo, node->m_sector, rover, oldx, oldy, NULL)
|
|
&& oldz + player->mo->height > P_GetFOFBottomZ(player->mo, node->m_sector, rover, oldx, oldy, NULL))
|
|
{
|
|
player->mo->momx = -FixedMul(player->mo->momx,rover->bouncestrength);
|
|
player->mo->momy = -FixedMul(player->mo->momy,rover->bouncestrength);
|
|
}
|
|
else
|
|
{
|
|
pslope_t *slope = (abs(oldz - topheight) < abs(oldz + player->mo->height - bottomheight)) ? *rover->t_slope : *rover->b_slope;
|
|
|
|
momentum.x = player->mo->momx;
|
|
momentum.y = player->mo->momy;
|
|
momentum.z = player->mo->momz*2;
|
|
|
|
if (slope)
|
|
P_ReverseQuantizeMomentumToSlope(&momentum, slope);
|
|
|
|
momentum.z = -FixedMul(momentum.z,rover->bouncestrength)/2;
|
|
|
|
if (abs(momentum.z) < (rover->bouncestrength*2))
|
|
goto bouncydone;
|
|
|
|
if (momentum.z > FixedMul(24*FRACUNIT, player->mo->scale)) //half of the default player height
|
|
momentum.z = FixedMul(24*FRACUNIT, player->mo->scale);
|
|
else if (momentum.z < -FixedMul(24*FRACUNIT, player->mo->scale))
|
|
momentum.z = -FixedMul(24*FRACUNIT, player->mo->scale);
|
|
|
|
if (slope)
|
|
P_QuantizeMomentumToSlope(&momentum, slope);
|
|
|
|
player->mo->momx = momentum.x;
|
|
player->mo->momy = momentum.y;
|
|
player->mo->momz = momentum.z;
|
|
}
|
|
goto bouncydone;
|
|
}
|
|
}
|
|
|
|
bouncydone:
|
|
P_UnsetThingPosition(player->mo);
|
|
player->mo->x = oldx;
|
|
player->mo->y = oldy;
|
|
player->mo->z = oldz;
|
|
P_SetThingPosition(player->mo);
|
|
}
|
|
|
|
static void P_CheckQuicksand(player_t *player)
|
|
{
|
|
ffloor_t *rover;
|
|
fixed_t sinkspeed, friction;
|
|
fixed_t topheight, bottomheight;
|
|
|
|
if (!(player->mo->subsector->sector->ffloors && player->mo->momz <= 0))
|
|
return;
|
|
|
|
for (rover = player->mo->subsector->sector->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (!(rover->fofflags & FOF_EXISTS)) continue;
|
|
|
|
if (!(rover->fofflags & FOF_QUICKSAND))
|
|
continue;
|
|
|
|
topheight = P_GetFFloorTopZAt (rover, player->mo->x, player->mo->y);
|
|
bottomheight = P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y);
|
|
|
|
if (topheight >= player->mo->z && bottomheight < player->mo->z + player->mo->height)
|
|
{
|
|
sinkspeed = abs(rover->master->v1->x - rover->master->v2->x)>>1;
|
|
|
|
sinkspeed = FixedDiv(sinkspeed,TICRATE*FRACUNIT);
|
|
|
|
if (player->mo->eflags & MFE_VERTICALFLIP)
|
|
{
|
|
fixed_t ceilingheight = P_GetCeilingZ(player->mo, player->mo->subsector->sector, player->mo->x, player->mo->y, NULL);
|
|
|
|
player->mo->z += sinkspeed;
|
|
|
|
if (player->mo->z + player->mo->height >= ceilingheight)
|
|
player->mo->z = ceilingheight - player->mo->height;
|
|
|
|
if (player->mo->momz <= 0)
|
|
P_PlayerHitFloor(player, false, player->mo->roll, player->mo->pitch);
|
|
}
|
|
else
|
|
{
|
|
fixed_t floorheight = P_GetFloorZ(player->mo, player->mo->subsector->sector, player->mo->x, player->mo->y, NULL);
|
|
|
|
player->mo->z -= sinkspeed;
|
|
|
|
if (player->mo->z <= floorheight)
|
|
player->mo->z = floorheight;
|
|
|
|
if (player->mo->momz >= 0)
|
|
P_PlayerHitFloor(player, false, player->mo->roll, player->mo->pitch);
|
|
}
|
|
|
|
friction = abs(rover->master->v1->y - rover->master->v2->y)>>6;
|
|
|
|
player->mo->momx = FixedMul(player->mo->momx, friction);
|
|
player->mo->momy = FixedMul(player->mo->momy, friction);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_CheckInvincibilityTimer
|
|
//
|
|
// Restores music from invincibility, and handles invincibility sparkles
|
|
//
|
|
static void P_CheckInvincibilityTimer(player_t *player)
|
|
{
|
|
if (!player->invincibilitytimer)
|
|
return;
|
|
|
|
// Resume normal music stuff.
|
|
if (player->invincibilitytimer == 1)
|
|
{
|
|
//K_KartResetPlayerColor(player); -- this gets called every tic anyways
|
|
G_GhostAddColor((INT32) (player - players), GHC_NORMAL);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_DoBubbleBreath
|
|
//
|
|
// Handles bubbles spawned by the player
|
|
//
|
|
static void P_DoBubbleBreath(player_t *player)
|
|
{
|
|
fixed_t x = player->mo->x;
|
|
fixed_t y = player->mo->y;
|
|
fixed_t z = player->mo->z;
|
|
mobj_t *bubble = NULL;
|
|
|
|
if (!(player->mo->eflags & MFE_UNDERWATER) || player->spectator || player->curshield == KSHIELD_BUBBLE)
|
|
return;
|
|
|
|
#if 0
|
|
if (player->charflags & SF_MACHINE)
|
|
{
|
|
if (P_RandomChance(PR_BUBBLE, FRACUNIT/5))
|
|
{
|
|
fixed_t r = player->mo->radius>>FRACBITS;
|
|
x += (P_RandomRange(PR_BUBBLE, r, -r)<<FRACBITS);
|
|
y += (P_RandomRange(PR_BUBBLE, r, -r)<<FRACBITS);
|
|
z += (P_RandomKey(PR_BUBBLE, player->mo->height>>FRACBITS)<<FRACBITS);
|
|
bubble = P_SpawnMobj(x, y, z, MT_WATERZAP);
|
|
S_StartSound(bubble, sfx_beelec);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
fixed_t topspeed = K_GetKartSpeed(player, false, false);
|
|
fixed_t f = FixedDiv(player->speed, topspeed/2);
|
|
|
|
if (player->mo->eflags & MFE_VERTICALFLIP)
|
|
z += player->mo->height - FixedDiv(player->mo->height,5*(FRACUNIT/4));
|
|
else
|
|
z += FixedDiv(player->mo->height,5*(FRACUNIT/4));
|
|
|
|
if (P_RandomChance(PR_BUBBLE, FixedMul(FRACUNIT/16, FRACUNIT + 3*f)))
|
|
{
|
|
UINT32 seed = P_GetRandSeed(PR_BUBBLE);
|
|
x += P_RandomRange(PR_BUBBLE, -16, 16) * player->mo->scale;
|
|
y += P_RandomRange(PR_BUBBLE, -16, 16) * player->mo->scale;
|
|
z += P_RandomRange(PR_BUBBLE, -16, 16) * player->mo->scale;
|
|
P_SetRandSeed(PR_BUBBLE, seed);
|
|
bubble = P_SpawnMobj(x, y, z, MT_SMALLBUBBLE);
|
|
}
|
|
else if (P_RandomChance(PR_BUBBLE, FixedMul(3*FRACUNIT/256, FRACUNIT + 3*f)))
|
|
{
|
|
UINT32 seed = P_GetRandSeed(PR_BUBBLE);
|
|
x += P_RandomRange(PR_BUBBLE, -16, 16) * player->mo->scale;
|
|
y += P_RandomRange(PR_BUBBLE, -16, 16) * player->mo->scale;
|
|
z += P_RandomRange(PR_BUBBLE, -16, 16) * player->mo->scale;
|
|
P_SetRandSeed(PR_BUBBLE, seed);
|
|
bubble = P_SpawnMobj(x, y, z, MT_MEDIUMBUBBLE);
|
|
}
|
|
|
|
if (bubble)
|
|
{
|
|
bubble->color = SKINCOLOR_TEAL;
|
|
bubble->colorized = true;
|
|
|
|
f = min(f, 11*FRACUNIT/10);
|
|
vector3_t v = {FixedMul(player->mo->momx, f), FixedMul(player->mo->momy, f), FixedMul(player->mo->momz, f)};
|
|
if (player->mo->standingslope)
|
|
P_QuantizeMomentumToSlope(&v, player->mo->standingslope);
|
|
bubble->momx += v.x;
|
|
bubble->momy += v.y;
|
|
bubble->momz += v.z;
|
|
}
|
|
}
|
|
|
|
if (bubble)
|
|
{
|
|
bubble->threshold = 42;
|
|
bubble->destscale = 3 * player->mo->scale / 2;
|
|
P_SetScale(bubble, bubble->destscale);
|
|
}
|
|
}
|
|
|
|
static inline boolean P_IsMomentumAngleLocked(player_t *player)
|
|
{
|
|
// This timer is used for the animation too and the
|
|
// animation should continue for a bit after the physics
|
|
// stop.
|
|
|
|
fixed_t myspeed = player->speed;
|
|
|
|
// Stairjank soften tuning constants
|
|
fixed_t BASE_SPEED = K_GetKartSpeed(player, false, true);
|
|
fixed_t TOP_SPEED = 5*BASE_SPEED/2;
|
|
fixed_t BASE_ANGLE_CHECK = ANGLE_45; // Higher values = need more deflection to soften stairjank
|
|
fixed_t FAST_ANGLE_CHECK = BASE_ANGLE_CHECK/3;
|
|
|
|
fixed_t speedrange = TOP_SPEED - BASE_SPEED;
|
|
|
|
if (myspeed < BASE_SPEED)
|
|
myspeed = BASE_SPEED;
|
|
if (myspeed > TOP_SPEED)
|
|
myspeed = TOP_SPEED;
|
|
|
|
myspeed -= BASE_SPEED; // normalize lowest checked speed to 0
|
|
fixed_t speedrate = FixedDiv(myspeed, speedrange); // ...and highest checked speed to FRACUNIT
|
|
|
|
fixed_t anglecheck = Easing_Linear(speedrate, BASE_ANGLE_CHECK, FAST_ANGLE_CHECK);
|
|
|
|
if (player->stairjank > 8)
|
|
{
|
|
const angle_t th = K_MomentumAngle(player->mo);
|
|
const angle_t d = AngleDelta(th, player->mo->angle);
|
|
|
|
// A larger difference between momentum and facing
|
|
// angles awards back control.
|
|
// <45 deg: 3/4 tics
|
|
// >45 deg: 2/4 tics
|
|
// >90 deg: 1/4 tics
|
|
// >135 deg: 0/4 tics
|
|
|
|
if ((leveltime & 3) > (d / anglecheck))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (K_IsRidingFloatingTop(player))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//#define OLD_MOVEMENT_CODE 1
|
|
static void P_3dMovement(player_t *player)
|
|
{
|
|
angle_t movepushangle; // Analog
|
|
fixed_t movepushforward = 0;
|
|
angle_t dangle; // replaces old quadrants bits
|
|
fixed_t oldMagnitude, newMagnitude;
|
|
vector3_t totalthrust;
|
|
|
|
totalthrust.x = totalthrust.y = 0; // I forget if this is needed
|
|
|
|
totalthrust.z = FixedMul(mapobjectscale, K_GrowShrinkSpeedMul(player))*P_MobjFlip(player->mo)/3; // A bit of extra push-back on slopes, but scaled for mapobject and player size
|
|
|
|
if (K_SlopeResistance(player) == true)
|
|
{
|
|
totalthrust.z = -(totalthrust.z);
|
|
}
|
|
|
|
// Get the old momentum; this will be needed at the end of the function! -SH
|
|
oldMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0);
|
|
|
|
if (P_IsMomentumAngleLocked(player))
|
|
{
|
|
movepushangle = K_MomentumAngle(player->mo);
|
|
}
|
|
else if (player->drift != 0)
|
|
{
|
|
movepushangle = player->mo->angle - (ANGLE_45/5) * player->drift;
|
|
}
|
|
else if (player->spinouttimer || player->wipeoutslow) // if spun out, use the boost angle
|
|
{
|
|
movepushangle = (angle_t)player->boostangle;
|
|
}
|
|
else
|
|
{
|
|
movepushangle = player->mo->angle;
|
|
}
|
|
|
|
// cmomx/cmomy stands for the conveyor belt speed.
|
|
if (player->onconveyor == 2) // Wind/Current
|
|
{
|
|
//if (player->mo->z > player->mo->watertop || player->mo->z + player->mo->height < player->mo->waterbottom)
|
|
if (!(player->mo->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)))
|
|
player->cmomx = player->cmomy = 0;
|
|
}
|
|
else if (player->onconveyor == 4 && !P_IsObjectOnGround(player->mo)) // Actual conveyor belt
|
|
player->cmomx = player->cmomy = 0;
|
|
else if (player->onconveyor != 2 && player->onconveyor != 4
|
|
&& player->onconveyor != 1
|
|
)
|
|
player->cmomx = player->cmomy = 0;
|
|
|
|
player->rmomx = player->mo->momx - player->cmomx;
|
|
player->rmomy = player->mo->momy - player->cmomy;
|
|
|
|
// Calculates player's speed based on distance-of-a-line formula
|
|
player->speed = R_PointToDist2(0, 0, player->rmomx, player->rmomy);
|
|
|
|
const fixed_t topspeedometer = K_GetKartSpeed(player, false, true);
|
|
|
|
if (player->speed > topspeedometer)
|
|
{
|
|
const fixed_t convSpeed = (player->speed * 100) / topspeedometer;
|
|
|
|
if (convSpeed > player->roundconditions.maxspeed)
|
|
{
|
|
player->roundconditions.maxspeed = convSpeed;
|
|
//player->roundconditions.checkthisframe = true; -- no, safe to leave until lapchange at worst
|
|
}
|
|
}
|
|
|
|
// Monster Iestyn - 04-11-13
|
|
// Quadrants are stupid, excessive and broken, let's do this a much simpler way!
|
|
// Get delta angle from rmom angle and player angle first
|
|
dangle = R_PointToAngle2(0,0, player->rmomx, player->rmomy) - player->mo->angle;
|
|
if (dangle > ANGLE_180) //flip to keep to one side
|
|
{
|
|
dangle = InvAngle(dangle);
|
|
}
|
|
|
|
// anything else will leave both at 0, so no need to do anything else
|
|
|
|
//{ SRB2kart 220217 - Toaster Code for misplaced thrust
|
|
#if 0
|
|
if (!player->drift) // Not Drifting
|
|
{
|
|
angle_t difference = dangle/2;
|
|
boolean reverse = (dangle >= ANGLE_90);
|
|
|
|
if (dangleflip)
|
|
difference = InvAngle(difference);
|
|
|
|
if (reverse)
|
|
difference += ANGLE_180;
|
|
|
|
P_InstaThrust(player->mo, player->mo->angle + difference, player->speed);
|
|
}
|
|
#endif
|
|
//}
|
|
|
|
// Do not let the player control movement if not onground.
|
|
onground = P_IsObjectOnGround(player->mo);
|
|
|
|
K_AdjustPlayerFriction(player);
|
|
|
|
// Forward movement
|
|
// If the player isn't on the ground, there is no change in speed
|
|
// Smiley Face
|
|
if (onground)
|
|
{
|
|
movepushforward = K_3dKartMovement(player);
|
|
|
|
if (player->mo->movefactor != FRACUNIT) // Friction-scaled acceleration...
|
|
movepushforward = FixedMul(movepushforward, player->mo->movefactor);
|
|
|
|
if (player->curshield != KSHIELD_TOP)
|
|
{
|
|
INT32 a = K_GetUnderwaterTurnAdjust(player);
|
|
INT32 adj = 0;
|
|
|
|
if (a)
|
|
{
|
|
const fixed_t maxadj = ANG10/4;
|
|
|
|
adj = a / 4;
|
|
|
|
if (adj > 0)
|
|
{
|
|
if (adj > maxadj)
|
|
adj = maxadj;
|
|
}
|
|
else if (adj < 0)
|
|
{
|
|
if (adj < -(maxadj))
|
|
adj = -(maxadj);
|
|
}
|
|
|
|
if (abs(player->underwatertilt + adj) > abs(a))
|
|
adj = (a - player->underwatertilt);
|
|
|
|
if (abs(a) < abs(player->underwatertilt))
|
|
adj = 0;
|
|
|
|
movepushangle += a;
|
|
}
|
|
|
|
if (adj)
|
|
{
|
|
player->underwatertilt += adj;
|
|
|
|
if (abs(player->underwatertilt) > ANG30)
|
|
{
|
|
player->underwatertilt =
|
|
player->underwatertilt > 0 ? ANG30
|
|
: -(ANG30);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
player->underwatertilt =
|
|
FixedMul(player->underwatertilt,
|
|
7*FRACUNIT/8);
|
|
}
|
|
}
|
|
|
|
totalthrust.x += P_ReturnThrustX(player->mo, movepushangle, movepushforward);
|
|
totalthrust.y += P_ReturnThrustY(player->mo, movepushangle, movepushforward);
|
|
}
|
|
|
|
if ((totalthrust.x || totalthrust.y)
|
|
&& player->mo->standingslope != NULL
|
|
&& (!(player->mo->standingslope->flags & SL_NOPHYSICS))
|
|
&& abs(player->mo->standingslope->zdelta) > FRACUNIT/2)
|
|
{
|
|
// Factor thrust to slope, but only for the part pushing up it!
|
|
// The rest is unaffected.
|
|
angle_t thrustangle = R_PointToAngle2(0, 0, totalthrust.x, totalthrust.y) - player->mo->standingslope->xydirection;
|
|
|
|
if (player->mo->standingslope->zdelta < 0)
|
|
{
|
|
// Direction goes down, so thrustangle needs to face toward
|
|
if (thrustangle < ANGLE_90 || thrustangle > ANGLE_270)
|
|
{
|
|
P_QuantizeMomentumToSlope(&totalthrust, player->mo->standingslope);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Direction goes up, so thrustangle needs to face away
|
|
if (thrustangle > ANGLE_90 && thrustangle < ANGLE_270)
|
|
{
|
|
P_QuantizeMomentumToSlope(&totalthrust, player->mo->standingslope);
|
|
}
|
|
}
|
|
}
|
|
|
|
player->mo->momx += totalthrust.x;
|
|
player->mo->momy += totalthrust.y;
|
|
|
|
// Releasing a drift while on the Top translates all your
|
|
// momentum (and even then some) into whichever direction
|
|
// you're facing
|
|
if (onground && player->curshield == KSHIELD_TOP && (K_GetKartButtons(player) & BT_DRIFT) != BT_DRIFT && (player->oldcmd.buttons & BT_DRIFT))
|
|
{
|
|
const fixed_t gmin = FRACUNIT/4;
|
|
const fixed_t gmax = 3*FRACUNIT;
|
|
|
|
const fixed_t grindfactor = (gmax - gmin) / GARDENTOP_MAXGRINDTIME;
|
|
const fixed_t grindscale = gmin + (player->topdriftheld * grindfactor);
|
|
|
|
const fixed_t speed = R_PointToDist2(0, 0, player->mo->momx, player->mo->momy);
|
|
const fixed_t minspeed = 3 * K_GetKartSpeed(player, false, false) / 5; // 60% top speed
|
|
|
|
P_InstaThrust(player->mo, player->mo->angle, FixedMul(max(speed, minspeed), grindscale));
|
|
|
|
player->topdriftheld = 0;/* reset after release */
|
|
}
|
|
|
|
if (!onground)
|
|
{
|
|
const fixed_t airspeedcap = (50*mapobjectscale);
|
|
const fixed_t speed = R_PointToDist2(0, 0, player->mo->momx, player->mo->momy);
|
|
|
|
// If you're going too fast in the air, ease back down to a certain speed.
|
|
// Helps lots of jumps from breaking when using speed items, since you can't move in the air.
|
|
if (speed > airspeedcap)
|
|
{
|
|
fixed_t div = 32*FRACUNIT;
|
|
fixed_t newspeed;
|
|
|
|
// Make rubberbanding bots slow down faster
|
|
if (K_PlayerUsesBotMovement(player) && player->dashpadcooldown == 0)
|
|
{
|
|
fixed_t rubberband = player->botvars.rubberband - FRACUNIT;
|
|
|
|
if (rubberband > 0)
|
|
{
|
|
div = FixedDiv(div, FRACUNIT + (rubberband * 2));
|
|
}
|
|
}
|
|
|
|
newspeed = speed - FixedDiv((speed - airspeedcap), div);
|
|
|
|
player->mo->momx = FixedMul(FixedDiv(player->mo->momx, speed), newspeed);
|
|
player->mo->momy = FixedMul(FixedDiv(player->mo->momy, speed), newspeed);
|
|
}
|
|
}
|
|
|
|
// Time to ask three questions:
|
|
// 1) Are we over topspeed?
|
|
// 2) If "yes" to 1, were we moving over topspeed to begin with?
|
|
// 3) If "yes" to 2, are we now going faster?
|
|
|
|
// If "yes" to 3, normalize to our initial momentum; this will allow thoks to stay as fast as they normally are.
|
|
// If "no" to 3, ignore it; the player might be going too fast, but they're slowing down, so let them.
|
|
// If "no" to 2, normalize to topspeed, so we can't suddenly run faster than it of our own accord.
|
|
// If "no" to 1, we're not reaching any limits yet, so ignore this entirely!
|
|
// -Shadow Hog
|
|
// Only do this forced cap of speed when in midair, the kart acceleration code takes into account friction, and
|
|
// doesn't let you accelerate past top speed, so this is unnecessary on the ground, but in the air is needed to
|
|
// allow for being able to change direction on spring jumps without being accelerated into the void - Sryder
|
|
if (!P_IsObjectOnGround(player->mo))
|
|
{
|
|
fixed_t topspeed = K_GetKartSpeed(player, true, true);
|
|
newMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0);
|
|
if (newMagnitude > topspeed)
|
|
{
|
|
fixed_t tempmomx, tempmomy;
|
|
if (oldMagnitude > topspeed)
|
|
{
|
|
if (newMagnitude > oldMagnitude)
|
|
{
|
|
tempmomx = FixedMul(FixedDiv(player->mo->momx - player->cmomx, newMagnitude), oldMagnitude);
|
|
tempmomy = FixedMul(FixedDiv(player->mo->momy - player->cmomy, newMagnitude), oldMagnitude);
|
|
player->mo->momx = tempmomx + player->cmomx;
|
|
player->mo->momy = tempmomy + player->cmomy;
|
|
}
|
|
// else do nothing
|
|
}
|
|
else
|
|
{
|
|
tempmomx = FixedMul(FixedDiv(player->mo->momx - player->cmomx, newMagnitude), topspeed);
|
|
tempmomy = FixedMul(FixedDiv(player->mo->momy - player->cmomy, newMagnitude), topspeed);
|
|
player->mo->momx = tempmomx + player->cmomx;
|
|
player->mo->momy = tempmomy + player->cmomy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// For turning correction in P_UpdatePlayerAngle.
|
|
// Given a range of possible steering inputs, finds a steering input that corresponds to the desired angle change.
|
|
static INT16 P_FindClosestTurningForAngle(player_t *player, INT32 targetAngle, INT16 lowBound, INT16 highBound)
|
|
{
|
|
INT16 newBound;
|
|
INT16 preferred = lowBound;
|
|
int attempts = 0;
|
|
|
|
// Only works if our low bound is actually our low bound.
|
|
if (highBound < lowBound)
|
|
{
|
|
INT16 tmp = lowBound;
|
|
lowBound = highBound;
|
|
highBound = tmp;
|
|
}
|
|
|
|
// Slightly frumpy binary search for the ideal turning input.
|
|
// We do this instead of reversing K_GetKartTurnValue so that future handling changes are automatically accounted for.
|
|
|
|
while (attempts++ < 20) // Practical calls of this function search maximum 10 times, this is solely for safety.
|
|
{
|
|
// These need to be treated as signed, or situations where boundaries straddle 0 are a mess.
|
|
INT32 lowAngle = K_GetKartTurnValue(player, lowBound) << TICCMD_REDUCE;
|
|
INT32 highAngle = K_GetKartTurnValue(player, highBound) << TICCMD_REDUCE;
|
|
|
|
// EXIT CONDITION 1: Hopeless search, target angle isn't between boundaries at all.
|
|
if (lowAngle >= targetAngle)
|
|
return lowBound;
|
|
if (highAngle <= targetAngle)
|
|
return highBound;
|
|
|
|
// Test the middle of our steering range, so we can see which side is more promising.
|
|
newBound = (lowBound + highBound) / 2;
|
|
|
|
// EXIT CONDITION 2: Boundaries converged and we're all out of precision.
|
|
if (newBound == lowBound || newBound == highBound)
|
|
break;
|
|
|
|
INT32 newAngle = K_GetKartTurnValue(player, newBound) << TICCMD_REDUCE;
|
|
|
|
angle_t lowError = abs(targetAngle - lowAngle);
|
|
angle_t highError = abs(targetAngle - highAngle);
|
|
angle_t newError = abs(targetAngle - newAngle);
|
|
|
|
// CONS_Printf("steering %d / %d / %d - angle %d / %d / %d - TA %d - error %d / %d / %d\n", lowBound, newBound, highBound, lowAngle, newAngle, highAngle, targetAngle, lowError, newError, highError);
|
|
|
|
// EXIT CONDITION 3: We got lucky!
|
|
if (lowError == 0)
|
|
return lowBound;
|
|
if (newError == 0)
|
|
return newBound;
|
|
if (highError == 0)
|
|
return highBound;
|
|
|
|
// If not, store the best estimate...
|
|
if (lowError <= newError && lowError <= highError)
|
|
preferred = lowBound;
|
|
if (highError <= newError && highError <= lowError)
|
|
preferred = highBound;
|
|
if (newError <= lowError && newError <= highError)
|
|
preferred = newBound;
|
|
|
|
// ....and adjust the bounds for another run.
|
|
if (lowAngle <= targetAngle && targetAngle <= newAngle)
|
|
highBound = newBound;
|
|
else
|
|
lowBound = newBound;
|
|
}
|
|
|
|
return preferred;
|
|
}
|
|
|
|
//
|
|
// P_UpdatePlayerAngle
|
|
//
|
|
// Updates player angleturn with cmd->turning
|
|
//
|
|
static void P_UpdatePlayerAngle(player_t *player)
|
|
{
|
|
angle_t angleChange = ANGLE_MAX;
|
|
UINT8 p = UINT8_MAX;
|
|
UINT8 i;
|
|
|
|
for (i = 0; i <= splitscreen; i++)
|
|
{
|
|
if (player == &players[g_localplayers[i]])
|
|
{
|
|
p = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
player->botvars.predictionError = 0;
|
|
|
|
// Don't apply steering just yet. If we make a correction, we'll need to adjust it.
|
|
INT16 targetsteering = K_UpdateSteeringValue(player->steering, player->cmd.turning);
|
|
angleChange = K_GetKartTurnValue(player, targetsteering) << TICCMD_REDUCE;
|
|
|
|
if (K_PlayerUsesBotMovement(player))
|
|
{
|
|
// You're a bot. Go where you're supposed to go
|
|
player->steering = targetsteering;
|
|
// But the "angle" field of this ticcmd stores your prediction error,
|
|
// which we use to apply friction. Transfer it!
|
|
player->botvars.predictionError = player->cmd.angle << TICCMD_REDUCE;
|
|
}
|
|
else if ((!(player->cmd.flags & TICCMD_RECEIVED)) && (!!(player->oldcmd.flags && TICCMD_RECEIVED)))
|
|
{
|
|
// Missed a single tic. This ticcmd is copied from their previous one
|
|
// (less the TICCMD_RECEIVED flag), so it will include an old angle, and
|
|
// steering towards that will turn unambitiously. A better guess is to
|
|
// assume their inputs are the same, and turn based on those for 1 tic.
|
|
player->steering = targetsteering;
|
|
// "Why not use this for multiple consecutive dropped tics?" Oversimplification:
|
|
// Clients have default netticbuffer 1, so missing more than 1 tic will freeze
|
|
// your client, and with it, your local camera. Our goal then becomes not to
|
|
// steer PAST the angle you can see, so the default turn solver behavior is best.
|
|
}
|
|
else
|
|
{
|
|
// With a full slam on the analog stick, how far could we steer in either direction?
|
|
INT16 steeringRight = K_UpdateSteeringValue(player->steering, KART_FULLTURN);
|
|
INT16 steeringLeft = K_UpdateSteeringValue(player->steering, -KART_FULLTURN);
|
|
|
|
#if 1
|
|
// When entering/leaving drifts, allow all legal turns with no easing.
|
|
// This is the hardest case for the turn solver, because your handling properties on
|
|
// client side are very different than your handling properties on server side—at least,
|
|
// until your drift status makes the full round-trip and is reflected in your gamestate.
|
|
if (player->drift && abs(player->drift) < 5 && player->cmd.latency)
|
|
{
|
|
steeringRight = KART_FULLTURN;
|
|
steeringLeft = -KART_FULLTURN;
|
|
}
|
|
#endif
|
|
|
|
angle_t maxTurnRight = K_GetKartTurnValue(player, steeringRight) << TICCMD_REDUCE;
|
|
angle_t maxTurnLeft = K_GetKartTurnValue(player, steeringLeft) << TICCMD_REDUCE;
|
|
|
|
// Grab local camera angle from ticcmd. Where do we actually want to go?
|
|
angle_t targetAngle = (player->cmd.angle) << TICCMD_REDUCE;
|
|
angle_t targetDelta = targetAngle - (player->mo->angle);
|
|
|
|
#define SOLVERANGLECHEATS
|
|
|
|
#ifdef SOLVERANGLECHEATS
|
|
// Corrections via fake turn go through easing.
|
|
// That means undoing them takes the same amount of time as doing them.
|
|
// This can lead to oscillating death spiral states on a multi-tic correction, as we swing past the target angle.
|
|
// So before we go into death-spirals, if our predicton is _almost_ right...
|
|
angle_t leniency_base = 2 * ANG1;
|
|
angle_t leniency = leniency_base * min(player->cmd.latency, 6);
|
|
// Don't force another turning tic, just give them the desired angle!
|
|
#endif
|
|
|
|
if (!(player->cmd.buttons & BT_DRIFT) && (abs(player->drift) == 1) && ((player->cmd.turning > 0) == (player->drift > 0)) && player->handleboost >= SLIPTIDEHANDLING)
|
|
{
|
|
// This drift release is eligible to start a sliptide. Don't do lag-compensation countersteer behavior that could destroy it!
|
|
if (player->cmd.turning > 0)
|
|
{
|
|
steeringLeft = max(steeringLeft, 1);
|
|
steeringRight = max(steeringRight, steeringLeft);
|
|
}
|
|
else if (player->cmd.turning < 0)
|
|
{
|
|
steeringRight = min(steeringRight, -1);
|
|
steeringLeft = min(steeringLeft, steeringRight);
|
|
}
|
|
}
|
|
|
|
if (K_Sliptiding(player) && P_IsObjectOnGround(player->mo))
|
|
{
|
|
// Unless someone explicitly inputs a turn that would break their sliptide, keep sliptiding.
|
|
if (player->aizdriftstrat > 0 && player->cmd.turning >= 0)
|
|
{
|
|
steeringLeft = max(steeringLeft, 1);
|
|
steeringRight = max(steeringRight, steeringLeft);
|
|
}
|
|
else if ((player->aizdriftstrat < 0 && player->cmd.turning <= 0))
|
|
{
|
|
steeringRight = min(steeringRight, -1);
|
|
steeringLeft = min(steeringLeft, steeringRight);
|
|
}
|
|
}
|
|
|
|
if (maxTurnRight == 0 && maxTurnLeft == 0)
|
|
{
|
|
// Either we're dead on or we can't steer at all.
|
|
player->steering = targetsteering;
|
|
}
|
|
else
|
|
{
|
|
// We're off. Try to legally steer the player towards their camera.
|
|
player->steering = P_FindClosestTurningForAngle(player, targetDelta, steeringLeft, steeringRight);
|
|
//CONS_Printf("aiz %d - dr %d - hb %d\n", player->aizdriftstrat, player->drift, player->handleboost);
|
|
//CONS_Printf("st %d - ts %d - t %d\n", player->steering, targetsteering, player->cmd.turning);
|
|
//CONS_Printf("%d\n", player->steering - targetsteering);
|
|
angleChange = K_GetKartTurnValue(player, player->steering) << TICCMD_REDUCE;
|
|
|
|
#ifdef SOLVERANGLECHEATS
|
|
// And if the resulting steering input is close enough, snap them exactly.
|
|
if (min(targetDelta - angleChange, angleChange - targetDelta) <= leniency)
|
|
angleChange = targetDelta;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (p == UINT8_MAX)
|
|
{
|
|
// When F12ing players, set local angle directly.
|
|
P_SetPlayerAngle(player, player->angleturn + angleChange);
|
|
player->mo->angle = player->angleturn;
|
|
}
|
|
else
|
|
{
|
|
player->angleturn += angleChange;
|
|
player->mo->angle = player->angleturn;
|
|
}
|
|
|
|
if (!cv_allowmlook.value || player->spectator == false)
|
|
{
|
|
player->aiming = 0;
|
|
}
|
|
else
|
|
{
|
|
player->aiming += (player->cmd.aiming << TICCMD_REDUCE);
|
|
player->aiming = G_ClipAimingPitch((INT32 *)&player->aiming);
|
|
}
|
|
|
|
if (p != UINT8_MAX)
|
|
{
|
|
localaiming[p] = player->aiming;
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_MovePlayer
|
|
void P_MovePlayer(player_t *player)
|
|
{
|
|
ticcmd_t *cmd;
|
|
//INT32 i;
|
|
|
|
fixed_t runspd;
|
|
|
|
cmd = &player->cmd;
|
|
runspd = 14*player->mo->scale; //srb2kart
|
|
|
|
// Let's have some movement speed fun on low-friction surfaces, JUST for players...
|
|
// (high friction surfaces shouldn't have any adjustment, since the acceleration in
|
|
// this game is super high and that ends up cheesing high-friction surfaces.)
|
|
runspd = FixedMul(runspd, player->mo->movefactor);
|
|
|
|
// Control relinquishing stuff!
|
|
if (player->nocontrol || player->respawn.state == RESPAWNST_MOVE)
|
|
player->pflags |= PF_STASIS;
|
|
|
|
// note: don't unset stasis here
|
|
|
|
if (player->spectator)
|
|
{
|
|
player->mo->eflags &= ~MFE_VERTICALFLIP; // deflip...
|
|
return;
|
|
}
|
|
|
|
//////////////////////
|
|
// MOVEMENT CODE //
|
|
//////////////////////
|
|
|
|
P_UpdatePlayerAngle(player);
|
|
|
|
ticruned++;
|
|
if (!(cmd->flags & TICCMD_RECEIVED))
|
|
ticmiss++;
|
|
|
|
P_3dMovement(player);
|
|
|
|
if (cmd->turning == 0)
|
|
{
|
|
player->justDI = 0;
|
|
}
|
|
|
|
// Kart frames
|
|
if (player->icecube.frozen)
|
|
{
|
|
INT32 spd = FixedMul(player->mo->scale, FixedHypot(player->mo->momx, player->mo->momy)) / FRACUNIT;
|
|
P_SetPlayerMobjState(player->mo, S_KART_SPINOUT);
|
|
player->drawangle -= max(2, spd / 6) * ANG1;
|
|
P_ResetPitchRoll(player->mo);
|
|
}
|
|
else if (player->tumbleBounces > 0)
|
|
{
|
|
fixed_t playerSpeed = P_AproxDistance(player->mo->momx, player->mo->momy); // maybe momz too?
|
|
|
|
const UINT8 minSpinSpeed = 4;
|
|
UINT8 spinSpeed = max(minSpinSpeed, min(8 + minSpinSpeed, (playerSpeed / player->mo->scale) * 2));
|
|
|
|
UINT8 rollSpeed = max(1, min(8, player->tumbleHeight / 10));
|
|
|
|
if (player->pflags & PF_TUMBLELASTBOUNCE)
|
|
spinSpeed = 2;
|
|
|
|
P_SetPlayerMobjState(player->mo, S_KART_SPINOUT);
|
|
player->drawangle -= (ANGLE_11hh * spinSpeed);
|
|
|
|
player->mo->rollangle -= (ANGLE_11hh * rollSpeed);
|
|
|
|
if (player->pflags & PF_TUMBLELASTBOUNCE)
|
|
{
|
|
if (abs((signed)(player->mo->angle - player->drawangle)) < ANGLE_22h)
|
|
player->drawangle = player->mo->angle;
|
|
|
|
if (abs((signed)player->mo->rollangle) < ANGLE_22h)
|
|
player->mo->rollangle = 0;
|
|
}
|
|
}
|
|
else if (player->carry == CR_SLIDING)
|
|
{
|
|
P_SetPlayerMobjState(player->mo, S_KART_SPINOUT);
|
|
player->drawangle -= ANGLE_22h;
|
|
player->mo->rollangle = 0;
|
|
player->glanceDir = 0;
|
|
player->pflags &= ~PF_GAINAX;
|
|
}
|
|
else if ((player->pflags & PF_FAULT) || (player->spinouttimer > 0) || (player->turbine && (player->mo->flags & MF_NOCLIP)))
|
|
{
|
|
tic_t timer = 0;
|
|
|
|
if ((player->pflags & PF_FAULT))
|
|
timer = player->nocontrol;
|
|
else if (player->spinouttimer > 0)
|
|
timer = player->spinouttimer;
|
|
else if (player->turbine && (player->mo->flags & MF_NOCLIP))
|
|
timer = TICRATE;
|
|
|
|
UINT16 speed = timer / 8;
|
|
|
|
if (speed > 8)
|
|
speed = 8;
|
|
else if (speed < 1)
|
|
speed = 1;
|
|
|
|
P_SetPlayerMobjState(player->mo, S_KART_SPINOUT);
|
|
|
|
if (speed == 1 && abs((signed)(player->mo->angle - player->drawangle)) < ANGLE_22h)
|
|
player->drawangle = player->mo->angle; // Face forward at the end of the animation
|
|
else
|
|
player->drawangle -= (ANGLE_11hh * speed);
|
|
|
|
player->mo->rollangle = 0;
|
|
}
|
|
else
|
|
{
|
|
if (player->trickpanel > TRICKSTATE_READY)
|
|
{
|
|
if (player->trickpanel <= TRICKSTATE_RIGHT) // right/forward
|
|
{
|
|
player->drawangle += ANGLE_22h;
|
|
}
|
|
else //if (player->trickpanel >= TRICKSTATE_LEFT) // left/back
|
|
{
|
|
player->drawangle -= ANGLE_22h;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
K_KartMoveAnimation(player);
|
|
|
|
player->drawangle = player->mo->angle;
|
|
|
|
if (player->aizdriftturn)
|
|
{
|
|
player->drawangle += player->aizdriftturn;
|
|
}
|
|
else if (player->drift != 0)
|
|
{
|
|
INT32 a = (ANGLE_45 / 5) * player->drift;
|
|
|
|
if (player->mo->eflags & MFE_UNDERWATER)
|
|
a /= 2;
|
|
|
|
player->drawangle += a;
|
|
}
|
|
}
|
|
|
|
player->mo->rollangle = 0;
|
|
}
|
|
|
|
//{ SRB2kart
|
|
|
|
// Drifting sound
|
|
// Start looping the sound now.
|
|
if (leveltime % 50 == 0 && onground && player->drift != 0)
|
|
S_StartSound(player->mo, sfx_drift);
|
|
// Leveltime being 50 might take a while at times. We'll start it up once, isntantly.
|
|
else if (!S_SoundPlaying(player->mo, sfx_drift) && onground && player->drift != 0)
|
|
S_StartSound(player->mo, sfx_drift);
|
|
// Ok, we'll stop now.
|
|
else if (player->drift == 0)
|
|
S_StopSoundByID(player->mo, sfx_drift);
|
|
|
|
K_MoveKartPlayer(player, onground);
|
|
//}
|
|
|
|
//////////////////
|
|
//GAMEPLAY STUFF//
|
|
//////////////////
|
|
|
|
////////////////////////////
|
|
//SPINNING AND SPINDASHING//
|
|
////////////////////////////
|
|
|
|
// SRB2kart - Drifting smoke and fire
|
|
if ((player->sneakertimer || player->panelsneakertimer || player->weaksneakertimer || player->flamedash)
|
|
&& onground && (leveltime & 1))
|
|
K_SpawnBoostTrail(player);
|
|
|
|
if (player->invincibilitytimer > 0)
|
|
K_SpawnSparkleTrail(player->mo);
|
|
|
|
if (player->wipeoutslow > 1 && (leveltime & 1))
|
|
K_SpawnWipeoutTrail(player->mo);
|
|
|
|
K_DriftDustHandling(player->mo);
|
|
|
|
// Crush test...
|
|
if ((player->mo->ceilingz - player->mo->floorz < player->mo->height) && !(player->mo->flags & MF_NOCLIP))
|
|
{
|
|
if (player->spectator)
|
|
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_SPECTATOR); // Respawn crushed spectators
|
|
else
|
|
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_CRUSHED);
|
|
|
|
if (player->playerstate == PST_DEAD)
|
|
return;
|
|
}
|
|
|
|
#ifdef FLOORSPLATS
|
|
if (cv_shadow.value && rendermode == render_soft)
|
|
R_AddFloorSplat(player->mo->subsector, player->mo, "SHADOW", player->mo->x,
|
|
player->mo->y, player->mo->floorz, SPLATDRAWMODE_OPAQUE);
|
|
#endif
|
|
|
|
// Look for blocks to bust up
|
|
// Because of FF_SHATTER, we should look for blocks constantly,
|
|
// not just when spinning or playing as Knuckles
|
|
if (CheckForBustableBlocks)
|
|
P_CheckBustableBlocks(player);
|
|
|
|
// Check for a BOUNCY sector!
|
|
if (CheckForBouncySector)
|
|
P_CheckBouncySectors(player);
|
|
|
|
// Look for Quicksand!
|
|
if (CheckForQuicksand)
|
|
P_CheckQuicksand(player);
|
|
}
|
|
|
|
static void P_DoZoomTube(player_t *player)
|
|
{
|
|
fixed_t speed;
|
|
mobj_t *waypoint = NULL;
|
|
fixed_t dist;
|
|
boolean reverse;
|
|
|
|
if (player->speed > 0)
|
|
reverse = false;
|
|
else
|
|
reverse = true;
|
|
|
|
player->flashing = 1;
|
|
|
|
speed = abs(player->speed);
|
|
|
|
// change slope
|
|
dist = P_AproxDistance(P_AproxDistance(player->mo->tracer->x - player->mo->x, player->mo->tracer->y - player->mo->y), player->mo->tracer->z - player->mo->z);
|
|
|
|
if (dist < 1)
|
|
dist = 1;
|
|
|
|
player->mo->momx = FixedMul(FixedDiv(player->mo->tracer->x - player->mo->x, dist), (speed));
|
|
player->mo->momy = FixedMul(FixedDiv(player->mo->tracer->y - player->mo->y, dist), (speed));
|
|
player->mo->momz = FixedMul(FixedDiv(player->mo->tracer->z - player->mo->z, dist), (speed));
|
|
|
|
// Calculate the distance between the player and the waypoint
|
|
// 'dist' already equals this.
|
|
|
|
// Will the player go past the waypoint?
|
|
if (speed > dist)
|
|
{
|
|
speed -= dist;
|
|
// If further away, set XYZ of player to waypoint location
|
|
P_UnsetThingPosition(player->mo);
|
|
player->mo->x = player->mo->tracer->x;
|
|
player->mo->y = player->mo->tracer->y;
|
|
player->mo->z = player->mo->tracer->z;
|
|
P_SetThingPosition(player->mo);
|
|
|
|
// ugh, duh!!
|
|
player->mo->floorz = player->mo->subsector->sector->floorheight;
|
|
player->mo->ceilingz = player->mo->subsector->sector->ceilingheight;
|
|
|
|
CONS_Debug(DBG_GAMELOGIC, "Looking for next waypoint...\n");
|
|
|
|
// Find next waypoint
|
|
waypoint = reverse ? P_GetPreviousTubeWaypoint(player->mo->tracer, false) : P_GetNextTubeWaypoint(player->mo->tracer, false);
|
|
|
|
if (waypoint)
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Found waypoint (sequence %d, number %d).\n", waypoint->threshold, waypoint->health);
|
|
|
|
P_SetTarget(&player->mo->tracer, waypoint);
|
|
|
|
// calculate MOMX/MOMY/MOMZ for next waypoint
|
|
|
|
// change slope
|
|
dist = P_AproxDistance(P_AproxDistance(player->mo->tracer->x - player->mo->x, player->mo->tracer->y - player->mo->y), player->mo->tracer->z - player->mo->z);
|
|
|
|
if (dist < 1)
|
|
dist = 1;
|
|
|
|
player->mo->momx = FixedMul(FixedDiv(player->mo->tracer->x - player->mo->x, dist), (speed));
|
|
player->mo->momy = FixedMul(FixedDiv(player->mo->tracer->y - player->mo->y, dist), (speed));
|
|
player->mo->momz = FixedMul(FixedDiv(player->mo->tracer->z - player->mo->z, dist), (speed));
|
|
}
|
|
else
|
|
{
|
|
P_SetTarget(&player->mo->tracer, NULL); // Else, we just let them fly.
|
|
player->carry = CR_NONE;
|
|
|
|
CONS_Debug(DBG_GAMELOGIC, "Next waypoint not found, releasing from track...\n");
|
|
}
|
|
}
|
|
|
|
// change angle
|
|
if (player->mo->tracer)
|
|
{
|
|
player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, player->mo->tracer->x, player->mo->tracer->y);
|
|
P_SetPlayerAngle(player, player->mo->angle);
|
|
}
|
|
|
|
P_SetPlayerMobjState(player->mo, S_KART_SPINOUT);
|
|
player->drawangle -= ANGLE_22h;
|
|
}
|
|
|
|
#if 0
|
|
//
|
|
// P_NukeAllPlayers
|
|
//
|
|
// Hurts all players
|
|
// source = guy who gets the credit
|
|
//
|
|
static void P_NukeAllPlayers(player_t *player)
|
|
{
|
|
mobj_t *mo;
|
|
UINT8 i;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i])
|
|
continue;
|
|
if (players[i].spectator)
|
|
continue;
|
|
if (!players[i].mo)
|
|
continue;
|
|
if (players[i].mo == player->mo)
|
|
continue;
|
|
if (players[i].mo->health <= 0)
|
|
continue;
|
|
|
|
P_DamageMobj(players[i].mo, player->mo, player->mo, 1, DMG_NORMAL);
|
|
}
|
|
|
|
CONS_Printf(M_GetText("%s caused a world of pain.\n"), player_names[player-players]);
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// P_NukeEnemies
|
|
// Looks for something you can hit - Used for bomb shield
|
|
//
|
|
void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius)
|
|
{
|
|
mobj_t *mo;
|
|
thinker_t *think;
|
|
|
|
radius = FixedMul(radius, mapobjectscale);
|
|
|
|
for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
|
|
{
|
|
if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
|
|
continue;
|
|
|
|
mo = (mobj_t *)think;
|
|
|
|
if (!(mo->flags & MF_SHOOTABLE) && (mo->type != MT_SPB)) // Don't want to give SPB MF_SHOOTABLE, to ensure it's undamagable through other means
|
|
continue;
|
|
|
|
if (abs(inflictor->x - mo->x) > radius || abs(inflictor->y - mo->y) > radius || abs(inflictor->z - mo->z) > radius)
|
|
continue; // Workaround for possible integer overflow in the below -Red
|
|
|
|
if (P_AproxDistance(P_AproxDistance(inflictor->x - mo->x, inflictor->y - mo->y), inflictor->z - mo->z) > radius)
|
|
continue;
|
|
|
|
if (mo->type == MT_SPB) // If you destroy a SPB, you don't get the luxury of a cooldown.
|
|
{
|
|
spbplace = -1;
|
|
itemCooldowns[KITEM_SPB - 1] = 0;
|
|
}
|
|
|
|
if (mo->flags & MF_BOSS) //don't OHKO bosses nor players!
|
|
P_DamageMobj(mo, inflictor, source, 1, DMG_NORMAL|DMG_CANTHURTSELF);
|
|
else if (mo->type == MT_PLAYER) // Lightning shield: Combo players.
|
|
P_DamageMobj(mo, inflictor, source, 1, DMG_NORMAL|DMG_CANTHURTSELF|DMG_WOMBO);
|
|
else
|
|
P_DamageMobj(mo, inflictor, source, 1000, DMG_NORMAL|DMG_CANTHURTSELF);
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_ConsiderAllGone
|
|
// Shamelessly lifted from TD. Thanks, Sryder!
|
|
//
|
|
|
|
// SRB2Kart: Use for GP?
|
|
/*
|
|
static void P_ConsiderAllGone(void)
|
|
{
|
|
INT32 i, lastdeadplayer = -1, deadtimercheck = INT32_MAX;
|
|
|
|
if (countdown2)
|
|
return;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i])
|
|
continue;
|
|
|
|
if (players[i].playerstate != PST_DEAD && !players[i].spectator && players[i].mo && players[i].mo->health)
|
|
break;
|
|
|
|
if (players[i].spectator)
|
|
{
|
|
if (lastdeadplayer == -1)
|
|
lastdeadplayer = i;
|
|
}
|
|
else if (players[i].lives > 0)
|
|
{
|
|
lastdeadplayer = i;
|
|
if (players[i].deadtimer < deadtimercheck)
|
|
deadtimercheck = players[i].deadtimer;
|
|
}
|
|
}
|
|
|
|
if (i == MAXPLAYERS && lastdeadplayer != -1 && deadtimercheck > 2*TICRATE) // the last killed player will reset the level in G_DoReborn
|
|
{
|
|
//players[lastdeadplayer].spectator = true;
|
|
players[lastdeadplayer].outofcoop = true;
|
|
players[lastdeadplayer].playerstate = PST_REBORN;
|
|
}
|
|
}
|
|
*/
|
|
|
|
//
|
|
// P_DeathThink
|
|
// Fall on your face when dying.
|
|
// Decrease POV height to floor height.
|
|
//
|
|
static void P_DeathThink(player_t *player)
|
|
{
|
|
boolean playerGone = false;
|
|
|
|
player->deltaviewheight = 0;
|
|
|
|
if (player->deadtimer < INT32_MAX)
|
|
player->deadtimer++;
|
|
|
|
if ((player->pflags & PF_NOCONTEST) && (gametyperules & GTR_CIRCUIT))
|
|
{
|
|
player->karthud[khud_timeovercam]++;
|
|
|
|
if (player->mo)
|
|
{
|
|
player->mo->flags |= (MF_NOGRAVITY|MF_NOCLIP);
|
|
}
|
|
}
|
|
else
|
|
player->karthud[khud_timeovercam] = 0;
|
|
|
|
if (player->pflags & PF_NOCONTEST)
|
|
{
|
|
playerGone = true;
|
|
}
|
|
else if (player->bot == false)
|
|
{
|
|
if (G_GametypeUsesLives() == true && player->lives == 0)
|
|
{
|
|
playerGone = true;
|
|
}
|
|
}
|
|
|
|
if ((player->pflags & PF_ELIMINATED) || exitcountdown)
|
|
{
|
|
playerGone = true;
|
|
}
|
|
|
|
if (playerGone == false && player->deadtimer > TICRATE)
|
|
{
|
|
if (!netgame && !splitscreen
|
|
&& player->bot == false
|
|
#ifdef DEVELOP
|
|
&& player->spectator == false
|
|
#endif
|
|
&& (gametyperules & GTR_CHECKPOINTS))
|
|
{
|
|
G_SetRetryFlag();
|
|
}
|
|
else
|
|
{
|
|
if (!player->exiting)
|
|
player->playerstate = PST_REBORN;
|
|
}
|
|
}
|
|
|
|
// Spectate another player after 2 seconds
|
|
if (G_IsPartyLocal(player - players) && playerGone == true &&
|
|
(gametyperules & GTR_BUMPERS) && battleprisons == false &&
|
|
player->deadtimer == 2*TICRATE)
|
|
{
|
|
K_ToggleDirector(G_PartyPosition(player - players), true);
|
|
}
|
|
|
|
if (!player->mo)
|
|
return;
|
|
|
|
//K_KartResetPlayerColor(player); -- called at death, don't think we need to re-establish
|
|
|
|
P_CalcHeight(player);
|
|
}
|
|
|
|
//
|
|
// P_MoveCamera: make sure the camera is not outside the world and looks at the player avatar
|
|
//
|
|
|
|
camera_t camera[MAXSPLITSCREENPLAYERS]; // Four cameras, three for splitscreen
|
|
|
|
void CV_CamRotate_OnChange(void);
|
|
void CV_CamRotate_OnChange(void)
|
|
{
|
|
if (cv_cam_rotate[0].value < 0)
|
|
CV_SetValue(&cv_cam_rotate[0], cv_cam_rotate[0].value + 360);
|
|
else if (cv_cam_rotate[0].value > 359)
|
|
CV_SetValue(&cv_cam_rotate[0], cv_cam_rotate[0].value % 360);
|
|
}
|
|
|
|
void CV_CamRotate2_OnChange(void);
|
|
void CV_CamRotate2_OnChange(void)
|
|
{
|
|
if (cv_cam_rotate[1].value < 0)
|
|
CV_SetValue(&cv_cam_rotate[1], cv_cam_rotate[1].value + 360);
|
|
else if (cv_cam_rotate[1].value > 359)
|
|
CV_SetValue(&cv_cam_rotate[1], cv_cam_rotate[1].value % 360);
|
|
}
|
|
|
|
void CV_CamRotate3_OnChange(void);
|
|
void CV_CamRotate3_OnChange(void)
|
|
{
|
|
if (cv_cam_rotate[2].value < 0)
|
|
CV_SetValue(&cv_cam_rotate[2], cv_cam_rotate[2].value + 360);
|
|
else if (cv_cam_rotate[2].value > 359)
|
|
CV_SetValue(&cv_cam_rotate[2], cv_cam_rotate[2].value % 360);
|
|
}
|
|
|
|
void CV_CamRotate4_OnChange(void);
|
|
void CV_CamRotate4_OnChange(void)
|
|
{
|
|
if (cv_cam_rotate[3].value < 0)
|
|
CV_SetValue(&cv_cam_rotate[3], cv_cam_rotate[3].value + 360);
|
|
else if (cv_cam_rotate[3].value > 359)
|
|
CV_SetValue(&cv_cam_rotate[3], cv_cam_rotate[3].value % 360);
|
|
}
|
|
|
|
fixed_t t_cam_dist[MAXSPLITSCREENPLAYERS] = {-42,-42,-42,-42};
|
|
fixed_t t_cam_height[MAXSPLITSCREENPLAYERS] = {-42,-42,-42,-42};
|
|
fixed_t t_cam_rotate[MAXSPLITSCREENPLAYERS] = {-42,-42,-42,-42};
|
|
|
|
void P_DemoCameraMovement(camera_t *cam, UINT8 num)
|
|
{
|
|
extern consvar_t cv_freecam_speed;
|
|
|
|
ticcmd_t *cmd;
|
|
angle_t thrustangle;
|
|
player_t *lastp;
|
|
angle_t turning;
|
|
|
|
boolean moving = false;
|
|
|
|
// first off we need to get button input
|
|
cmd = D_LocalTiccmd(num);
|
|
|
|
if (cmd->aiming != 0)
|
|
{
|
|
cam->aiming += cmd->aiming << TICCMD_REDUCE;
|
|
|
|
cam->reset_aiming = false;
|
|
}
|
|
|
|
turning = cmd->turning << TICCMD_REDUCE;
|
|
if (encoremode)
|
|
{
|
|
turning = -turning;
|
|
}
|
|
cam->angle += turning;
|
|
|
|
// camera movement:
|
|
if (!cam->button_a_held && cv_freecam_speed.value)
|
|
{
|
|
int dir = ((cmd->buttons & BT_ACCELERATE) ? 1 : 0) + ((cmd->buttons & BT_BRAKE) ? -1 : 0);
|
|
|
|
fixed_t spd = 32*mapobjectscale;
|
|
if (cv_freecam_speed.value > 1)
|
|
spd *= cv_freecam_speed.value;
|
|
else if (cv_freecam_speed.value < -1)
|
|
spd /= -cv_freecam_speed.value;
|
|
|
|
switch (dir)
|
|
{
|
|
case 1:
|
|
cam->z += spd;
|
|
moving = true;
|
|
break;
|
|
|
|
case -1:
|
|
cam->z -= spd;
|
|
moving = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!(cmd->buttons & (BT_ACCELERATE | BT_DRIFT)) && cam->button_a_held)
|
|
{
|
|
cam->button_a_held--;
|
|
}
|
|
|
|
// if you hold Y, you will lock on to displayplayer. (The last player you were ""f12-ing"")
|
|
if (cam->freecam && cmd->buttons & BT_BAIL)
|
|
{
|
|
lastp = &players[displayplayers[0]]; // Fun fact, I was trying displayplayers[0]->mo as if it was Lua like an absolute idiot.
|
|
cam->angle = R_PointToAngle2(cam->x, cam->y, lastp->mo->x, lastp->mo->y);
|
|
cam->aiming = R_PointToAngle2(0, cam->z, R_PointToDist2(cam->x, cam->y, lastp->mo->x, lastp->mo->y), lastp->mo->z + lastp->mo->scale*128*P_MobjFlip(lastp->mo)); // This is still unholy. Aim a bit above their heads.
|
|
|
|
cam->reset_aiming = false;
|
|
}
|
|
|
|
if (cmd->forwardmove != 0)
|
|
{
|
|
moving = true;
|
|
}
|
|
|
|
// After switching to democam, the vertical angle of
|
|
// chasecam is inherited. This is intentional because it
|
|
// creates a smooth transition. However, moving
|
|
// forward/back will have a slope. So, as long as democam
|
|
// controls haven't been used to alter the vertical angle,
|
|
// slowly reset it to flat.
|
|
if ((cam->reset_aiming && moving) || ((cmd->buttons & BT_DRIFT) && !cam->button_a_held))
|
|
{
|
|
INT32 aiming = cam->aiming;
|
|
INT32 smooth = FixedMul(ANGLE_11hh / 4, FCOS(cam->aiming));
|
|
|
|
if (abs(smooth) < abs(aiming))
|
|
{
|
|
cam->aiming -= smooth * intsign(aiming);
|
|
}
|
|
else
|
|
{
|
|
cam->aiming = 0;
|
|
cam->reset_aiming = false; // completely smoothed out
|
|
}
|
|
}
|
|
|
|
if (rendermode == render_soft
|
|
#ifdef HWRENDER
|
|
|| (rendermode == render_opengl && (cv_glshearing.value == 1))
|
|
#endif
|
|
)
|
|
{
|
|
// Extra restriction on this so it's not possible to
|
|
// distort the view too much.
|
|
if ((INT32)cam->aiming > ANGLE_45)
|
|
cam->aiming = ANGLE_45;
|
|
else if ((INT32)cam->aiming < -ANGLE_45)
|
|
cam->aiming = -ANGLE_45;
|
|
}
|
|
else
|
|
{
|
|
G_ClipAimingPitch((INT32 *)&cam->aiming);
|
|
}
|
|
|
|
cam->momx = cam->momy = cam->momz = 0;
|
|
|
|
if (cmd->forwardmove != 0 && cv_freecam_speed.value)
|
|
{
|
|
fixed_t spd = cmd->forwardmove*mapobjectscale;
|
|
if (cv_freecam_speed.value > 1)
|
|
spd *= cv_freecam_speed.value;
|
|
else if (cv_freecam_speed.value < -1)
|
|
spd /= -cv_freecam_speed.value;
|
|
|
|
thrustangle = cam->angle >> ANGLETOFINESHIFT;
|
|
|
|
cam->x += FixedMul(spd, FINECOSINE(thrustangle));
|
|
cam->y += FixedMul(spd, FINESINE(thrustangle));
|
|
|
|
if (!cam->reset_aiming)
|
|
{
|
|
cam->z += FixedMul(spd, AIMINGTOSLOPE(cam->aiming));
|
|
}
|
|
// momentums are useless here, directly add to the coordinates
|
|
|
|
// this.......... doesn't actually check for floors and walls and whatnot but the function to do that is a pure mess so fuck that.
|
|
// besides freecam going inside walls sounds pretty cool on paper.
|
|
}
|
|
|
|
// update subsector to avoid crashes;
|
|
cam->subsector = R_PointInSubsector(cam->x, cam->y);
|
|
}
|
|
|
|
void P_ToggleDemoCamera(UINT8 viewnum)
|
|
{
|
|
camera_t *cam = &camera[viewnum];
|
|
|
|
if (!cam->freecam) // toggle on
|
|
{
|
|
cam->freecam = true;
|
|
cam->button_a_held = 2;
|
|
cam->reset_aiming = true;
|
|
}
|
|
else // toggle off
|
|
{
|
|
cam->freecam = false;
|
|
}
|
|
}
|
|
|
|
void P_ResetCamera(player_t *player, camera_t *thiscam)
|
|
{
|
|
if (g_endcam.active)
|
|
{
|
|
return;
|
|
}
|
|
|
|
tic_t tries = 0;
|
|
fixed_t x, y, z;
|
|
|
|
if (thiscam->freecam)
|
|
return; // do not reset the camera there.
|
|
|
|
if (!player->mo)
|
|
return;
|
|
|
|
if (thiscam->chase && player->mo->health <= 0)
|
|
return;
|
|
|
|
thiscam->chase = !player->spectator;
|
|
x = player->mo->x - P_ReturnThrustX(player->mo, thiscam->angle, player->mo->radius);
|
|
y = player->mo->y - P_ReturnThrustY(player->mo, thiscam->angle, player->mo->radius);
|
|
if (player->mo->eflags & MFE_VERTICALFLIP)
|
|
z = player->mo->z + player->mo->height - P_GetPlayerViewHeight(player) - 16*FRACUNIT;
|
|
else
|
|
z = player->mo->z + P_GetPlayerViewHeight(player);
|
|
|
|
// set bits for the camera
|
|
thiscam->x = x;
|
|
thiscam->y = y;
|
|
thiscam->z = z;
|
|
thiscam->centerfloorz = player->mo->floorz;
|
|
thiscam->centerceilingz = player->mo->ceilingz;
|
|
|
|
thiscam->angle = player->mo->angle;
|
|
thiscam->aiming = 0;
|
|
thiscam->relativex = 0;
|
|
|
|
thiscam->subsector = R_PointInSubsector(thiscam->x,thiscam->y);
|
|
|
|
thiscam->radius = 20*FRACUNIT;
|
|
thiscam->height = 16*FRACUNIT;
|
|
|
|
thiscam->reset_aiming = true;
|
|
|
|
while (!P_MoveChaseCamera(player,thiscam,true) && ++tries < 2*TICRATE);
|
|
}
|
|
|
|
boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcalled)
|
|
{
|
|
static boolean lookbackactive[MAXSPLITSCREENPLAYERS];
|
|
static UINT8 lookbackdelay[MAXSPLITSCREENPLAYERS];
|
|
UINT8 num;
|
|
angle_t angle = 0, focusangle = 0, focusaiming = 0, pitch = 0;
|
|
fixed_t x, y, z, dist, distxy, distz, viewpointx, viewpointy, camspeed, camdist, camheight, pviewheight;
|
|
fixed_t pan, xpan, ypan;
|
|
INT32 camrotate;
|
|
boolean camstill, lookback, lookbackdown;
|
|
UINT8 timeover;
|
|
mobj_t *mo;
|
|
fixed_t f1, f2;
|
|
fixed_t speed;
|
|
|
|
fixed_t playerScale;
|
|
fixed_t scaleDiff;
|
|
fixed_t cameraScale = mapobjectscale;
|
|
|
|
sonicloopcamvars_t *loop = &player->loop.camera;
|
|
tic_t loop_out = leveltime - loop->enter_tic;
|
|
tic_t loop_in = max(leveltime, loop->exit_tic) - loop->exit_tic;
|
|
boolean affected_by_loop = (loop_out <=
|
|
(loop->zoom_in_speed + loop->zoom_out_speed) && leveltime > introtime);
|
|
|
|
thiscam->old_x = thiscam->x;
|
|
thiscam->old_y = thiscam->y;
|
|
thiscam->old_z = thiscam->z;
|
|
thiscam->old_angle = thiscam->angle;
|
|
thiscam->old_aiming = thiscam->aiming;
|
|
|
|
if (roundqueue.snapshotmaps == true)
|
|
return true;
|
|
|
|
// We probably shouldn't move the camera if there is no player or player mobj somehow
|
|
if (!player || !player->mo)
|
|
return true;
|
|
|
|
// This can happen when joining
|
|
if (thiscam->subsector == NULL || thiscam->subsector->sector == NULL)
|
|
return true;
|
|
|
|
if (thiscam == &camera[1]) // Camera 2
|
|
{
|
|
num = 1;
|
|
}
|
|
else if (thiscam == &camera[2]) // Camera 3
|
|
{
|
|
num = 2;
|
|
}
|
|
else if (thiscam == &camera[3]) // Camera 4
|
|
{
|
|
num = 3;
|
|
}
|
|
else // Camera 1
|
|
{
|
|
num = 0;
|
|
}
|
|
|
|
if ((thiscam->freecam || player->spectator) && !g_endcam.active)
|
|
{
|
|
P_DemoCameraMovement(thiscam, num);
|
|
return true;
|
|
}
|
|
|
|
if (paused || P_AutoPause())
|
|
return true;
|
|
|
|
if (g_endcam.active)
|
|
{
|
|
K_MoveEndCamera(thiscam);
|
|
return true;
|
|
}
|
|
|
|
playerScale = FixedDiv(player->mo->scale, mapobjectscale);
|
|
scaleDiff = playerScale - FRACUNIT;
|
|
|
|
mo = player->mo;
|
|
|
|
if (P_MobjIsFrozen(mo) || player->playerstate == PST_DEAD || F_CreditsDemoExitFade() >= 0)
|
|
{
|
|
// Do not move the camera while in hitlag!
|
|
// The camera zooming out after you got hit makes it hard to focus on the vibration.
|
|
// of course, if you're in chase, don't forget the postimage - otherwise encore will flip back
|
|
if (thiscam->chase)
|
|
P_CalcChasePostImg(player, thiscam);
|
|
|
|
return true;
|
|
}
|
|
|
|
if ((player->pflags & PF_NOCONTEST) && (gametyperules & GTR_CIRCUIT) && player->karthud[khud_timeovercam] != 0) // 1 for momentum keep, 2 for turnaround
|
|
timeover = (player->karthud[khud_timeovercam] > 2*TICRATE ? 2 : 1);
|
|
else
|
|
timeover = 0;
|
|
|
|
if (!(player->playerstate == PST_DEAD || player->exiting || leveltime < introtime))
|
|
{
|
|
if (player->spectator) // force cam off for spectators
|
|
return true;
|
|
|
|
if (!cv_chasecam[num].value && thiscam == &camera[num])
|
|
return true;
|
|
}
|
|
|
|
if (!thiscam->chase && !resetcalled)
|
|
{
|
|
focusangle = localangle[num];
|
|
camrotate = cv_cam_rotate[num].value;
|
|
|
|
if (leveltime < introtime) // Whoooshy camera!
|
|
{
|
|
const INT32 introcam = (introtime - leveltime);
|
|
camrotate += introcam*5;
|
|
}
|
|
|
|
thiscam->angle = focusangle + FixedAngle(camrotate*FRACUNIT);
|
|
P_ResetCamera(player, thiscam);
|
|
return true;
|
|
}
|
|
|
|
// Adjust camera to match Grow/Shrink
|
|
cameraScale = FixedMul(cameraScale, FRACUNIT + (scaleDiff / 3));
|
|
|
|
thiscam->radius = 20*cameraScale;
|
|
thiscam->height = 16*cameraScale;
|
|
|
|
// Don't run while respawning from a cheatcheck
|
|
// Inu 4/8/13 Why not?!
|
|
// if (leveltime > 0 && timeinmap <= 0)
|
|
// return true;
|
|
|
|
if (demo.playback)
|
|
{
|
|
if (K_PlayerUsesBotMovement(player))
|
|
focusangle = mo->angle; // Bots don't even send cmd angle; they always turn where they want to!
|
|
else if (leveltime <= introtime)
|
|
focusangle = mo->angle; // Can't turn yet. P_UpdatePlayerAngle will ignore angle in stale ticcmds, chasecam should too.
|
|
else
|
|
focusangle = player->cmd.angle << TICCMD_REDUCE;
|
|
focusaiming = 0;
|
|
}
|
|
else
|
|
{
|
|
// Players-turned-bots outside of end of race contexts (Lua)
|
|
// don't update their local camera angle, so it should be updated
|
|
// somewhere - I choose here because it makes the most sense.
|
|
if (K_PlayerUsesBotMovement(player) && !player->bot)
|
|
P_ForceLocalAngle(player, mo->angle);
|
|
|
|
focusangle = localangle[num];
|
|
focusaiming = localaiming[num];
|
|
}
|
|
|
|
if (abs(thiscam->dpad_y_held) >= 2*TICRATE)
|
|
{
|
|
focusaiming += ANGLE_45 * intsign(thiscam->dpad_y_held) * P_MobjFlip(mo);
|
|
}
|
|
|
|
if (P_CameraThinker(player, thiscam, resetcalled))
|
|
return true;
|
|
|
|
lookback = K_GetKartButtons(player) & BT_LOOKBACK;
|
|
|
|
camspeed = cv_cam_speed[num].value;
|
|
camstill = cv_cam_still[num].value
|
|
|| player->seasaw // RR: seasaws lock the camera so that it isn't disorienting.
|
|
|| player->carry == CR_MUSHROOMHILLPOLE
|
|
;
|
|
camrotate = cv_cam_rotate[num].value;
|
|
camdist = FixedMul(cv_cam_dist[num].value, cameraScale);
|
|
camheight = FixedMul(cv_cam_height[num].value, cameraScale);
|
|
|
|
// Map-specific camera height
|
|
if (mapheaderinfo[gamemap-1]->cameraHeight >= 0)
|
|
{
|
|
if (r_splitscreen != 1)
|
|
camheight = FixedMul(mapheaderinfo[gamemap-1]->cameraHeight, cameraScale);
|
|
|
|
// For 2p SPLITSCREEN SPECIFICALLY:
|
|
// The view is pretty narrow, so move it back 3/20 of the way towards default camera height.
|
|
else {
|
|
// CONS_Printf( "Camera values: %f / %f / %f \n", FixedToFloat(mapheaderinfo[gamemap-1]->cameraHeight), FixedToFloat(cv_cam_height[num].value), FixedToFloat(cameraScale) );
|
|
camheight = FixedMul((mapheaderinfo[gamemap-1]->cameraHeight*17 + cv_cam_height[num].value*3)/20, cameraScale);
|
|
}
|
|
}
|
|
|
|
thiscam->chaseheight = FixedDiv(camheight, cameraScale);
|
|
// CONS_Printf("thiscam camheight %d\n", thiscam->chaseheight/FRACUNIT);
|
|
|
|
if (loop_in < loop->zoom_in_speed)
|
|
{
|
|
fixed_t f = loop_out < loop->zoom_out_speed
|
|
? (loop_out * FRACUNIT) / loop->zoom_out_speed
|
|
: FRACUNIT - ((loop_in * FRACUNIT) / loop->zoom_in_speed);
|
|
|
|
camspeed -= FixedMul(f, camspeed - (FRACUNIT/10));
|
|
camdist += FixedMul(f, loop->dist);
|
|
}
|
|
|
|
if (loop_in < max(loop->pan_back, 1))
|
|
{
|
|
fixed_t f = (loop_in * FRACUNIT) / max(loop->pan_back, 1);
|
|
|
|
fixed_t dx = mo->x - thiscam->x;
|
|
fixed_t dy = mo->y - thiscam->y;
|
|
|
|
angle_t th = R_PointToAngle2(0, 0, dx, dy);
|
|
fixed_t d = AngleFixed(focusangle - th);
|
|
|
|
if (d > 180*FRACUNIT)
|
|
{
|
|
d -= (360*FRACUNIT);
|
|
}
|
|
|
|
focusangle = th + FixedAngle(FixedMul(f, d));
|
|
|
|
if (loop_in == 0)
|
|
{
|
|
focusaiming = R_PointToAngle2(0, thiscam->z, FixedHypot(dx, dy), mo->z);
|
|
}
|
|
}
|
|
|
|
if (loop_in == 0)
|
|
{
|
|
tic_t accel = max(loop->pan_accel, 1);
|
|
fixed_t f = (min(loop_out, accel) * FRACUNIT) / accel;
|
|
|
|
INT32 turn = AngleDeltaSigned(focusangle, player->loop.yaw - loop->pan);
|
|
INT32 turnspeed = FixedAngle(FixedMul(f, loop->pan_speed));
|
|
|
|
if (turn > turnspeed)
|
|
{
|
|
// TODO: this code let the panning angle flip
|
|
// depending on your camera angle when entering
|
|
// the loop.
|
|
// It caused just about every loop to look weird
|
|
// sometimes, since the camera could move
|
|
// differently than configured.
|
|
// I don't know if this behavior should ever come
|
|
// back, but in case it should, I'm leaving this
|
|
// comment here.
|
|
#if 0
|
|
if (turn < ANGLE_90)
|
|
{
|
|
turnspeed = -(turnspeed);
|
|
}
|
|
#endif
|
|
|
|
focusangle += turnspeed;
|
|
}
|
|
}
|
|
|
|
if (timeover)
|
|
{
|
|
const INT32 timeovercam = max(0, min(180, (player->karthud[khud_timeovercam] - 2*TICRATE)*15));
|
|
camrotate += timeovercam;
|
|
}
|
|
else if (leveltime < introtime && !(modeattacking && !demo.playback)) // Whoooshy camera! (don't do this in RA when we PLAY, still do it in replays however~)
|
|
{
|
|
const INT32 introcam = (introtime - leveltime);
|
|
camrotate += introcam*5;
|
|
camdist += (introcam * cameraScale)*3;
|
|
camheight += (introcam * cameraScale)*2;
|
|
}
|
|
else if (player->exiting) // SRB2Kart: Leave the camera behind while exiting, for dramatic effect!
|
|
camstill = true;
|
|
else if ((lookback || lookbackdelay[num]) && !affected_by_loop)
|
|
{
|
|
// SRB2Kart -- Camera flip when looking backwards
|
|
#define MAXLOOKBACKDELAY 2
|
|
camspeed = FRACUNIT;
|
|
if (lookback)
|
|
{
|
|
camrotate += 180;
|
|
lookbackdelay[num] = MAXLOOKBACKDELAY;
|
|
}
|
|
else
|
|
lookbackdelay[num]--;
|
|
}
|
|
else if (player->respawn.state != RESPAWNST_NONE)
|
|
{
|
|
camspeed = 3*FRACUNIT/4;
|
|
}
|
|
lookbackdown = (lookbackdelay[num] == MAXLOOKBACKDELAY) != lookbackactive[num];
|
|
lookbackactive[num] = (lookbackdelay[num] == MAXLOOKBACKDELAY);
|
|
#undef MAXLOOKBACKDELAY
|
|
|
|
if (mo->eflags & MFE_VERTICALFLIP)
|
|
camheight += thiscam->height;
|
|
|
|
if (camspeed > FRACUNIT)
|
|
camspeed = FRACUNIT;
|
|
|
|
if (timeover)
|
|
angle = mo->angle + FixedAngle(camrotate*FRACUNIT);
|
|
else if (leveltime < introtime)
|
|
angle = focusangle + FixedAngle(camrotate*FRACUNIT);
|
|
else if (camstill || resetcalled || player->playerstate == PST_DEAD)
|
|
angle = thiscam->angle;
|
|
else
|
|
{
|
|
if (camspeed == FRACUNIT)
|
|
angle = focusangle + FixedAngle(camrotate<<FRACBITS);
|
|
else
|
|
{
|
|
angle_t input = focusangle + FixedAngle(camrotate<<FRACBITS) - thiscam->angle;
|
|
boolean invert = (input > ANGLE_180);
|
|
if (invert)
|
|
input = InvAngle(input);
|
|
|
|
input = FixedAngle(FixedMul(AngleFixed(input), camspeed));
|
|
if (invert)
|
|
input = InvAngle(input);
|
|
|
|
angle = thiscam->angle + input;
|
|
}
|
|
}
|
|
|
|
/* The Top is Big Large so zoom out */
|
|
if (player->curshield == KSHIELD_TOP)
|
|
{
|
|
camdist += 40 * mapobjectscale;
|
|
camheight += 40 * mapobjectscale;
|
|
}
|
|
|
|
if (!resetcalled && (leveltime >= introtime && timeover != 2)
|
|
&& (t_cam_rotate[num] != -42))
|
|
{
|
|
angle = FixedAngle(camrotate*FRACUNIT);
|
|
thiscam->angle = angle;
|
|
}
|
|
|
|
// sets ideal cam pos
|
|
{
|
|
const fixed_t speedthreshold = 48*cameraScale;
|
|
const fixed_t olddist = P_AproxDistance(mo->x - thiscam->x, mo->y - thiscam->y);
|
|
|
|
fixed_t lag, distoffset;
|
|
|
|
dist = camdist;
|
|
|
|
if (player->karthud[khud_boostcam])
|
|
{
|
|
dist -= FixedMul(11*dist/16, player->karthud[khud_boostcam]);
|
|
}
|
|
|
|
if (player->loop.radius)
|
|
{
|
|
speed = player->speed;
|
|
}
|
|
else
|
|
{
|
|
speed = P_AproxDistance(P_AproxDistance(mo->momx, mo->momy), mo->momz / 16);
|
|
}
|
|
|
|
lag = FRACUNIT - ((FixedDiv(speed, speedthreshold) - FRACUNIT) * 2);
|
|
|
|
if (lag > FRACUNIT)
|
|
{
|
|
lag = FRACUNIT;
|
|
}
|
|
|
|
if (lag < camspeed)
|
|
{
|
|
lag = camspeed;
|
|
}
|
|
|
|
distoffset = dist - olddist;
|
|
dist = olddist + FixedMul(distoffset, lag);
|
|
|
|
if (dist < 0)
|
|
{
|
|
dist = 0;
|
|
}
|
|
}
|
|
|
|
if (mo->standingslope)
|
|
{
|
|
pitch = (angle_t)FixedMul(P_ReturnThrustX(mo, thiscam->angle - mo->standingslope->xydirection, FRACUNIT), (fixed_t)mo->standingslope->zangle);
|
|
|
|
if (mo->eflags & MFE_VERTICALFLIP)
|
|
{
|
|
if (pitch >= ANGLE_180)
|
|
pitch = 0;
|
|
}
|
|
else
|
|
{
|
|
if (pitch < ANGLE_180)
|
|
pitch = 0;
|
|
}
|
|
}
|
|
|
|
pitch = thiscam->pitch + (angle_t)FixedMul(pitch - thiscam->pitch, camspeed/4);
|
|
|
|
if (rendermode == render_opengl && !cv_glshearing.value)
|
|
distxy = FixedMul(dist, FINECOSINE((pitch>>ANGLETOFINESHIFT) & FINEMASK));
|
|
else
|
|
distxy = dist;
|
|
distz = -FixedMul(dist, FINESINE((pitch>>ANGLETOFINESHIFT) & FINEMASK));
|
|
|
|
x = mo->x - FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
|
|
y = mo->y - FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
|
|
|
|
// SRB2Kart: set camera panning
|
|
if (camstill || resetcalled || player->playerstate == PST_DEAD || player->loop.radius)
|
|
pan = xpan = ypan = 0;
|
|
else
|
|
{
|
|
if (player->drift != 0)
|
|
{
|
|
fixed_t panmax = (dist/5);
|
|
INT32 driftval = K_GetKartDriftSparkValue(player);
|
|
INT32 dc = player->driftcharge;
|
|
|
|
if (dc > driftval || dc < 0)
|
|
dc = driftval;
|
|
|
|
pan = FixedDiv(FixedMul((fixed_t)dc, panmax), driftval);
|
|
|
|
if (pan > panmax)
|
|
pan = panmax;
|
|
if (player->drift < 0)
|
|
pan *= -1;
|
|
}
|
|
else
|
|
pan = 0;
|
|
|
|
pan = thiscam->pan + FixedMul(pan - thiscam->pan, camspeed/4);
|
|
|
|
xpan = FixedMul(FINECOSINE(((angle+ANGLE_90)>>ANGLETOFINESHIFT) & FINEMASK), pan);
|
|
ypan = FixedMul(FINESINE(((angle+ANGLE_90)>>ANGLETOFINESHIFT) & FINEMASK), pan);
|
|
|
|
x += xpan;
|
|
y += ypan;
|
|
}
|
|
|
|
pviewheight = FixedMul(32<<FRACBITS, mo->scale);
|
|
|
|
if (mo->eflags & MFE_VERTICALFLIP)
|
|
{
|
|
distz = min(-camheight, distz);
|
|
z = mo->z + mo->height - pviewheight + distz;
|
|
}
|
|
else
|
|
{
|
|
distz = max(camheight, distz);
|
|
z = mo->z + pviewheight + distz;
|
|
}
|
|
|
|
z += player->cameraOffset;
|
|
|
|
// point viewed by the camera
|
|
// this point is just 64 unit forward the player
|
|
dist = 64*cameraScale;
|
|
viewpointx = mo->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist) + xpan;
|
|
viewpointy = mo->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist) + ypan;
|
|
|
|
if (timeover)
|
|
thiscam->angle = angle;
|
|
else if (!camstill && !resetcalled && !paused && timeover != 1)
|
|
thiscam->angle = R_PointToAngle2(thiscam->x, thiscam->y, viewpointx, viewpointy);
|
|
|
|
if (timeover == 1)
|
|
{
|
|
thiscam->momx = P_ReturnThrustX(NULL, mo->angle, 32*mo->scale); // Push forward
|
|
thiscam->momy = P_ReturnThrustY(NULL, mo->angle, 32*mo->scale);
|
|
thiscam->momz = 0;
|
|
}
|
|
else if (player->exiting || timeover == 2)
|
|
{
|
|
thiscam->momx = thiscam->momy = thiscam->momz = 0;
|
|
}
|
|
else if (leveltime < introtime)
|
|
{
|
|
thiscam->momx = FixedMul(x - thiscam->x, camspeed);
|
|
thiscam->momy = FixedMul(y - thiscam->y, camspeed);
|
|
thiscam->momz = FixedMul(z - thiscam->z, camspeed);
|
|
}
|
|
else
|
|
{
|
|
thiscam->momx = x - thiscam->x;
|
|
thiscam->momy = y - thiscam->y;
|
|
|
|
if (lookback && lookbackdelay[num] && !affected_by_loop) {
|
|
// when looking back, camera's momentum
|
|
// should inherit the momentum of the player
|
|
// plus extra
|
|
thiscam->momx += 2*mo->momx;
|
|
thiscam->momy += 2*mo->momy;
|
|
}
|
|
|
|
fixed_t z_speed = Easing_Linear(
|
|
player->karthud[khud_aircam],
|
|
camspeed * 3 / 5,
|
|
camspeed
|
|
);
|
|
|
|
thiscam->momz = FixedMul(z - thiscam->z, z_speed);
|
|
}
|
|
|
|
thiscam->pan = pan;
|
|
thiscam->pitch = pitch;
|
|
|
|
// compute aming to look the viewed point
|
|
f1 = viewpointx-thiscam->x;
|
|
f2 = viewpointy-thiscam->y;
|
|
dist = FixedHypot(f1, f2);
|
|
|
|
if (mo->eflags & MFE_VERTICALFLIP)
|
|
{
|
|
angle = R_PointToAngle2(0, thiscam->z + thiscam->height, dist, mo->z + mo->height - player->mo->height + player->cameraOffset);
|
|
if (thiscam->pitch < ANGLE_180 && thiscam->pitch > angle)
|
|
angle += (thiscam->pitch - angle)/2;
|
|
}
|
|
else
|
|
{
|
|
angle = R_PointToAngle2(0, thiscam->z, dist, mo->z + player->mo->height + player->cameraOffset);
|
|
if (thiscam->pitch >= ANGLE_180 && thiscam->pitch < angle)
|
|
angle -= (angle - thiscam->pitch)/2;
|
|
}
|
|
|
|
if (player->playerstate != PST_DEAD)
|
|
angle += (focusaiming < ANGLE_180 ? focusaiming/2 : InvAngle(InvAngle(focusaiming)/2)); // overcomplicated version of '((signed)focusaiming)/2;'
|
|
|
|
if (!camstill && !timeover) // Keep the view still...
|
|
{
|
|
G_ClipAimingPitch((INT32 *)&angle);
|
|
|
|
if (camspeed == FRACUNIT)
|
|
thiscam->aiming = angle;
|
|
else
|
|
{
|
|
angle_t input;
|
|
boolean invert;
|
|
|
|
input = thiscam->aiming - angle;
|
|
invert = (input > ANGLE_180);
|
|
if (invert)
|
|
input = InvAngle(input);
|
|
|
|
input = FixedAngle(FixedMul(AngleFixed(input), (5*camspeed)/16));
|
|
if (invert)
|
|
input = InvAngle(input);
|
|
|
|
thiscam->aiming -= input;
|
|
}
|
|
}
|
|
|
|
if (!resetcalled && (player->playerstate == PST_DEAD || player->playerstate == PST_REBORN))
|
|
{
|
|
// Don't let the camera match your movement.
|
|
thiscam->momz = 0;
|
|
if (player->spectator)
|
|
thiscam->aiming = 0;
|
|
// Only let the camera go a little bit downwards.
|
|
else if (!(mo->eflags & MFE_VERTICALFLIP) && thiscam->aiming < ANGLE_337h && thiscam->aiming > ANGLE_180)
|
|
thiscam->aiming = ANGLE_337h;
|
|
else if (mo->eflags & MFE_VERTICALFLIP && thiscam->aiming > ANGLE_22h && thiscam->aiming < ANGLE_180)
|
|
thiscam->aiming = ANGLE_22h;
|
|
}
|
|
|
|
if (lookbackdown)
|
|
{
|
|
P_MoveChaseCamera(player, thiscam, false);
|
|
R_ResetViewInterpolation(num + 1);
|
|
}
|
|
|
|
thiscam->centerfloorz = mo->floorz;
|
|
thiscam->centerceilingz = mo->ceilingz;
|
|
|
|
return (x == thiscam->x && y == thiscam->y && z == thiscam->z && angle == thiscam->aiming);
|
|
|
|
}
|
|
|
|
boolean P_SpectatorJoinGame(player_t *player)
|
|
{
|
|
const char *text = NULL;
|
|
|
|
// no conditions that could cause the gamejoin to fail below this line
|
|
if (player->mo)
|
|
{
|
|
P_RemoveMobj(player->mo);
|
|
P_SetTarget(&player->mo, NULL);
|
|
}
|
|
player->spectator = false;
|
|
player->pflags &= ~PF_WANTSTOJOIN;
|
|
player->team = TEAM_UNASSIGNED; // We will auto-assign later.
|
|
player->playerstate = PST_REBORN;
|
|
player->enteredGame = true;
|
|
|
|
// Reset away view (some code referenced from Got_Teamchange)
|
|
if (G_IsPartyLocal(player - players))
|
|
{
|
|
LUA_HookViewpointSwitch(player, player, true);
|
|
displayplayers[G_PartyPosition(player - players)] = (player-players);
|
|
}
|
|
|
|
// a surprise tool that will help us later...
|
|
text = va("\x82*%s entered the game.", player_names[player-players]);
|
|
|
|
if (P_IsMachineLocalPlayer(player) && player->spectatewait > TICRATE)
|
|
S_StartSound(NULL, sfx_s3ka9);
|
|
player->spectatewait = 0;
|
|
|
|
HU_AddChatText(text, false);
|
|
return true; // no more player->mo, cannot continue.
|
|
}
|
|
|
|
// the below is first person only, if you're curious. check out P_CalcChasePostImg in p_mobj.c for chasecam
|
|
static void P_CalcPostImg(player_t *player, size_t viewnum)
|
|
{
|
|
sector_t *sector = player->mo->subsector->sector;
|
|
postimg_t *type = &postimgtype[viewnum];
|
|
INT32 *param = &postimgparam[viewnum];
|
|
fixed_t pviewheight;
|
|
size_t i;
|
|
|
|
if (player->mo->eflags & MFE_VERTICALFLIP)
|
|
pviewheight = player->mo->z + player->mo->height - player->viewheight;
|
|
else
|
|
pviewheight = player->mo->z + player->viewheight;
|
|
|
|
if (player->awayview.tics && player->awayview.mobj && !P_MobjWasRemoved(player->awayview.mobj))
|
|
{
|
|
sector = player->awayview.mobj->subsector->sector;
|
|
pviewheight = player->awayview.mobj->z;
|
|
}
|
|
|
|
// see if we are in heat (no, not THAT kind of heat...)
|
|
for (i = 0; i < sector->tags.count; i++)
|
|
{
|
|
if (Tag_FindLineSpecial(13, sector->tags.tags[i]) != -1)
|
|
{
|
|
*type = postimg_heat;
|
|
break;
|
|
}
|
|
else if (sector->ffloors)
|
|
{
|
|
ffloor_t *rover;
|
|
fixed_t topheight;
|
|
fixed_t bottomheight;
|
|
boolean gotres = false;
|
|
|
|
for (rover = sector->ffloors; rover; rover = rover->next)
|
|
{
|
|
size_t j;
|
|
|
|
if (!(rover->fofflags & FOF_EXISTS))
|
|
continue;
|
|
|
|
topheight = P_GetFFloorTopZAt (rover, player->mo->x, player->mo->y);
|
|
bottomheight = P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y);
|
|
|
|
if (pviewheight >= topheight || pviewheight <= bottomheight)
|
|
continue;
|
|
|
|
for (j = 0; j < rover->master->frontsector->tags.count; j++)
|
|
{
|
|
if (Tag_FindLineSpecial(13, rover->master->frontsector->tags.tags[j]) != -1)
|
|
{
|
|
*type = postimg_heat;
|
|
gotres = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (gotres)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// see if we are in water (water trumps heat)
|
|
if (sector->ffloors)
|
|
{
|
|
ffloor_t *rover;
|
|
fixed_t topheight;
|
|
fixed_t bottomheight;
|
|
|
|
for (rover = sector->ffloors; rover; rover = rover->next)
|
|
{
|
|
if (!(rover->fofflags & FOF_EXISTS) || !(rover->fofflags & FOF_SWIMMABLE) || rover->fofflags & FOF_BLOCKPLAYER)
|
|
continue;
|
|
|
|
topheight = P_GetFFloorTopZAt (rover, player->mo->x, player->mo->y);
|
|
bottomheight = P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y);
|
|
|
|
if (pviewheight >= topheight || pviewheight <= bottomheight)
|
|
continue;
|
|
|
|
*type = postimg_water;
|
|
}
|
|
}
|
|
|
|
if (player->mo->eflags & MFE_VERTICALFLIP)
|
|
*type = postimg_flip;
|
|
|
|
#if 1
|
|
(void)param;
|
|
#else
|
|
// Motion blur
|
|
if (player->speed > (35<<FRACBITS))
|
|
{
|
|
*type = postimg_motion;
|
|
*param = (player->speed - 32)/4;
|
|
|
|
if (*param > 5)
|
|
*param = 5;
|
|
}
|
|
#endif
|
|
|
|
if (encoremode) // srb2kart
|
|
*type = postimg_mirror;
|
|
}
|
|
|
|
void P_DoTimeOver(player_t *player)
|
|
{
|
|
if (player->pflags & PF_NOCONTEST)
|
|
{
|
|
// NO! Don't do this!
|
|
return;
|
|
}
|
|
|
|
if (P_IsPartyPlayer(player) && !demo.playback)
|
|
{
|
|
legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p
|
|
player->roundconditions.checkthisframe = true;
|
|
gamedata->deferredconditioncheck = true;
|
|
}
|
|
|
|
if (netgame && !player->bot && !(gametyperules & GTR_BOSS))
|
|
{
|
|
CON_LogMessage(va(M_GetText("%s ran out of time.\n"), player_names[player-players]));
|
|
}
|
|
|
|
// actually, lets not do the below, because its a suitable penalty to not be granted the increase from remaining gradingpoints
|
|
|
|
// iterate through remaining gradingpoints and update gradingfactor and exp for current position, as if you crossed all of them
|
|
// const UINT32 numgradingpoints = K_GetNumGradingPoints();
|
|
// const UINT32 remaininggradingpoints = numgradingpoints - player->gradingpointnum;
|
|
// for (UINT32 i = 0; i < remaininggradingpoints; i++)
|
|
// {
|
|
// player->gradingfactor += K_GetGradingFactorAdjustment(player, player->gradingpointnum);
|
|
// player->gradingpointnum++;
|
|
// player->exp = K_GetEXP(player);
|
|
// }
|
|
|
|
player->pflags |= PF_NOCONTEST;
|
|
K_UpdatePowerLevelsFinalize(player, false);
|
|
|
|
if (G_GametypeUsesLives())
|
|
{
|
|
K_PlayerLoseLife(player);
|
|
}
|
|
|
|
if (player->mo)
|
|
{
|
|
S_StopSound(player->mo);
|
|
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_TIMEOVER);
|
|
}
|
|
|
|
if (G_IsPartyLocal(player - players) && musiccountdown == 0)
|
|
{
|
|
Music_Play("finish_silence");
|
|
musiccountdown = MUSIC_COUNTDOWN_MAX;
|
|
}
|
|
|
|
if (!exitcountdown)
|
|
{
|
|
G_BeginLevelExit();
|
|
}
|
|
|
|
K_InitPlayerTally(player);
|
|
}
|
|
|
|
// SRB2Kart: These are useful functions, but we aren't using them yet.
|
|
#if 0
|
|
|
|
// Get an axis of a certain ID number
|
|
static mobj_t *P_GetAxis(INT32 num)
|
|
{
|
|
thinker_t *th;
|
|
mobj_t *mobj;
|
|
|
|
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
|
|
{
|
|
if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
|
|
continue;
|
|
|
|
mobj = (mobj_t *)th;
|
|
|
|
// NiGHTS axes spawn before anything else. If this mobj doesn't have MF2_AXIS, it means we reached the axes' end.
|
|
if (!(mobj->flags2 & MF2_AXIS))
|
|
break;
|
|
|
|
// Skip if this axis isn't the one we want.
|
|
if (mobj->health != num)
|
|
continue;
|
|
|
|
return mobj;
|
|
}
|
|
|
|
CONS_Alert(CONS_WARNING, "P_GetAxis: Track segment %d is missing!\n", num);
|
|
return NULL;
|
|
}
|
|
|
|
// Auxiliary function. For a given position and axis, it calculates the nearest "valid" snap-on position.
|
|
static void P_GetAxisPosition(fixed_t x, fixed_t y, mobj_t *amo, fixed_t *newx, fixed_t *newy, angle_t *targetangle, angle_t *grind)
|
|
{
|
|
fixed_t ax = amo->x;
|
|
fixed_t ay = amo->y;
|
|
angle_t ang;
|
|
angle_t gr = 0;
|
|
|
|
if (amo->type == MT_AXISTRANSFERLINE)
|
|
{
|
|
ang = amo->angle;
|
|
// Extra security for cardinal directions.
|
|
if (ang == ANGLE_90 || ang == ANGLE_270) // Vertical lines
|
|
x = ax;
|
|
else if (ang == 0 || ang == ANGLE_180) // Horizontal lines
|
|
y = ay;
|
|
else // Diagonal lines
|
|
{
|
|
fixed_t distance = R_PointToDist2(ax, ay, x, y);
|
|
angle_t fad = ((R_PointToAngle2(ax, ay, x, y) - ang) >> ANGLETOFINESHIFT) & FINEMASK;
|
|
fixed_t cosine = FINECOSINE(fad);
|
|
angle_t fa = (ang >> ANGLETOFINESHIFT) & FINEMASK;
|
|
distance = FixedMul(distance, cosine);
|
|
x = ax + FixedMul(distance, FINECOSINE(fa));
|
|
y = ay + FixedMul(distance, FINESINE(fa));
|
|
}
|
|
}
|
|
else // Keep minecart to circle
|
|
{
|
|
fixed_t rad = amo->radius;
|
|
fixed_t distfactor = FixedDiv(rad, R_PointToDist2(ax, ay, x, y));
|
|
|
|
gr = R_PointToAngle2(ax, ay, x, y);
|
|
ang = gr + ANGLE_90;
|
|
x = ax + FixedMul(x - ax, distfactor);
|
|
y = ay + FixedMul(y - ay, distfactor);
|
|
}
|
|
|
|
*newx = x;
|
|
*newy = y;
|
|
*targetangle = ang;
|
|
*grind = gr;
|
|
}
|
|
|
|
static void P_ParabolicMove(mobj_t *mo, fixed_t x, fixed_t y, fixed_t z, fixed_t g, fixed_t speed)
|
|
{
|
|
fixed_t dx = x - mo->x;
|
|
fixed_t dy = y - mo->y;
|
|
fixed_t dz = z - mo->z;
|
|
fixed_t dh = P_AproxDistance(dx, dy);
|
|
fixed_t c = FixedDiv(dx, dh);
|
|
fixed_t s = FixedDiv(dy, dh);
|
|
fixed_t fixConst = FixedDiv(speed, g);
|
|
|
|
mo->momx = FixedMul(c, speed);
|
|
mo->momy = FixedMul(s, speed);
|
|
mo->momz = FixedDiv(dh, 2*fixConst) + FixedDiv(dz, FixedDiv(dh, fixConst/2));
|
|
}
|
|
|
|
#endif
|
|
|
|
/* gaysed script from me, based on Golden's sprite slope roll */
|
|
|
|
// holy SHIT
|
|
static INT32
|
|
Quaketilt (player_t *player)
|
|
{
|
|
angle_t tilt;
|
|
fixed_t lowb; // this threshold for speed
|
|
angle_t moma = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
|
|
INT32 delta = (INT32)( player->mo->angle - moma );
|
|
fixed_t speed;
|
|
|
|
boolean sliptiding = K_Sliptiding(player);
|
|
|
|
if (delta == (INT32)ANGLE_180)/* FUCK YOU HAVE A HACK */
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Hi! I'm "not a math guy"!
|
|
if (abs(delta) > ANGLE_90)
|
|
delta = (INT32)(( moma + ANGLE_180 ) - player->mo->angle );
|
|
if (P_IsObjectOnGround(player->mo))
|
|
{
|
|
if (sliptiding)
|
|
{
|
|
tilt = ANGLE_45;
|
|
lowb = 20*FRACUNIT;
|
|
}
|
|
else
|
|
{
|
|
tilt = ANGLE_11hh/2;
|
|
lowb = 60*FRACUNIT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tilt = ANGLE_22h;
|
|
lowb = 40*FRACUNIT;
|
|
}
|
|
lowb = FixedMul(lowb, player->mo->scale);
|
|
moma = FixedMul(FixedDiv(delta, ANGLE_90), tilt);
|
|
speed = player->speed;
|
|
if (speed < lowb)
|
|
{
|
|
// ease out tilt as we slow...
|
|
moma = FixedMul(moma, FixedDiv(speed, lowb));
|
|
}
|
|
return moma;
|
|
}
|
|
|
|
static void
|
|
DoABarrelRoll (player_t *player)
|
|
{
|
|
UINT8 viewnum = R_GetViewNumber();
|
|
camera_t *cam = &camera[viewnum];
|
|
|
|
angle_t slope;
|
|
angle_t delta;
|
|
|
|
fixed_t smoothing;
|
|
|
|
if (player->exiting || F_CreditsDemoExitFade() >= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (player->loop.radius)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (player->respawn.state != RESPAWNST_NONE)
|
|
{
|
|
player->tilt = 0;
|
|
return;
|
|
}
|
|
|
|
slope = InvAngle(R_GetPitchRollAngle(player->mo, player));
|
|
|
|
if (AbsAngle(slope) < ANGLE_11hh)
|
|
{
|
|
slope = 0;
|
|
}
|
|
|
|
if (cam->chase)
|
|
{
|
|
if (AbsAngle(slope) > ANGLE_45)
|
|
{
|
|
slope = slope & ANGLE_180 ? InvAngle(ANGLE_45) : ANGLE_45;
|
|
}
|
|
} else {
|
|
if (AbsAngle(slope) > ANGLE_90)
|
|
{
|
|
slope = slope & ANGLE_180 ? InvAngle(ANGLE_90) : ANGLE_90;
|
|
}
|
|
}
|
|
|
|
slope -= Quaketilt(player);
|
|
|
|
delta = slope - player->tilt;
|
|
smoothing = FixedDiv(AbsAngle(slope), ANGLE_45);
|
|
|
|
delta = FixedDiv(delta, (cam->chase ? 33 : 11) *
|
|
FixedDiv(FRACUNIT, FRACUNIT + smoothing));
|
|
|
|
if (delta)
|
|
player->tilt += delta;
|
|
else
|
|
player->tilt = slope;
|
|
}
|
|
|
|
void P_TickAltView(altview_t *view)
|
|
{
|
|
if (view->mobj != NULL && P_MobjWasRemoved(view->mobj) == true)
|
|
{
|
|
P_SetTarget(&view->mobj, NULL); // remove view->mobj asap if invalid
|
|
view->tics = 0; // reset to zero
|
|
}
|
|
|
|
if (view->tics > 0)
|
|
{
|
|
view->tics--;
|
|
|
|
if (view->tics == 0)
|
|
{
|
|
P_SetTarget(&view->mobj, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// P_PlayerThink
|
|
//
|
|
|
|
void P_PlayerThink(player_t *player)
|
|
{
|
|
ticcmd_t *cmd;
|
|
const size_t playeri = (size_t)(player - players);
|
|
|
|
#ifdef PARANOIA
|
|
if (!player->mo)
|
|
I_Error("p_playerthink: players[%s].mo == NULL", sizeu1(playeri));
|
|
#endif
|
|
|
|
// todo: Figure out what is actually causing these problems in the first place...
|
|
if (player->mo->health <= 0 && player->playerstate == PST_LIVE) //you should be DEAD!
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "P_PlayerThink: Player %s in PST_LIVE with 0 health. (\"Zombie bug\")\n", sizeu1(playeri));
|
|
player->playerstate = PST_DEAD;
|
|
}
|
|
|
|
if (G_GametypeUsesLives() == true && player->lives <= 0 && player->playerstate != PST_DEAD)
|
|
{
|
|
player->mo->flags &= ~(MF_SOLID|MF_SHOOTABLE); // does not block
|
|
P_UnsetThingPosition(player->mo);
|
|
player->mo->flags |= MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY;
|
|
P_SetThingPosition(player->mo);
|
|
player->mo->standingslope = NULL;
|
|
player->mo->terrain = NULL;
|
|
player->mo->pmomz = 0;
|
|
|
|
player->playerstate = PST_DEAD;
|
|
|
|
// hide the player sprite forever
|
|
player->mo->hitlag = INT32_MAX;
|
|
player->mo->renderflags |= RF_DONTDRAW;
|
|
player->mo->reappear = INFTICS; // also hides the follower
|
|
|
|
// respawn from where you died
|
|
player->respawn.pointx = player->mo->x;
|
|
player->respawn.pointy = player->mo->y;
|
|
player->respawn.pointz = player->mo->z;
|
|
|
|
player->pflags |= PF_LOSTLIFE|PF_ELIMINATED|PF_NOCONTEST;
|
|
player->realtime = UINT32_MAX;
|
|
K_InitPlayerTally(player);
|
|
}
|
|
|
|
// Erasing invalid player pointers
|
|
{
|
|
#define PlayerPointerErase(field) \
|
|
if (field && P_MobjWasRemoved(field)) \
|
|
P_SetTarget(&field, NULL); \
|
|
|
|
PlayerPointerErase(player->followmobj);
|
|
PlayerPointerErase(player->stumbleIndicator);
|
|
PlayerPointerErase(player->wavedashIndicator);
|
|
PlayerPointerErase(player->trickIndicator);
|
|
PlayerPointerErase(player->whip);
|
|
PlayerPointerErase(player->hand);
|
|
PlayerPointerErase(player->ringShooter);
|
|
PlayerPointerErase(player->hoverhyudoro);
|
|
PlayerPointerErase(player->ballhogreticule);
|
|
PlayerPointerErase(player->flickyAttacker);
|
|
PlayerPointerErase(player->stoneShoe);
|
|
PlayerPointerErase(player->toxomisterCloud);
|
|
PlayerPointerErase(player->powerup.flickyController);
|
|
PlayerPointerErase(player->powerup.barrier);
|
|
|
|
#undef PlayerPointerErase
|
|
}
|
|
|
|
player->old_drawangle = player->drawangle;
|
|
|
|
P_TickAltView(&player->awayview);
|
|
|
|
if (player->flashcount)
|
|
player->flashcount--;
|
|
|
|
// Track airtime
|
|
if (P_IsObjectOnGround(player->mo)
|
|
&& !P_PlayerInPain(player)) // This isn't airtime, but it's control loss all the same.
|
|
{
|
|
player->lastairtime = player->airtime;
|
|
player->airtime = 0;
|
|
}
|
|
else
|
|
{
|
|
player->airtime++;
|
|
}
|
|
|
|
if ((player->pflags & PF_FAULT) || (player->pflags & PF_VOID))
|
|
{
|
|
player->lastairtime = 0;
|
|
player->airtime = 0;
|
|
}
|
|
|
|
cmd = &player->cmd;
|
|
|
|
// SRB2kart
|
|
// Save the dir the player is holding
|
|
// to allow items to be thrown forward or backward.
|
|
{
|
|
const INT16 threshold = 6*KART_FULLTURN/10;
|
|
if (cmd->throwdir > threshold)
|
|
{
|
|
player->throwdir = 1;
|
|
}
|
|
else if (cmd->throwdir < -threshold)
|
|
{
|
|
player->throwdir = -1;
|
|
}
|
|
else
|
|
{
|
|
player->throwdir = 0;
|
|
}
|
|
}
|
|
|
|
// Accessibility - kickstart your acceleration
|
|
if (!(player->pflags & PF_KICKSTARTACCEL))
|
|
{
|
|
player->kickstartaccel = 0;
|
|
}
|
|
else if (cmd->buttons & BT_ACCELERATE)
|
|
{
|
|
if (!player->exiting && !(player->oldcmd.buttons & BT_ACCELERATE) && ((cmd->buttons & BT_SPINDASHMASK) != BT_SPINDASHMASK) && player->trickpanel != TRICKSTATE_READY)
|
|
{
|
|
player->kickstartaccel = 0;
|
|
}
|
|
else if (player->kickstartaccel < ACCEL_KICKSTART)
|
|
{
|
|
player->kickstartaccel++;
|
|
|
|
if ((
|
|
player->kickstartaccel < ACCEL_KICKSTART
|
|
&& player->spindash != 0 // spindashings
|
|
) && (
|
|
player->rings <= 0 // desperation
|
|
|| (G_TimeAttackStart() && leveltime < starttime) // TA
|
|
))
|
|
{
|
|
// Double speed fill
|
|
player->kickstartaccel++;
|
|
}
|
|
|
|
if ((player->kickstartaccel == ACCEL_KICKSTART) && !K_PlayerUsesBotMovement(player) && P_IsDisplayPlayer(player))
|
|
{
|
|
S_StartSound(NULL, sfx_ding);
|
|
}
|
|
}
|
|
else // for HUD
|
|
{
|
|
player->kickstartaccel = ACCEL_KICKSTART+1;
|
|
}
|
|
}
|
|
else if (player->kickstartaccel < ACCEL_KICKSTART)
|
|
{
|
|
player->kickstartaccel = 0;
|
|
}
|
|
else // for HUD
|
|
{
|
|
player->kickstartaccel = ACCEL_KICKSTART+1;
|
|
}
|
|
|
|
#ifdef PARANOIA
|
|
if (player->playerstate == PST_REBORN)
|
|
I_Error("player %s is in PST_REBORN\n", sizeu1(playeri));
|
|
#endif
|
|
|
|
if (!mapreset)
|
|
{
|
|
if (gametyperules & GTR_CIRCUIT)
|
|
{
|
|
#if 0
|
|
// If 10 seconds are left on the timer,
|
|
// begin the drown music for countdown!
|
|
// SRB2Kart: despite how perfect this is, it's disabled FOR A REASON
|
|
if (racecountdown == 11*TICRATE - 1)
|
|
{
|
|
if (P_IsPartyPlayer(player))
|
|
S_ChangeMusicInternal("drown", false);
|
|
}
|
|
#endif
|
|
|
|
// If you've hit the countdown and you haven't made
|
|
// it to the exit, you're a goner!
|
|
if (racecountdown == 1 && !player->spectator && !player->exiting && !(player->pflags & PF_NOCONTEST) && player->lives > 0)
|
|
{
|
|
P_DoTimeOver(player);
|
|
|
|
if (player->playerstate == PST_DEAD)
|
|
{
|
|
LUA_HookPlayer(player, HOOK(PlayerThink));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure spectators always have a score and ring count of 0.
|
|
if (player->spectator)
|
|
{
|
|
//player->score = 0;
|
|
player->rings = 0;
|
|
player->mo->health = 1;
|
|
}
|
|
|
|
// SRB2kart 010217
|
|
if (leveltime < introtime)
|
|
{
|
|
player->nocontrol = 2;
|
|
}
|
|
|
|
// Synchronizes the "real" amount of time spent in the level.
|
|
if (!(player->exiting || mapreset) && !(player->pflags & PF_NOCONTEST) && !stoppedclock)
|
|
{
|
|
if (leveltime >= starttime)
|
|
{
|
|
player->realtime = leveltime - starttime;
|
|
|
|
if (player->spectator)
|
|
player->laptime[LAP_CUR] = 0;
|
|
else if (player->laptime[LAP_CUR] != UINT32_MAX)
|
|
player->laptime[LAP_CUR]++; // This is too complicated to sync to realtime, just sorta hope for the best :V
|
|
}
|
|
else
|
|
{
|
|
player->realtime = 0;
|
|
player->laptime[LAP_CUR] = 0;
|
|
}
|
|
}
|
|
|
|
if (cmd->flags & TICCMD_TYPING)
|
|
{
|
|
/*
|
|
typing_duration is slow to start and slow to stop.
|
|
|
|
typing_timer counts down a grace period before the player is not
|
|
actually considered typing anymore.
|
|
*/
|
|
if (cmd->flags & TICCMD_KEYSTROKE)
|
|
{
|
|
/* speed up if we are typing quickly! */
|
|
if (player->typing_duration > 0 && player->typing_timer > 12)
|
|
{
|
|
if (player->typing_duration < 16)
|
|
{
|
|
player->typing_duration = 24;
|
|
}
|
|
else
|
|
{
|
|
/* slows down a tiny bit as it approaches the next dot */
|
|
const UINT8 step = (((player->typing_duration + 15) & ~15) -
|
|
player->typing_duration) / 2;
|
|
player->typing_duration += max(step, 4);
|
|
}
|
|
}
|
|
|
|
player->typing_timer = 15;
|
|
}
|
|
else if (player->typing_timer > 0)
|
|
{
|
|
player->typing_timer--;
|
|
}
|
|
|
|
/* if we are in the grace period (including currently typing) */
|
|
if (player->typing_timer + player->typing_duration > 0)
|
|
{
|
|
/* always end the cycle on two dots */
|
|
if (player->typing_timer == 0 &&
|
|
(player->typing_duration < 16 || player->typing_duration == 40))
|
|
{
|
|
player->typing_duration = 0;
|
|
}
|
|
else if (player->typing_duration < 63)
|
|
{
|
|
player->typing_duration++;
|
|
}
|
|
else
|
|
{
|
|
player->typing_duration = 16;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
player->typing_timer = 0;
|
|
player->typing_duration = 0;
|
|
}
|
|
|
|
/* ------------------------------------------ /
|
|
ALL ABOVE THIS BLOCK OCCURS EVEN WITH HITLAG
|
|
/ ------------------------------------------ */
|
|
|
|
if (P_MobjIsFrozen(player->mo))
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* ------------------------------------------ /
|
|
ALL BELOW THIS BLOCK IS STOPPED DURING HITLAG
|
|
/ ------------------------------------------ */
|
|
|
|
player->pflags &= ~PF_HITFINISHLINE;
|
|
|
|
// check water content, set stuff in mobj
|
|
P_MobjCheckWater(player->mo);
|
|
|
|
#ifndef SECTORSPECIALSAFTERTHINK
|
|
if (player->onconveyor != 1 || !P_IsObjectOnGround(player->mo))
|
|
player->onconveyor = 0;
|
|
// check special sectors : damage & secrets
|
|
|
|
if (!player->spectator)
|
|
P_PlayerInSpecialSector(player);
|
|
#endif
|
|
|
|
if (player->playerstate == PST_DEAD)
|
|
{
|
|
if (player->spectator)
|
|
player->mo->renderflags |= RF_GHOSTLY;
|
|
else
|
|
player->mo->renderflags &= ~RF_GHOSTLYMASK;
|
|
P_DeathThink(player);
|
|
LUA_HookPlayer(player, HOOK(PlayerThink));
|
|
return;
|
|
}
|
|
|
|
if ((netgame || multiplayer) && player->spectator && !player->bot && cmd->buttons & BT_ATTACK && !player->flashing)
|
|
{
|
|
player->pflags ^= PF_WANTSTOJOIN;
|
|
player->flashing = TICRATE/2 + 1;
|
|
/*if (P_SpectatorJoinGame(player))
|
|
return; // player->mo was removed.*/
|
|
//CONS_Printf("player %s wants to join on tic %d\n", player_names[player-players], leveltime);
|
|
}
|
|
|
|
if (player->respawn.state != RESPAWNST_NONE)
|
|
{
|
|
K_RespawnChecker(player);
|
|
player->rmomx = player->rmomy = 0;
|
|
|
|
player->markedfordeath = false; // In case we got here via a death sector or something.
|
|
|
|
if (player->respawn.state == RESPAWNST_DROP)
|
|
{
|
|
// Allows some turning
|
|
P_MovePlayer(player);
|
|
}
|
|
}
|
|
else if (player->mo->reactiontime)
|
|
{
|
|
// Reactiontime is used to prevent movement
|
|
// for a bit after a teleport.
|
|
player->mo->reactiontime--;
|
|
}
|
|
else if (player->carry == CR_ZOOMTUBE && player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT)
|
|
{
|
|
P_DoZoomTube(player);
|
|
player->rmomx = player->rmomy = 0;
|
|
}
|
|
else if (player->loop.radius != 0)
|
|
{
|
|
P_PlayerOrbit(player);
|
|
player->rmomx = player->mo->momx;
|
|
player->rmomy = player->mo->momy;
|
|
}
|
|
else
|
|
{
|
|
// Move around.
|
|
P_MovePlayer(player);
|
|
}
|
|
|
|
// Unset statis flag after moving.
|
|
// In other words, if you manually set stasis via code,
|
|
// it lasts for one tic.
|
|
player->pflags &= ~PF_STASIS;
|
|
|
|
if (player->onconveyor == 1)
|
|
player->onconveyor = 3;
|
|
else if (player->onconveyor == 3)
|
|
player->cmomy = player->cmomx = 0;
|
|
|
|
P_DoBubbleBreath(player); // Spawn Sonic's bubbles
|
|
P_CheckInvincibilityTimer(player); // Spawn Invincibility Sparkles
|
|
|
|
// Counters, time dependent power ups.
|
|
// Time Bonus & Ring Bonus count settings
|
|
|
|
// Strength counts up to diminish fade.
|
|
if (player->flashing && player->flashing < UINT16_MAX &&
|
|
(player->spectator || !P_PlayerInPain(player)))
|
|
{
|
|
player->flashing--;
|
|
}
|
|
|
|
if (!player->flashing && !P_PlayerInPain(player))
|
|
{
|
|
player->wallSpikeDampen = 0;
|
|
}
|
|
|
|
if (player->nocontrol && player->nocontrol < UINT16_MAX)
|
|
{
|
|
player->nocontrol--;
|
|
}
|
|
|
|
// tic down the var normaly and remove the flag upon respawn so its guaranteed to be removed from the player
|
|
if (!player->nocontrol && !player->respawn.timer && player->respawn.state == RESPAWNST_DROP && (player->pflags & PF_FAULT))
|
|
{
|
|
player->pflags &= ~PF_FAULT;
|
|
player->mo->renderflags &= ~RF_DONTDRAW;
|
|
player->mo->flags &= ~MF_NOCLIPTHING;
|
|
}
|
|
|
|
boolean deathcontrolled = (player->respawn.state != RESPAWNST_NONE && player->respawn.truedeath == true)
|
|
|| (player->pflags & PF_NOCONTEST) || (player->karmadelay);
|
|
boolean powercontrolled = (player->hyudorotimer) || (player->growshrinktimer > 0);
|
|
|
|
// Flash player after being hit.
|
|
if (!deathcontrolled && !powercontrolled)
|
|
{
|
|
if (player->flashing > 1 && player->flashing < K_GetKartFlashing(player)
|
|
&& (leveltime & 1))
|
|
player->mo->renderflags |= RF_DONTDRAW;
|
|
else
|
|
player->mo->renderflags &= ~RF_DONTDRAW;
|
|
}
|
|
else if (!deathcontrolled)
|
|
{
|
|
player->mo->renderflags &= ~RF_DONTDRAW;
|
|
}
|
|
|
|
if (player->stairjank > 0)
|
|
{
|
|
player->stairjank--;
|
|
}
|
|
|
|
// Random skin / "ironman"
|
|
{
|
|
UINT32 skinflags = (demo.playback)
|
|
? demo.skinlist[demo.currentskinid[playeri]].flags
|
|
: skins[player->skin]->flags;
|
|
|
|
if (skinflags & SF_IRONMAN) // we are Heavy Magician
|
|
{
|
|
if (player->charflags & SF_IRONMAN) // no fakeskin yet
|
|
{
|
|
if (leveltime >= starttime
|
|
&& !player->exiting
|
|
&& player->mo->health > 0
|
|
&& (player->respawn.state == RESPAWNST_NONE
|
|
|| (player->respawn.state == RESPAWNST_DROP && !player->respawn.timer))
|
|
&& !P_PlayerInPain(player))
|
|
{
|
|
if (player->fakeskin != MAXSKINS)
|
|
{
|
|
SetFakePlayerSkin(player, player->fakeskin);
|
|
if (player->spectator == false)
|
|
{
|
|
S_StartSound(player->mo, sfx_kc33);
|
|
K_SpawnMagicianParticles(player->mo, 5);
|
|
}
|
|
}
|
|
else if (!(gametyperules & GTR_CIRCUIT))
|
|
{
|
|
SetRandomFakePlayerSkin(player, false, false);
|
|
}
|
|
}
|
|
}
|
|
else if (player->exiting) // wearing a fakeskin, but need to display signpost postrace etc
|
|
{
|
|
ClearFakePlayerSkin(player);
|
|
}
|
|
}
|
|
}
|
|
|
|
K_KartPlayerThink(player, cmd); // SRB2kart
|
|
|
|
DoABarrelRoll(player);
|
|
|
|
if (player->carry == CR_SLIDING)
|
|
player->carry = CR_NONE;
|
|
|
|
LUA_HookPlayer(player, HOOK(PlayerThink));
|
|
}
|
|
|
|
//
|
|
// P_PlayerAfterThink
|
|
//
|
|
// Thinker for player after all other thinkers have run
|
|
//
|
|
void P_PlayerAfterThink(player_t *player)
|
|
{
|
|
UINT8 i;
|
|
|
|
#ifdef PARANOIA
|
|
if (!player->mo)
|
|
{
|
|
const size_t playeri = (size_t)(player - players);
|
|
I_Error("P_PlayerAfterThink: players[%s].mo == NULL", sizeu1(playeri));
|
|
}
|
|
#endif
|
|
|
|
if (player->exiting && !K_PlayerTallyActive(player))
|
|
{
|
|
// We defer P_DoPlayerExit tallies to the end of the tic.
|
|
K_InitPlayerTally(player);
|
|
}
|
|
|
|
if (P_IsObjectOnGround(player->mo) == true)
|
|
{
|
|
player->outrun = 0;
|
|
}
|
|
|
|
#ifdef SECTORSPECIALSAFTERTHINK
|
|
if (player->onconveyor != 1 || !P_IsObjectOnGround(player->mo))
|
|
player->onconveyor = 0;
|
|
// check special sectors : damage & secrets
|
|
|
|
if (!player->spectator)
|
|
P_PlayerInSpecialSector(player);
|
|
#endif
|
|
|
|
if (player->playerstate == PST_DEAD)
|
|
{
|
|
// Followers need handled while dead.
|
|
K_HandleFollower(player);
|
|
|
|
if (player->followmobj)
|
|
{
|
|
P_RemoveMobj(player->followmobj);
|
|
P_SetTarget(&player->followmobj, NULL);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
{
|
|
boolean chase = true;
|
|
|
|
for (i = 0; i <= r_splitscreen; i++)
|
|
{
|
|
if (player == &players[displayplayers[i]] && !camera[i].chase)
|
|
{
|
|
chase = false;
|
|
}
|
|
}
|
|
|
|
if (!chase) // bob view only if looking through the player's eyes
|
|
{
|
|
P_CalcHeight(player);
|
|
}
|
|
else
|
|
{
|
|
// defaults to make sure 1st person cam doesn't do anything weird on startup
|
|
player->deltaviewheight = 0;
|
|
player->viewheight = P_GetPlayerViewHeight(player);
|
|
|
|
if (player->mo->eflags & MFE_VERTICALFLIP)
|
|
player->viewz = player->mo->z + player->mo->height - player->viewheight;
|
|
else
|
|
player->viewz = player->mo->z + player->viewheight;
|
|
}
|
|
|
|
for (i = 0; i <= r_splitscreen; i++)
|
|
{
|
|
if (player == &players[displayplayers[i]] && !camera[i].chase)
|
|
{
|
|
P_CalcPostImg(player, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// spectator invisibility and nogravity.
|
|
if ((netgame || multiplayer) && player->spectator)
|
|
{
|
|
player->mo->renderflags |= RF_DONTDRAW;
|
|
player->mo->flags |= MF_NOGRAVITY;
|
|
}
|
|
|
|
K_KartPlayerAfterThink(player);
|
|
|
|
if (player->followmobj && (player->spectator || player->mo->health <= 0 || player->followmobj->type != player->followitem))
|
|
{
|
|
P_RemoveMobj(player->followmobj);
|
|
P_SetTarget(&player->followmobj, NULL);
|
|
}
|
|
|
|
if (!player->spectator && player->mo->health && player->followitem)
|
|
{
|
|
if (!player->followmobj || P_MobjWasRemoved(player->followmobj))
|
|
{
|
|
P_SetTarget(&player->followmobj, P_SpawnMobjFromMobj(player->mo, 0, 0, 0, player->followitem));
|
|
P_SetTarget(&player->followmobj->tracer, player->mo);
|
|
switch (player->followmobj->type)
|
|
{
|
|
default:
|
|
player->followmobj->flags2 |= MF2_LINKDRAW;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (player->followmobj)
|
|
{
|
|
if (LUA_HookFollowMobj(player, player->followmobj) || P_MobjWasRemoved(player->followmobj))
|
|
{;}
|
|
else
|
|
{
|
|
switch (player->followmobj->type)
|
|
{
|
|
default:
|
|
var1 = 1;
|
|
var2 = 0;
|
|
A_CapeChase(player->followmobj);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run followers in AfterThink, after the players have moved,
|
|
// so a lag value of 1 is exactly attached to the player.
|
|
K_HandleFollower(player);
|
|
|
|
if (P_MobjWasRemoved(player->mo) || (player->mo->eflags & MFE_PAUSED) == 0)
|
|
{
|
|
player->timeshitprev = player->timeshit;
|
|
player->timeshit = 0;
|
|
}
|
|
|
|
if (K_PlayerUsesBotMovement(player))
|
|
{
|
|
K_UpdateBotGameplayVars(player);
|
|
}
|
|
|
|
for (i = 0; i <= r_splitscreen; i++)
|
|
{
|
|
if (player != &players[displayplayers[i]])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Store before it gets 0'd out
|
|
camera[i].pmomz = player->mo->pmomz;
|
|
}
|
|
|
|
if (P_IsObjectOnGround(player->mo))
|
|
player->mo->pmomz = 0;
|
|
}
|
|
|
|
void P_IncrementGriefValue(player_t *player, UINT32 *grief, const UINT32 griefMax)
|
|
{
|
|
const fixed_t requireDist = (12*player->mo->scale) / FRACUNIT;
|
|
INT32 progress = player->distancetofinishprev - player->distancetofinish;
|
|
boolean exceptions = (
|
|
player->flashing != 0
|
|
|| P_MobjIsFrozen(player->mo)
|
|
|| player->airtime > 3*TICRATE/2
|
|
|| (player->justbumped > 0 && player->justbumped < bumptime-1)
|
|
);
|
|
|
|
if (!exceptions && (progress < requireDist))
|
|
{
|
|
// If antigrief is disabled, we don't want the
|
|
// player getting into a hole so deep no amount
|
|
// of good behaviour could ever make up for it.
|
|
if (*grief < griefMax)
|
|
{
|
|
// Making no progress, start counting against you.
|
|
*grief = *grief + 1;
|
|
if (progress < -requireDist && *grief < griefMax)
|
|
{
|
|
// Making NEGATIVE progress? Start counting even harder.
|
|
*grief = *grief + 1;
|
|
}
|
|
}
|
|
}
|
|
else if (*grief > 0)
|
|
{
|
|
// Playing normally.
|
|
*grief = *grief - 1;
|
|
|
|
if (*grief == 0)
|
|
player->griefWarned = false;
|
|
}
|
|
}
|
|
|
|
void P_CheckRaceGriefing(player_t *player, boolean dopunishment)
|
|
{
|
|
const UINT32 griefMax = cv_antigrief.value * TICRATE;
|
|
const UINT8 n = player - players;
|
|
|
|
// Don't punish if the cvar is turned off,
|
|
// otherwise NOBODY would be able to play!
|
|
if (griefMax == 0)
|
|
{
|
|
dopunishment = false;
|
|
}
|
|
|
|
P_IncrementGriefValue(player, &player->griefValue, griefMax);
|
|
|
|
if (dopunishment && !player->griefWarned && player->griefValue >= (griefMax/2))
|
|
{
|
|
K_AddMessageForPlayer(player, "Get moving!", true, false);
|
|
if (P_IsPartyPlayer(player))
|
|
S_StartSound(NULL, sfx_cftbl0);
|
|
player->griefWarned = true;
|
|
}
|
|
|
|
if (dopunishment && player->griefValue >= griefMax)
|
|
{
|
|
if (player->griefStrikes < 3)
|
|
{
|
|
player->griefStrikes++;
|
|
}
|
|
|
|
player->griefValue = 0;
|
|
player->griefWarned = false;
|
|
|
|
if (server)
|
|
{
|
|
if (player->griefStrikes == 3 && playernode[n] != servernode
|
|
#ifndef DEVELOP
|
|
&& !IsPlayerAdmin(n)
|
|
#endif
|
|
)
|
|
{
|
|
// Send kick
|
|
SendKick(n, KICK_MSG_GRIEF);
|
|
}
|
|
else
|
|
{
|
|
// Send spectate
|
|
UINT8 buf[2];
|
|
UINT8 *p = buf;
|
|
|
|
WRITEUINT8(p, n);
|
|
WRITEUINT8(p, 0);
|
|
|
|
SendNetXCmd(XD_SPECTATE, &buf, p - buf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void P_SetPlayerAngle(player_t *player, angle_t angle)
|
|
{
|
|
P_ForceLocalAngle(player, angle);
|
|
player->angleturn = angle;
|
|
}
|
|
|
|
void P_ForceLocalAngle(player_t *player, angle_t angle)
|
|
{
|
|
UINT8 i;
|
|
|
|
angle = angle & ~UINT16_MAX;
|
|
|
|
for (i = 0; i <= r_splitscreen; i++)
|
|
{
|
|
if (player == &players[displayplayers[i]])
|
|
{
|
|
localangle[i] = angle;
|
|
}
|
|
}
|
|
|
|
// jartha: OK, I don't really know how ticcmds work. The
|
|
// specific problem I'm trying to fix is that, on level
|
|
// load, the player angle gets reset. But the ticcmds
|
|
// copied in afterward don't match this angle, and they
|
|
// influence the player steering.
|
|
for (i = 0; i <= splitscreen; i++)
|
|
{
|
|
if (player == &players[g_localplayers[i]])
|
|
{
|
|
D_ResetTiccmdAngle(i, angle);
|
|
localsteering[i] = angle;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
boolean P_PlayerFullbright(player_t *player)
|
|
{
|
|
return (player->invincibilitytimer > 0);
|
|
}
|
|
|
|
void P_ResetPlayerCheats(void)
|
|
{
|
|
INT32 i;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
player_t *player = &players[i];
|
|
mobj_t *thing = player->mo;
|
|
|
|
if (!playeringame[i])
|
|
continue;
|
|
|
|
player->pflags &= ~(PF_GODMODE);
|
|
player->respawn.manual = false;
|
|
|
|
if (P_MobjWasRemoved(thing))
|
|
continue;
|
|
|
|
thing->flags &= ~(MF_NOCLIP);
|
|
|
|
thing->destscale = mapobjectscale;
|
|
P_SetScale(thing, thing->destscale);
|
|
}
|
|
}
|