RingRacers/src/p_user.c
Eidolon f7ec2f182f Uncapped (squashed v2)
Squashed SRB2 2.2 Public uncapped without UDMF merge

Implement interpolation at the renderer level

Instead of interpolating thinkers, we interpolate mobjs inside the
renderer. Further interpolation is TBI.

Place Frame Interpolation in "Experimental" video options header

This seems like an appropriate way to describe the feature for now.

Add smooth level platter under interpolation, `renderdeltatics`

`renderdeltatics` can be used as a standard delta time in any place,
allowing for smooth menus. It will always be equal to `realtics`
when frame interpolation is turned off, producing consistent
framerate behavior everywhere it is used.

Add smooth rendering to save select screen

Add smooth rendering to Record/NiGHTS Attack, F_SkyScroll

Ensure viewsector is accurate to viewx/viewy

This fixes a potential crash in OpenGL when changing between levels.

Ensure + commands get executed before map start

Always have precise_t defined

Fix misc dropshadow issues

Reset view interpolation on level load

Remove unnecessary precipmobj thinker hack

Add reset interpolation state functions

Reset precip interpolation on snap to ceil

Reset mobj interp state on TeleportMove

Only swap view interp state if a tick is run

Run anti-lag chasecam at tic frequency

Fixes jittery and unstable chasecam in high latency netgames

Homogenize mobj interpolations

Add sector plane level interpolations

Add SectorScroll interpolator

Add SideScroll interpolator

Add Polyobj interpolator

Intialize interpolator list at a better time

Delete interpolators associated with thinkers

Interpolate mobj angles and player drawangle

Interpolate HWR_DrawModel

Add functions to handle interpolation

Much less code duplication

P_InitAngle, to fix angle interpolation on spawning objects

Fully fix drop shadows

It used the thing's floorz / ceilingz directly -- that wouldn't account for interpolated coordinates.

Do not speed up underwater/heatwave effect in OpenGL

Closer OpenGL underwater/heatwave effect to Software

Interpolate from time of previous tic

Previously interpolated from last 35th of a second, which
may be offset from game time due to connection lag.

Consider this the proper fix to 6ecac4159a too.

Calculate FPS stuff even if frame is skipped

I decided ultimately to actually keep the frame skip optimization disabled, because I think it is actually a little bit helpful that you can still get accurate rendering perfstats while paused, however if we decide otherwise then we can have this optimization back without making the game act like it's lagging.

Keep rect in memory

Feel better about this than creating one all da time

Lots of FPS stuff

- Disabled VSync, due to the numerous problems it has.
- Instead, added an FPS cap.
- Frame interpolation is now tied to fpscap != 35.
- By default, the FPS cap is set to the monitor's refresh rate.
- Rewrote the FPS counter.

(This also consolidates several more commits ahead of this
fixing various issues. -eid)

Misc changes after Kart cherry-picks

Fix renderdeltatics with new timing data

Update mobj oldstates before all thinkers

Allow FPS cap values

Adjust how FPS cap is checked to improve FPS stability

Fix precip crash from missing vars

Improve the framerate limiter's timing for extreme stable FPS

Handle the sleep at the end of D_SRB2Loop instead of the start

Simplifies logic in the other parts of the loop, and fixes problems with it frequently waiting too long.

Reset mobj interp state on add

Add mobj interpolator on load netgame

Move mobj interpolators to r_fps

Dynamic slope interpolators

I_GetFrameTime to try and improve frame pace

(It doesn't feel that much better though.)

Move I_FinishUpdate to D_SRB2Loop to sync screen updates with FPS cap, use timestamps in I_FrameCapSleep to simplify the code

Fix plane interpolation light level flickering

Fix flickering plane interpolation for OpenGL in the exact same way

Funny OpenGL renderer being at least 50% copy-pasted Software code :)

P_SetOrigin & P_MoveOrigin to replace P_TeleportMove

Convert P_TeleportMove use to origin funcs

Revert "P_InitAngle, to fix angle interpolation on spawning objects"

This reverts commit a80c98bd164a2748cbbfad9027b34601185d93f5.

Waypoint polyobjects interpolate z & children

Add interpolation to more moving plane types

Adds interpolation to the following:
- Crumbling platforms
- Mario blocks
- Floatbob platforms (this one works really strangely due to two thinkers, maybe double-check this one?)

Reset overlays interp states each TryRunTics

Interpolate model interpolation (lol)

Use interp tracer pos for GL linkdraw

Papersprite angle interpolation

Makes the ending signpost smooth

Move intermission emerald bounce to ticker

Bring back shadows on polyobjects

Also optimizes the method used so rings can show their shadows too. Using just the subsector is a tad bit imprecise admittedly but any more precise methods get really laggy.

Fix a bunch of ticking in hu_ drawing functions

Revert "Reset overlays interp states each TryRunTics"

This reverts commit a71a216faa20e8751b3bd0157354e8d748940c92.

Move intro ticking out of the drawer

Adjust 1up monitor icon z offsets

Fixes interpolation issues with 1up monitors.

Delta time choose player menu animations

Add drawerlib deltaTime function

Interpolate afterimages further back

Use old sleep in dedicated mode

Clamp cechotimer to 0

Fixes issues with cechos staying on-screen and glitching out
(NiGHTS items for example).

Revert "Remove unnecessary precipmobj thinker hack"

This reverts commit 0e38208620d19ec2ab690740438ac2fc7862a49e.

Fix frame pacing when game lags behind

The frame timestamp should've been made at the start of the frame, not the end.

Fix I_FrameCapSleep not respecting cpusleep

Jonathan Joestar bruh

Allow dedicated to use precise sleep timing again

Instead of only using one old sleep, just enforce framerate cap to match TICRATE.

Make Lua TeleportMove call MoveOrigin

Reset Metal fume interp state on appear

Add interpdebug

Put interpdebug stuff in perfstats instead

Add timescale cvar

Slow the game down to debug animations / interpolation problems! Speed it up if you need to get somewhere quickly while mapping!

Enable timescale outside of DEVELOP builds

It has NETVAR, so it should be fine -- put an end to useful debugging features excluded in multiplayer!

Force interpolation when timescale != 1.0

Reset old_z in MT_LOCKON think

Fixes interpolation artifacting due to spawn pos.

Fix cutscenes in interp

Fix boss1 laser in interp

Interpolate mobj scale

Precalculate refresh rate

Slower PCs can have issue querying mode over and over. This might kinda suck for windowed mode if you have different refresh rate displays but oh well

Fix interp scaling crashing software

Reset interp scale when Lua sets .scale

Disable angle interp on fresh mobjs

Fix interp scale crash for hires sprites

Interp shadow scales

Copy interp state in P_SpawnMobjFromMobj

Fix multiplayer character select

Don't interpolate mobj state if frac = 1.0

Fix Mario block item placement

Interpolate spritescale/offset x/y

Fix offset copies for SpawnMobjFromMobj

THANKS SAL

Add Lua HUD drawlists

Buffers draw calls between tics to ensure hooks
run at the originally intended rate.

Rename drawerlib deltaTime to getDeltaTime

Make renderisnewtic is false between tics

I know what I'm doing! I swear

Completely refactor timing system

Time is now tracked internally in the game using I_GetPreciseTime
and I_UpdateTime. I_Time now pulls from this internal timer. The
system code no longer needs to keep track of time itself.

This significantly improves frame and tic timing in interp mode,
resulting in a much smoother image with essentially no judder at
any framerate.

Ensure mobj interpolators reset on level load

Ensure view is not interpolated on first frame

Disable sprite offset interpolation (for now)

Refactor timing code even more

System layer is greatly simplified and framecap
logic has been moved internally. I_Sleep now
takes a sleep duration and I_SleepDuration
generically implements a precise sleep with spin
loop.

Adjust spawned mobj z by gravity inversion

Only check VERTICALFLIP for old_z calc

Fix Marathon Mode menu for uncapped

Move screenshot code before timing

Only play emerald hunt sounds on new tics

Restore deleted zofs (fixes signpost sparkles etc)

Revert to before screenUpdate boolean was added

Was done this way for the sake of an older version of uncapped's timing; now that the new timing is much better, I decided it should remain as close to pre-uncapped as it can be.

Run UpdateLevelInterpolators in preticker

Fixes KartKrew/Kart-Public#10

Run UpdateMobjInterpolators in preticker

Reset view interp in preticker

Add per-split player view resets
2022-05-06 22:47:39 -05:00

4854 lines
127 KiB
C

// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2020 by Sonic Team Junior.
//
// 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 "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_terrain.h" // K_SpawnSplashForMobj
#include "k_color.h"
#ifdef HW3SOUND
#include "hardware/hw3sound.h"
#endif
#ifdef HWRENDER
#include "hardware/hw_light.h"
#include "hardware/hw_main.h"
#endif
#if 0
static void P_NukeAllPlayers(player_t *player);
#endif
//
// Jingle stuff.
//
jingle_t jingleinfo[NUMJINGLES] = {
// {musname, looping, reset, nest}
{"" , false}, // JT_NONE
{"" , false}, // JT_OTHER
{"" , false}, // JT_MASTER
{"_1up" , false},
{"_shoes" , true},
{"_inv" , false},
{"_minv" , false},
{"_drown" , false},
{"_super" , true},
{"_gover" , false},
{"_ntime" , false}, // JT_NIGHTSTIMEOUT
{"_drown" , false} // JT_SSTIMEOUT
// {"_clear" , false},
// {"_inter" , true},
// {"_conti" , true}
};
//
// 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)
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));
// 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 (player->viewz < mo->floorz + FixedMul(FRACUNIT, mo->scale))
player->viewz = mo->floorz + FixedMul(FRACUNIT, mo->scale);
}
else
{
player->viewz = mo->z + player->viewheight;
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)
{
if (gamemap >= sstage_start && gamemap <= sstage_end)
return (UINT8)(gamemap - sstage_start);
if (gamemap >= smpstage_start || gamemap <= smpstage_end)
return (UINT8)(gamemap - smpstage_start);
return 0;
}
//
// P_GiveEmerald
//
// Award an emerald upon completion
// of a special stage.
//
void P_GiveEmerald(boolean spawnObj)
{
UINT8 em = P_GetNextEmerald();
S_StartSound(NULL, sfx_cgot); // Got the emerald!
emeralds |= (1 << em);
stagefailed = false;
if (spawnObj)
{
// The Chaos Emerald begins to orbit us!
// Only visibly give it to ONE person!
UINT8 i, pnum = ((playeringame[consoleplayer]) && (!players[consoleplayer].spectator) && (players[consoleplayer].mo)) ? consoleplayer : 255;
for (i = 0; i < MAXPLAYERS; i++)
{
mobj_t *emmo;
if (!playeringame[i])
continue;
if (players[i].spectator)
continue;
if (!players[i].mo)
continue;
emmo = P_SpawnMobjFromMobj(players[i].mo, 0, 0, players[i].mo->height, MT_GOTEMERALD);
if (!emmo)
continue;
P_SetTarget(&emmo->target, players[i].mo);
P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em);
// Make sure we're not being carried before our tracer is changed
players[i].carry = CR_NONE;
P_SetTarget(&players[i].mo->tracer, emmo);
if (pnum == 255)
{
pnum = i;
continue;
}
if (i == pnum)
continue;
emmo->flags2 |= RF_DONTDRAW;
}
}
}
//
// P_GiveFinishFlags
//
// Give the player visual indicators
// that they've finished the map.
//
void P_GiveFinishFlags(player_t *player)
{
angle_t angle = FixedAngle(player->mo->angle << FRACBITS);
UINT8 i;
if (!player->mo)
return;
if (!(netgame||multiplayer))
return;
for (i = 0; i < 3; i++)
{
angle_t fa = (angle >> ANGLETOFINESHIFT) & FINEMASK;
fixed_t xoffs = FINECOSINE(fa);
fixed_t yoffs = FINESINE(fa);
mobj_t* flag = P_SpawnMobjFromMobj(player->mo, xoffs, yoffs, 0, MT_FINISHFLAG);
P_InitAngle(flag, angle);
angle += FixedAngle(120*FRACUNIT);
P_SetTarget(&flag->target, player->mo);
}
}
//
// 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(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_);
player->carry = CR_NONE;
player->onconveyor = 0;
//player->drift = player->driftcharge = 0;
player->trickpanel = 0;
player->glanceDir = 0;
}
//
// 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_BUMPERS)) // No rings in Battle Mode
return 0;
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;
//player->totalring += num_rings; // Used for GP lives later -- maybe you might want to move this earlier to discourage ring debt...
return num_rings;
}
INT32 P_GivePlayerSpheres(player_t *player, INT32 num_spheres)
{
num_spheres += player->spheres;
// Not alive
if ((gametyperules & GTR_BUMPERS) && (player->bumpers <= 0))
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 > 9)
player->lives = 9;
else if (player->lives < 1)
player->lives = 1;
}
// Adds to the player's score
void P_AddPlayerScore(player_t *player, UINT32 amount)
{
if (!((gametyperules & GTR_BUMPERS)))
return;
if (player->exiting) // srb2kart
return;
// Don't go above MAXSCORE.
if (player->roundscore + amount < MAXSCORE)
player->roundscore += amount;
else
player->roundscore = MAXSCORE;
}
//
// P_PlayLivesJingle
//
void P_PlayLivesJingle(player_t *player)
{
if (player && !P_IsLocalPlayer(player))
return;
if (use1upSound)
S_StartSound(NULL, sfx_oneup);
else
{
P_PlayJingle(player, JT_1UP);
strlcpy(S_sfx[sfx_None].caption, "One-up", 7);
S_StartCaption(sfx_None, -1, extralifetics+1);
}
}
void P_PlayJingle(player_t *player, jingletype_t jingletype)
{
const char *musname = jingleinfo[jingletype].musname;
UINT16 musflags = 0;
boolean looping = jingleinfo[jingletype].looping;
char newmusic[7];
strncpy(newmusic, musname, 7);
#ifdef HAVE_LUA_MUSICPLUS
if(LUAh_MusicJingle(jingletype, newmusic, &musflags, &looping))
return;
#endif
newmusic[6] = 0;
P_PlayJingleMusic(player, newmusic, musflags, looping, jingletype);
}
//
// P_PlayJingleMusic
//
void P_PlayJingleMusic(player_t *player, const char *musname, UINT16 musflags, boolean looping, UINT16 status)
{
// If gamestate != GS_LEVEL, always play the jingle (1-up intermission)
if (gamestate == GS_LEVEL && player && !P_IsLocalPlayer(player))
return;
S_RetainMusic(musname, musflags, looping, 0, status);
S_StopMusic();
S_ChangeMusicInternal(musname, looping);
}
boolean P_EvaluateMusicStatus(UINT16 status, const char *musname)
{
// \todo lua hook
int i;
boolean result = false;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!P_IsLocalPlayer(&players[i]))
continue;
switch(status)
{
case JT_1UP: // Extra life
result = false; //(players[i].powers[pw_extralife] > 1);
break;
case JT_SHOES: // Speed shoes
/*if (players[i].powers[pw_sneakers] > 1 && !players[i].powers[pw_super])
{
//strlcpy(S_sfx[sfx_None].caption, "Speed shoes", 12);
//S_StartCaption(sfx_None, -1, players[i].powers[pw_sneakers]);
result = true;
}
else*/
result = false;
break;
case JT_INV: // Invincibility
case JT_MINV: // Mario Invincibility
/*if (players[i].powers[pw_invulnerability] > 1)
{
//strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14);
//S_StartCaption(sfx_None, -1, players[i].powers[pw_invulnerability]);
result = true;
}
else*/
result = false;
break;
case JT_DROWN: // Drowning
result = false; //(players[i].powers[pw_underwater] && players[i].powers[pw_underwater] <= 11*TICRATE + 1);
break;
case JT_SUPER: // Super Sonic
result = false; //(players[i].powers[pw_super]);
break;
case JT_GOVER: // Game Over
result = (players[i].lives <= 0);
break;
case JT_NIGHTSTIMEOUT: // NiGHTS Time Out (10 seconds)
case JT_SSTIMEOUT:
result = false; //(players[i].nightstime && players[i].nightstime <= 10*TICRATE);
break;
case JT_OTHER: // Other state
result = LUAh_ShouldJingleContinue(&players[i], musname);
break;
case JT_NONE: // Null state
case JT_MASTER: // Main level music
default:
result = true;
}
if (result)
break;
}
return result;
}
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);
}
void P_PlayVictorySound(mobj_t *source)
{
if (cv_kartvoices.value)
S_StartSound(source, sfx_kwin);
}
//
// P_EndingMusic
//
// Consistently sets ending music!
//
boolean P_EndingMusic(player_t *player)
{
char buffer[9];
boolean looping = true;
INT32 bestlocalpos, test;
player_t *bestlocalplayer;
if (!P_IsLocalPlayer(player)) // Only applies to a local player
return false;
if (multiplayer && demo.playback) // Don't play this in multiplayer replays
return false;
// Event - Level Finish
// Check for if this is valid or not
#define getplayerpos(p) \
(((players[p].position < 1) || (players[p].pflags & PF_NOCONTEST)) \
? MAXPLAYERS+1 \
: players[p].position);
if (r_splitscreen)
{
INT32 *localplayertable = (splitscreen_partied[consoleplayer] ? splitscreen_party[consoleplayer] : g_localplayers);
if (!((players[localplayertable[0]].exiting || (players[localplayertable[0]].pflags & PF_NOCONTEST))
|| (players[localplayertable[1]].exiting || (players[localplayertable[1]].pflags & PF_NOCONTEST))
|| ((r_splitscreen > 1) && (players[localplayertable[2]].exiting || (players[localplayertable[2]].pflags & PF_NOCONTEST)))
|| ((r_splitscreen > 2) && (players[localplayertable[3]].exiting || (players[localplayertable[3]].pflags & PF_NOCONTEST)))))
return false;
bestlocalplayer = &players[localplayertable[0]];
bestlocalpos = getplayerpos(localplayertable[0]);
#define setbests(p) \
test = getplayerpos(p); \
if (test < bestlocalpos) \
{ \
bestlocalplayer = &players[p]; \
bestlocalpos = test; \
}
setbests(localplayertable[1]);
if (r_splitscreen > 1)
setbests(localplayertable[2]);
if (r_splitscreen > 2)
setbests(localplayertable[3]);
#undef setbests
}
else
{
if (!(player->exiting || (player->pflags & PF_NOCONTEST)))
return false;
bestlocalplayer = player;
bestlocalpos = getplayerpos((player-players));
}
#undef getplayerpos
if ((gametyperules & GTR_CIRCUIT) && bestlocalpos == MAXPLAYERS+1)
sprintf(buffer, "k*fail"); // F-Zero death results theme
else
{
if (K_IsPlayerLosing(bestlocalplayer))
sprintf(buffer, "k*lose");
else if (bestlocalpos == 1)
sprintf(buffer, "k*win");
else
sprintf(buffer, "k*ok");
}
S_SpeedMusic(1.0f);
if ((gametyperules & GTR_CIRCUIT))
buffer[1] = 'r';
else if ((gametyperules & GTR_BUMPERS))
{
buffer[1] = 'b';
looping = false;
}
S_ChangeMusicInternal(buffer, looping);
return true;
}
//
// P_RestoreMusic
//
// Restores music after some special music change
//
void P_RestoreMusic(player_t *player)
{
UINT32 position;
if (!P_IsLocalPlayer(player)) // Only applies to a local player
return;
S_SpeedMusic(1.0f);
// TO-DO: Use jingle system for Kart's stuff
// Event - HERE COMES A NEW CHALLENGER
if (mapreset)
{
S_ChangeMusicInternal("chalng", false); //S_StopMusic();
return;
}
// Event - Level Ending
if (P_EndingMusic(player))
return;
// Event - Level Start
if (bossinfo.boss == false && (leveltime < (starttime + (TICRATE/2)))) // see also where time overs are handled
return;
{
INT32 wantedmus = 0; // 0 is level music, 1 is invincibility, 2 is grow
if (r_splitscreen)
{
INT32 bestlocaltimer = 1;
INT32 *localplayertable = (splitscreen_partied[consoleplayer] ? splitscreen_party[consoleplayer] : g_localplayers);
#define setbests(p) \
if (players[p].playerstate == PST_LIVE) \
{ \
if (players[p].invincibilitytimer > bestlocaltimer) \
{ wantedmus = 1; bestlocaltimer = players[p].invincibilitytimer; } \
else if (players[p].growshrinktimer > bestlocaltimer) \
{ wantedmus = 2; bestlocaltimer = players[p].growshrinktimer; } \
}
setbests(localplayertable[0]);
setbests(localplayertable[1]);
if (r_splitscreen > 1)
setbests(localplayertable[2]);
if (r_splitscreen > 2)
setbests(localplayertable[3]);
#undef setbests
}
else
{
if (player->playerstate == PST_LIVE)
{
if (player->invincibilitytimer > 1)
wantedmus = 1;
else if (player->growshrinktimer > 1)
wantedmus = 2;
}
}
// Item - Grow
if (wantedmus == 2)
{
S_ChangeMusicInternal("kgrow", true);
S_SetRestoreMusicFadeInCvar(&cv_growmusicfade);
}
// Item - Invincibility
else if (wantedmus == 1)
{
S_ChangeMusicInternal("kinvnc", true);
S_SetRestoreMusicFadeInCvar(&cv_invincmusicfade);
}
else
{
#if 0
// Event - Final Lap
// Still works for GME, but disabled for consistency
if ((gametyperules & GTR_CIRCUIT) && player->laps >= (UINT8)(cv_numlaps.value))
S_SpeedMusic(1.2f);
#endif
if (mapmusresume && cv_resume.value)
position = mapmusresume;
else
position = mapmusposition;
S_ChangeMusicEx(mapmusname, mapmusflags, true, position, 0,
S_GetRestoreMusicFadeIn());
S_ClearRestoreMusicFadeInCvar();
mapmusresume = 0;
}
}
}
//
// P_IsObjectInGoop
//
// Returns true if the object is inside goop water.
// (Spectators and objects otherwise without gravity cannot have goop gravity!)
//
boolean P_IsObjectInGoop(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(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(mobj_t *mo, 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->flags & FF_EXISTS))
continue;
// If the FOF is configured to let the object through, continue.
if (!((rover->flags & FF_BLOCKPLAYER && mo->player)
|| (rover->flags & FF_BLOCKOTHERS && !mo->player)))
continue;
// If the the platform is intangible from below, continue.
if (rover->flags & FF_PLATFORM)
continue;
// If the FOF is a water block, continue. (Unnecessary check?)
if (rover->flags & FF_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->flags & FF_EXISTS))
continue;
// If the FOF is configured to let the object through, continue.
if (!((rover->flags & FF_BLOCKPLAYER && mo->player)
|| (rover->flags & FF_BLOCKOTHERS && !mo->player)))
continue;
// If the the platform is intangible from above, continue.
if (rover->flags & FF_REVERSEPLATFORM)
continue;
// If the FOF is a water block, continue. (Unnecessary check?)
if (rover->flags & FF_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(mobj_t *mo, 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(player_t *player)
{
UINT8 i;
for (i = 0; i <= r_splitscreen; i++)
{
if (player == &players[g_localplayers[i]])
return true;
}
return false;
}
//
// P_IsLocalPlayer
//
// Returns true if player is
// on the local machine
// (or simulated party)
//
boolean P_IsLocalPlayer(player_t *player)
{
UINT8 i;
// 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;
// parties - treat everyone as if it's couch co-op
if (splitscreen_partied[consoleplayer])
{
for (i = 0; i < splitscreen_party_size[consoleplayer]; i++)
{
if (splitscreen_party[consoleplayer][i] == (player-players))
return true;
}
return false;
}
return P_IsMachineLocalPlayer(player);
}
//
// P_IsDisplayPlayer
//
// Returns true if player is
// currently being watched.
//
boolean P_IsDisplayPlayer(player_t *player)
{
UINT8 i;
for (i = 0; i <= r_splitscreen; i++) // DON'T skip P1
{
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->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 |= tr_trans50 << RF_TRANSSHIFT;
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;
ghost->rollangle = mobj->rollangle;
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;
return ghost;
}
//
// P_DoPlayerExit
//
// Player exits the map via sector trigger
void P_DoPlayerExit(player_t *player)
{
const boolean losing = K_IsPlayerLosing(player);
if (player->exiting || mapreset)
return;
if (P_IsLocalPlayer(player) && (!player->spectator && !demo.playback))
legitimateexit = true;
if (G_GametypeUsesLives() && losing)
{
// Remove a life from the losing player
K_PlayerLoseLife(player);
}
if ((gametyperules & GTR_CIRCUIT)) // If in Race Mode, allow
{
player->exiting = raceexittime+2;
K_KartUpdatePosition(player);
if (cv_kartvoices.value)
{
if (P_IsDisplayPlayer(player))
{
sfxenum_t sfx_id;
if (losing)
sfx_id = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_klose].skinsound];
else
sfx_id = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_kwin].skinsound];
S_StartSound(NULL, sfx_id);
}
else
{
if (losing)
S_StartSound(player->mo, sfx_klose);
else
S_StartSound(player->mo, sfx_kwin);
}
}
if (cv_inttime.value > 0)
P_EndingMusic(player);
if (P_CheckRacers())
player->exiting = raceexittime+1;
}
else if ((gametyperules & GTR_BUMPERS)) // Battle Mode exiting
{
player->exiting = battleexittime+1;
P_EndingMusic(player);
}
else
player->exiting = raceexittime+2; // Accidental death safeguard???
if (grandprixinfo.gp == true)
{
if (player->bot)
{
// Bots are going to get harder... :)
K_IncreaseBotDifficulty(player);
}
else if (!losing)
{
const UINT8 lifethreshold = 20;
UINT8 extra = 0;
// YOU WIN
grandprixinfo.wonround = true;
// Increase your total rings
if (RINGTOTAL(player) > 0)
{
player->totalring += RINGTOTAL(player);
extra = player->totalring / lifethreshold;
if (extra > player->xtralife)
{
P_GivePlayerLives(player, extra - player->xtralife);
S_StartSound(NULL, sfx_cdfm73);
player->xtralife = extra;
}
}
}
}
player->karthud[khud_cardanimation] = 0; // srb2kart: reset battle animation
if (player == &players[consoleplayer])
demo.savebutton = leveltime;
}
//
// P_PlayerHitFloor
//
// Handles player hitting floor surface.
// Returns whether to clip momz.
boolean P_PlayerHitFloor(player_t *player, boolean fromAir)
{
boolean clipmomz;
I_Assert(player->mo != NULL);
clipmomz = !(P_CheckDeathPitCollide(player->mo));
if (fromAir == true && clipmomz == true)
{
K_SpawnSplashForMobj(player->mo, abs(player->mo->momz));
}
return clipmomz;
}
boolean P_InQuicksand(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->flags & FF_EXISTS))
continue;
if (!(rover->flags & FF_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 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;
// SRB2Kart TODO: make shatter blocks the default behavior, we don't need the hundreds of other types
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)
{
if (!node->m_sector)
break;
if (node->m_sector->ffloors)
{
ffloor_t *rover;
fixed_t topheight, bottomheight;
for (rover = node->m_sector->ffloors; rover; rover = rover->next)
{
if (!(rover->flags & FF_EXISTS)) continue;
if ((rover->flags & FF_BUSTUP)/* && rover->master->frontsector->crumblestate == CRUMBLE_NONE*/)
{
// If it's an FF_SHATTER, you can break it just by touching it.
if (rover->flags & FF_SHATTER)
goto bust;
if (rover->flags & FF_STRONGBUST)
continue;
bust:
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->flags & FF_SHATTERBOTTOM)
{
if (player->mo->z+player->mo->momz + player->mo->height < bottomheight)
continue;
if (player->mo->z+player->mo->height > bottomheight)
continue;
}
else if (rover->flags & FF_SPINBUST)
{
if (player->mo->z+player->mo->momz > topheight)
continue;
if (player->mo->z + player->mo->height < bottomheight)
continue;
}
else if (rover->flags & FF_SHATTER)
{
if (player->mo->z + player->mo->momz > topheight)
continue;
if (player->mo->z+player->mo->momz + player->mo->height < bottomheight)
continue;
}
else
{
if (player->mo->z >= topheight)
continue;
if (player->mo->z + player->mo->height < bottomheight)
continue;
}
// Impede the player's fall a bit
if (((rover->flags & FF_SPINBUST) || (rover->flags & FF_SHATTER)) && player->mo->z >= topheight)
player->mo->momz >>= 1;
else if (rover->flags & FF_SHATTER)
{
player->mo->momx >>= 1;
player->mo->momy >>= 1;
}
//if (metalrecording)
// G_RecordBustup(rover);
EV_CrumbleChain(NULL, rover); // node->m_sector
// Run a linedef executor??
if (rover->master->flags & ML_EFFECT5)
P_LinedefExecute((INT16)(P_AproxDistance(rover->master->dx, rover->master->dy)>>FRACBITS), 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)
{
if (!node->m_sector)
break;
if (node->m_sector->ffloors)
{
ffloor_t *rover;
boolean top = true;
fixed_t topheight, bottomheight;
for (rover = node->m_sector->ffloors; rover; rover = rover->next)
{
if (!(rover->flags & FF_EXISTS))
continue; // FOFs should not be bouncy if they don't even "exist"
if (GETSECSPECIAL(rover->master->frontsector->special, 1) != 15)
continue; // this sector type is required for FOFs to be bouncy
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))
top = false;
{
fixed_t linedist;
linedist = P_AproxDistance(rover->master->v1->x-rover->master->v2->x, rover->master->v1->y-rover->master->v2->y);
linedist = FixedDiv(linedist,100*FRACUNIT);
if (top)
{
fixed_t newmom;
pslope_t *slope;
if (abs(oldz - topheight) < abs(oldz + player->mo->height - bottomheight)) { // Hit top
slope = *rover->t_slope;
} else { // Hit bottom
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);
newmom = momentum.z = -FixedMul(momentum.z,linedist)/2;
if (abs(newmom) < (linedist*2))
{
goto bouncydone;
}
if (!(rover->master->flags & ML_NOTBOUNCY))
{
if (newmom > 0)
{
if (newmom < 8*FRACUNIT)
newmom = 8*FRACUNIT;
}
else if (newmom > -8*FRACUNIT && newmom != 0)
newmom = -8*FRACUNIT;
}
if (newmom > player->mo->height/2)
newmom = player->mo->height/2;
else if (newmom < -player->mo->height/2)
newmom = -player->mo->height/2;
momentum.z = newmom*2;
if (slope)
P_QuantizeMomentumToSlope(&momentum, slope);
player->mo->momx = momentum.x;
player->mo->momy = momentum.y;
player->mo->momz = momentum.z/2;
}
else
{
player->mo->momx = -FixedMul(player->mo->momx,linedist);
player->mo->momy = -FixedMul(player->mo->momy,linedist);
}
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->flags & FF_EXISTS)) continue;
if (!(rover->flags & FF_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);
}
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);
}
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;
player->mo->color = K_RainbowColor(leveltime);
// Resume normal music stuff.
if (player->invincibilitytimer == 1)
{
player->mo->color = player->skincolor;
G_GhostAddColor((INT32) (player - players), GHC_NORMAL);
P_RestoreMusic(player);
}
}
//
// 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)
return;
if (player->charflags & SF_MACHINE)
{
if (P_RandomChance(FRACUNIT/5))
{
fixed_t r = player->mo->radius>>FRACBITS;
x += (P_RandomRange(r, -r)<<FRACBITS);
y += (P_RandomRange(r, -r)<<FRACBITS);
z += (P_RandomKey(player->mo->height>>FRACBITS)<<FRACBITS);
bubble = P_SpawnMobj(x, y, z, MT_WATERZAP);
S_StartSound(bubble, sfx_beelec);
}
}
else
{
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(FRACUNIT/16))
bubble = P_SpawnMobj(x, y, z, MT_SMALLBUBBLE);
else if (P_RandomChance(3*FRACUNIT/256))
bubble = P_SpawnMobj(x, y, z, MT_MEDIUMBUBBLE);
}
if (bubble)
{
bubble->threshold = 42;
bubble->destscale = player->mo->scale;
P_SetScale(bubble, bubble->destscale);
}
}
//#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 = FRACUNIT*P_MobjFlip(player->mo)/3; // A bit of extra push-back on slopes
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 (player->stairjank > 8 && leveltime & 3)
{
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
#ifdef POLYOBJECTS
&& player->onconveyor != 1
#endif
)
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);
// 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);
{
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 && (!(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;
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))
{
fixed_t rubberband = K_BotRubberband(player) - 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))
{
newMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0);
if (newMagnitude > K_GetKartSpeed(player, true)) //topspeed)
{
fixed_t tempmomx, tempmomy;
if (oldMagnitude > K_GetKartSpeed(player, true))
{
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), K_GetKartSpeed(player, true)); //topspeed)
tempmomy = FixedMul(FixedDiv(player->mo->momy - player->cmomy, newMagnitude), K_GetKartSpeed(player, true)); //topspeed)
player->mo->momx = tempmomx + player->cmomx;
player->mo->momy = tempmomy + player->cmomy;
}
}
}
}
//
// P_UpdatePlayerAngle
//
// Updates player angleturn with cmd->turning
//
static void P_UpdatePlayerAngle(player_t *player)
{
angle_t angleChange = ANGLE_MAX;
UINT8 i;
K_UpdateSteeringValue(player, player->cmd.turning);
angleChange = K_GetKartTurnValue(player, player->steering) << TICCMD_REDUCE;
P_SetPlayerAngle(player, 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);
}
for (i = 0; i <= r_splitscreen; i++)
{
if (player == &players[displayplayers[i]])
{
localaiming[i] = player->aiming;
break;
}
}
}
//
// P_SpectatorMovement
//
// Control for spectators in multiplayer
//
static void P_SpectatorMovement(player_t *player)
{
ticcmd_t *cmd = &player->cmd;
P_UpdatePlayerAngle(player);
ticruned++;
if (!(cmd->flags & TICCMD_RECEIVED))
ticmiss++;
if (cmd->buttons & BT_ACCELERATE)
player->mo->z += 32*mapobjectscale;
else if (cmd->buttons & BT_BRAKE)
player->mo->z -= 32*mapobjectscale;
if (player->mo->z > player->mo->ceilingz - player->mo->height)
player->mo->z = player->mo->ceilingz - player->mo->height;
if (player->mo->z < player->mo->floorz)
player->mo->z = player->mo->floorz;
player->mo->momx = player->mo->momy = player->mo->momz = 0;
if (cmd->forwardmove != 0)
{
P_Thrust(player->mo, player->mo->angle, cmd->forwardmove*mapobjectscale);
// Quake-style flying spectators :D
player->mo->momz += FixedMul(cmd->forwardmove*mapobjectscale, AIMINGTOSLOPE(player->aiming));
}
}
//
// P_MovePlayer
void P_MovePlayer(player_t *player)
{
ticcmd_t *cmd;
//INT32 i;
fixed_t runspd;
if (countdowntimeup)
return;
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->pflags |= PF_STASIS;
// note: don't unset stasis here
if (player->spectator)
{
player->mo->eflags &= ~MFE_VERTICALFLIP; // deflip...
P_SpectatorMovement(player);
return;
}
//////////////////////
// MOVEMENT CODE //
//////////////////////
P_UpdatePlayerAngle(player);
ticruned++;
if (!(cmd->flags & TICCMD_RECEIVED))
ticmiss++;
P_3dMovement(player);
// Kart frames
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_LOOKDOWN;
}
else if ((player->pflags & PF_FAULT) || (player->spinouttimer > 0))
{
UINT16 speed = ((player->pflags & PF_FAULT) ? player->nocontrol : player->spinouttimer)/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
{
K_KartMoveAnimation(player);
if (player->trickpanel == 2)
{
player->drawangle += ANGLE_22h;
}
else if (player->trickpanel >= 3)
{
player->drawangle -= ANGLE_22h;
}
else
{
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//
//////////////////
if (((!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z + player->mo->height >= player->mo->watertop && player->mo->z <= player->mo->watertop)
|| (player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height >= player->mo->waterbottom && player->mo->z <= player->mo->waterbottom))
&& (player->speed > runspd)
&& player->mo->momz == 0 && player->carry != CR_SLIDING && !player->spectator)
{
fixed_t trailScale = FixedMul(FixedDiv(player->speed - runspd, K_GetKartSpeed(player, false) - runspd), mapobjectscale);
fixed_t playerTopSpeed = K_GetKartSpeed(player, false);
if (playerTopSpeed > runspd)
trailScale = FixedMul(FixedDiv(player->speed - runspd, playerTopSpeed - runspd), mapobjectscale);
else
trailScale = mapobjectscale; // Scaling is based off difference between runspeed and top speed
if (trailScale > 0)
{
const angle_t forwardangle = K_MomentumAngle(player->mo);
const fixed_t playerVisualRadius = player->mo->radius + (8 * player->mo->scale);
const size_t numFrames = S_WATERTRAIL8 - S_WATERTRAIL1;
const statenum_t curOverlayFrame = S_WATERTRAIL1 + (leveltime % numFrames);
const statenum_t curUnderlayFrame = S_WATERTRAILUNDERLAY1 + (leveltime % numFrames);
fixed_t x1, x2, y1, y2;
mobj_t *water;
x1 = player->mo->x + player->mo->momx + P_ReturnThrustX(player->mo, forwardangle + ANGLE_90, playerVisualRadius);
y1 = player->mo->y + player->mo->momy + P_ReturnThrustY(player->mo, forwardangle + ANGLE_90, playerVisualRadius);
x1 = x1 + P_ReturnThrustX(player->mo, forwardangle, playerVisualRadius);
y1 = y1 + P_ReturnThrustY(player->mo, forwardangle, playerVisualRadius);
x2 = player->mo->x + player->mo->momx + P_ReturnThrustX(player->mo, forwardangle - ANGLE_90, playerVisualRadius);
y2 = player->mo->y + player->mo->momy + P_ReturnThrustY(player->mo, forwardangle - ANGLE_90, playerVisualRadius);
x2 = x2 + P_ReturnThrustX(player->mo, forwardangle, playerVisualRadius);
y2 = y2 + P_ReturnThrustY(player->mo, forwardangle, playerVisualRadius);
// Left
// underlay
water = P_SpawnMobj(x1, y1,
((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_WATERTRAILUNDERLAY].height, player->mo->scale) : player->mo->watertop), MT_WATERTRAILUNDERLAY);
P_InitAngle(water, forwardangle - ANGLE_180 - ANGLE_22h);
water->destscale = trailScale;
water->momx = player->mo->momx;
water->momy = player->mo->momy;
water->momz = player->mo->momz;
P_SetScale(water, trailScale);
P_SetMobjState(water, curUnderlayFrame);
// overlay
water = P_SpawnMobj(x1, y1,
((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_WATERTRAIL].height, player->mo->scale) : player->mo->watertop), MT_WATERTRAIL);
P_InitAngle(water, forwardangle - ANGLE_180 - ANGLE_22h);
water->destscale = trailScale;
water->momx = player->mo->momx;
water->momy = player->mo->momy;
water->momz = player->mo->momz;
P_SetScale(water, trailScale);
P_SetMobjState(water, curOverlayFrame);
// Right
// Underlay
water = P_SpawnMobj(x2, y2,
((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_WATERTRAILUNDERLAY].height, player->mo->scale) : player->mo->watertop), MT_WATERTRAILUNDERLAY);
P_InitAngle(water, forwardangle - ANGLE_180 + ANGLE_22h);
water->destscale = trailScale;
water->momx = player->mo->momx;
water->momy = player->mo->momy;
water->momz = player->mo->momz;
P_SetScale(water, trailScale);
P_SetMobjState(water, curUnderlayFrame);
// Overlay
water = P_SpawnMobj(x2, y2,
((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_WATERTRAIL].height, player->mo->scale) : player->mo->watertop), MT_WATERTRAIL);
P_InitAngle(water, forwardangle - ANGLE_180 + ANGLE_22h);
water->destscale = trailScale;
water->momx = player->mo->momx;
water->momy = player->mo->momy;
water->momz = player->mo->momz;
P_SetScale(water, trailScale);
P_SetMobjState(water, curOverlayFrame);
if (!S_SoundPlaying(player->mo, sfx_s3kdbs))
{
const INT32 volume = (min(trailScale, FRACUNIT) * 255) / FRACUNIT;
S_StartSoundAtVolume(player->mo, sfx_s3kdbs, volume);
}
}
}
// Little water sound while touching water - just a nicety.
if ((player->mo->eflags & MFE_TOUCHWATER) && !(player->mo->eflags & MFE_UNDERWATER) && !player->spectator)
{
if (P_RandomChance(FRACUNIT/2) && leveltime % TICRATE == 0)
S_StartSound(player->mo, sfx_floush);
}
////////////////////////////
//SPINNING AND SPINDASHING//
////////////////////////////
// SRB2kart - Drifting smoke and fire
if ((player->sneakertimer || 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);
if (P_IsObjectOnGround(player->mo))
player->mo->pmomz = 0;
}
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 (mo->flags & MF_MONITOR)
continue; // Monitors cannot be 'nuked'.
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;
indirectitemcooldown = 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) // Thunder 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);
player->mo->renderflags |= RF_DONTDRAW;
}
}
else
player->karthud[khud_timeovercam] = 0;
K_KartPlayerHUDUpdate(player);
if (player->pflags & PF_NOCONTEST)
{
playerGone = true;
}
else if (player->bot == false)
{
if (G_GametypeUsesLives() == true && player->lives == 0)
{
playerGone = true;
}
}
if (playerGone == false && player->deadtimer > TICRATE)
{
player->playerstate = PST_REBORN;
}
// Keep time rolling
if (!(exitcountdown && !racecountdown) && !(player->exiting || mapreset) && !(player->pflags & PF_NOCONTEST) && !stoppedclock)
{
if (leveltime >= starttime)
{
player->realtime = leveltime - starttime;
if (player == &players[consoleplayer])
{
if (player->spectator || !circuitmap)
curlap = 0;
else if (curlap != UINT32_MAX)
curlap++; // This is too complicated to sync to realtime, just sorta hope for the best :V
}
}
else
{
player->realtime = 0;
if (player == &players[consoleplayer])
curlap = 0;
}
}
if (!player->mo)
return;
player->mo->colorized = false;
player->mo->color = player->skincolor;
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
static 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);
}
static 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);
}
static 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);
}
static 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);
}
static CV_PossibleValue_t CV_CamSpeed[] = {{0, "MIN"}, {1*FRACUNIT, "MAX"}, {0, NULL}};
static CV_PossibleValue_t CV_CamRotate[] = {{-720, "MIN"}, {720, "MAX"}, {0, NULL}};
consvar_t cv_cam_dist[MAXSPLITSCREENPLAYERS] = {
CVAR_INIT ("cam_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL),
CVAR_INIT ("cam2_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL),
CVAR_INIT ("cam3_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL),
CVAR_INIT ("cam4_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL)
};
consvar_t cv_cam_height[MAXSPLITSCREENPLAYERS] = {
CVAR_INIT ("cam_height", "50", CV_FLOAT|CV_SAVE, NULL, NULL),
CVAR_INIT ("cam2_height", "50", CV_FLOAT|CV_SAVE, NULL, NULL),
CVAR_INIT ("cam3_height", "50", CV_FLOAT|CV_SAVE, NULL, NULL),
CVAR_INIT ("cam4_height", "50", CV_FLOAT|CV_SAVE, NULL, NULL)
};
consvar_t cv_cam_still[MAXSPLITSCREENPLAYERS] = {
CVAR_INIT ("cam_still", "Off", 0, CV_OnOff, NULL),
CVAR_INIT ("cam2_still", "Off", 0, CV_OnOff, NULL),
CVAR_INIT ("cam3_still", "Off", 0, CV_OnOff, NULL),
CVAR_INIT ("cam4_still", "Off", 0, CV_OnOff, NULL)
};
consvar_t cv_cam_speed[MAXSPLITSCREENPLAYERS] = {
CVAR_INIT ("cam_speed", "0.4", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL),
CVAR_INIT ("cam2_speed", "0.4", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL),
CVAR_INIT ("cam3_speed", "0.4", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL),
CVAR_INIT ("cam4_speed", "0.4", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL)
};
consvar_t cv_cam_rotate[MAXSPLITSCREENPLAYERS] = {
CVAR_INIT ("cam_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange),
CVAR_INIT ("cam2_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate2_OnChange),
CVAR_INIT ("cam3_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate3_OnChange),
CVAR_INIT ("cam4_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate4_OnChange)
};
consvar_t cv_tilting = CVAR_INIT ("tilting", "On", CV_SAVE, CV_OnOff, NULL);
consvar_t cv_actionmovie = CVAR_INIT ("actionmovie", "On", CV_SAVE, CV_OnOff, NULL);
consvar_t cv_windowquake = CVAR_INIT ("windowquake", "Off", CV_SAVE, CV_OnOff, NULL);
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};
// Heavily simplified version of G_BuildTicCmd that only takes the local first player's control input and converts it to readable ticcmd_t
// we then throw that ticcmd garbage in the camera and make it move
// TODO: please just use the normal ticcmd function somehow
static ticcmd_t cameracmd;
struct demofreecam_s democam;
// called by m_menu to reinit cam input every time it's toggled
void P_InitCameraCmd(void)
{
memset(&cameracmd, 0, sizeof(ticcmd_t)); // initialize cmd
}
static ticcmd_t *P_CameraCmd(camera_t *cam)
{
INT32 forward, axis; //i
// these ones used for multiple conditions
boolean turnleft, turnright, mouseaiming;
boolean invertmouse, lookaxis, usejoystick, kbl;
INT32 player_invert;
INT32 screen_invert;
ticcmd_t *cmd = &cameracmd;
(void)cam;
if (!demo.playback)
return cmd; // empty cmd, no.
kbl = democam.keyboardlook;
G_CopyTiccmd(cmd, I_BaseTiccmd(), 1); // empty, or external driver
mouseaiming = true;
invertmouse = cv_invertmouse.value;
lookaxis = cv_lookaxis[0].value;
usejoystick = true;
turnright = PlayerInputDown(1, gc_turnright);
turnleft = PlayerInputDown(1, gc_turnleft);
axis = PlayerJoyAxis(1, AXISTURN);
if (encoremode)
{
turnright ^= turnleft; // swap these using three XORs
turnleft ^= turnright;
turnright ^= turnleft;
axis = -axis;
}
if (axis != 0)
{
turnright = turnright || (axis > 0);
turnleft = turnleft || (axis < 0);
}
forward = 0;
cmd->turning = 0;
// let movement keys cancel each other out
if (turnright && !(turnleft))
{
cmd->turning -= KART_FULLTURN;
}
else if (turnleft && !(turnright))
{
cmd->turning += KART_FULLTURN;
}
cmd->turning -= (mousex * 8) * (encoremode ? -1 : 1);
axis = PlayerJoyAxis(1, AXISMOVE);
if (PlayerInputDown(1, gc_accelerate) || (usejoystick && axis > 0))
cmd->buttons |= BT_ACCELERATE;
axis = PlayerJoyAxis(1, AXISBRAKE);
if (PlayerInputDown(1, gc_brake) || (usejoystick && axis > 0))
cmd->buttons |= BT_BRAKE;
axis = PlayerJoyAxis(1, AXISAIM);
if (PlayerInputDown(1, gc_aimforward) || (usejoystick && axis < 0))
forward += MAXPLMOVE;
if (PlayerInputDown(1, gc_aimbackward) || (usejoystick && axis > 0))
forward -= MAXPLMOVE;
// fire with any button/key
axis = PlayerJoyAxis(1, AXISFIRE);
if (PlayerInputDown(1, gc_fire) || (usejoystick && axis > 0))
cmd->buttons |= BT_ATTACK;
// spectator aiming shit, ahhhh...
player_invert = invertmouse ? -1 : 1;
screen_invert = 1; // nope
// mouse look stuff (mouse look is not the same as mouse aim)
kbl = false;
// looking up/down
cmd->aiming += (mlooky<<19)*player_invert*screen_invert;
axis = PlayerJoyAxis(1, AXISLOOK);
// spring back if not using keyboard neither mouselookin'
if (!kbl && !lookaxis && !mouseaiming)
cmd->aiming = 0;
if (PlayerInputDown(1, gc_lookup) || (axis < 0))
{
cmd->aiming += KB_LOOKSPEED * screen_invert;
kbl = true;
}
else if (PlayerInputDown(1, gc_lookdown) || (axis > 0))
{
cmd->aiming -= KB_LOOKSPEED * screen_invert;
kbl = true;
}
if (PlayerInputDown(1, gc_centerview)) // No need to put a spectator limit on this one though :V
cmd->aiming = 0;
mousex = mousey = mlooky = 0;
cmd->forwardmove += (SINT8)forward;
if (cmd->forwardmove > MAXPLMOVE)
cmd->forwardmove = MAXPLMOVE;
else if (cmd->forwardmove < -MAXPLMOVE)
cmd->forwardmove = -MAXPLMOVE;
if (cmd->turning > KART_FULLTURN)
cmd->turning = KART_FULLTURN;
else if (cmd->turning < -KART_FULLTURN)
cmd->turning = -KART_FULLTURN;
democam.keyboardlook = kbl;
return cmd;
}
void P_DemoCameraMovement(camera_t *cam)
{
ticcmd_t *cmd;
angle_t thrustangle;
mobj_t *awayviewmobj_hack;
player_t *lastp;
// update democam stuff with what we got here:
democam.cam = cam;
democam.localangle = cam->angle;
democam.localaiming = cam->aiming;
// first off we need to get button input
cmd = P_CameraCmd(cam);
cam->aiming += cmd->aiming << TICCMD_REDUCE;
cam->angle += cmd->turning << TICCMD_REDUCE;
democam.localangle += cmd->turning << TICCMD_REDUCE;
democam.localaiming += cmd->aiming << TICCMD_REDUCE;
cam->aiming = G_ClipAimingPitch((INT32 *)&cam->aiming);
democam.localaiming = G_ClipAimingPitch((INT32 *)&democam.localaiming);
// camera movement:
if (cmd->buttons & BT_ACCELERATE)
cam->z += 32*mapobjectscale;
else if (cmd->buttons & BT_BRAKE)
cam->z -= 32*mapobjectscale;
// if you hold item, you will lock on to displayplayer. (The last player you were ""f12-ing"")
if (cmd->buttons & BT_ATTACK)
{
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->momx = cam->momy = cam->momz = 0;
if (cmd->forwardmove != 0)
{
thrustangle = cam->angle >> ANGLETOFINESHIFT;
cam->x += FixedMul(cmd->forwardmove*mapobjectscale, FINECOSINE(thrustangle));
cam->y += FixedMul(cmd->forwardmove*mapobjectscale, FINESINE(thrustangle));
cam->z += FixedMul(cmd->forwardmove*mapobjectscale, 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.
}
// awayviewmobj hack; this is to prevent us from hearing sounds from the player's perspective
awayviewmobj_hack = P_SpawnMobj(cam->x, cam->y, cam->z, MT_THOK);
awayviewmobj_hack->tics = 2;
awayviewmobj_hack->renderflags |= RF_DONTDRAW;
democam.soundmobj = awayviewmobj_hack;
// update subsector to avoid crashes;
cam->subsector = R_PointInSubsector(cam->x, cam->y);
}
void P_ResetCamera(player_t *player, camera_t *thiscam)
{
tic_t tries = 0;
fixed_t x, y, z;
if (demo.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->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;
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;
#ifndef NOCLIPCAM
boolean cameranoclip;
subsector_t *newsubsec;
#endif
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;
democam.soundmobj = NULL; // reset this each frame, we don't want the game crashing for stupid reasons now do we
// 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 (demo.freecam)
{
P_DemoCameraMovement(thiscam);
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;
}
mo = player->mo;
if (mo->hitlag > 0 || player->playerstate == PST_DEAD)
{
// 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;
}
#ifndef NOCLIPCAM
cameranoclip = ((player->pflags & PF_NOCLIP)
|| (mo->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT)) // Noclipping player camera noclips too!!
|| (leveltime < introtime)); // Kart intro cam
#endif
if ((player->pflags & PF_NOCONTEST) && (gametyperules & GTR_CIRCUIT)) // 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;
}
thiscam->radius = 20*mapobjectscale;
thiscam->height = 16*mapobjectscale;
// Don't run while respawning from a starpost
// Inu 4/8/13 Why not?!
// if (leveltime > 0 && timeinmap <= 0)
// return true;
if (demo.playback)
{
focusangle = mo->angle;
focusaiming = 0;
}
else
{
focusangle = localangle[num];
focusaiming = localaiming[num];
}
if (P_CameraThinker(player, thiscam, resetcalled))
return true;
lookback = ( player->cmd.buttons & BT_LOOKBACK );
camspeed = cv_cam_speed[num].value;
camstill = cv_cam_still[num].value;
camrotate = cv_cam_rotate[num].value;
camdist = FixedMul(cv_cam_dist[num].value, mapobjectscale);
camheight = FixedMul(cv_cam_height[num].value, mapobjectscale);
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 * mapobjectscale)*3;
camheight += (introcam * mapobjectscale)*2;
}
else if (player->exiting) // SRB2Kart: Leave the camera behind while exiting, for dramatic effect!
camstill = true;
else if (lookback || lookbackdelay[num]) // SRB2kart - Camera flipper
{
#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;
}
}
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*mapobjectscale;
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]);
}
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)
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;
}
#ifndef NOCLIPCAM // Disable all z-clipping for noclip cam
// move camera down to move under lower ceilings
newsubsec = R_PointInSubsectorOrNull(((mo->x>>FRACBITS) + (thiscam->x>>FRACBITS))<<(FRACBITS-1), ((mo->y>>FRACBITS) + (thiscam->y>>FRACBITS))<<(FRACBITS-1));
if (!newsubsec)
newsubsec = thiscam->subsector;
if (newsubsec)
{
fixed_t myfloorz, myceilingz;
fixed_t midz = thiscam->z + (thiscam->z - mo->z)/2;
fixed_t midx = ((mo->x>>FRACBITS) + (thiscam->x>>FRACBITS))<<(FRACBITS-1);
fixed_t midy = ((mo->y>>FRACBITS) + (thiscam->y>>FRACBITS))<<(FRACBITS-1);
// Cameras use the heightsec's heights rather then the actual sector heights.
// If you can see through it, why not move the camera through it too?
if (newsubsec->sector->camsec >= 0)
{
myfloorz = sectors[newsubsec->sector->camsec].floorheight;
myceilingz = sectors[newsubsec->sector->camsec].ceilingheight;
}
else if (newsubsec->sector->heightsec >= 0)
{
myfloorz = sectors[newsubsec->sector->heightsec].floorheight;
myceilingz = sectors[newsubsec->sector->heightsec].ceilingheight;
}
else
{
myfloorz = P_CameraGetFloorZ(thiscam, newsubsec->sector, midx, midy, NULL);
myceilingz = P_CameraGetCeilingZ(thiscam, newsubsec->sector, midx, midy, NULL);
}
// Check list of fake floors and see if floorz/ceilingz need to be altered.
if (newsubsec->sector->ffloors)
{
ffloor_t *rover;
fixed_t delta1, delta2;
INT32 thingtop = midz + thiscam->height;
for (rover = newsubsec->sector->ffloors; rover; rover = rover->next)
{
fixed_t topheight, bottomheight;
if (!(rover->flags & FF_BLOCKOTHERS) || !(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERALL) || GETSECSPECIAL(rover->master->frontsector->special, 4) == 12)
continue;
topheight = P_CameraGetFOFTopZ(thiscam, newsubsec->sector, rover, midx, midy, NULL);
bottomheight = P_CameraGetFOFBottomZ(thiscam, newsubsec->sector, rover, midx, midy, NULL);
delta1 = midz - (bottomheight
+ ((topheight - bottomheight)/2));
delta2 = thingtop - (bottomheight
+ ((topheight - bottomheight)/2));
if (topheight > myfloorz && abs(delta1) < abs(delta2))
myfloorz = topheight;
if (bottomheight < myceilingz && abs(delta1) >= abs(delta2))
myceilingz = bottomheight;
}
}
// Check polyobjects and see if floorz/ceilingz need to be altered
{
INT32 xl, xh, yl, yh, bx, by;
validcount++;
xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
yh = (unsigned)(tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT;
BMBOUNDFIX(xl, xh, yl, yh);
for (by = yl; by <= yh; by++)
for (bx = xl; bx <= xh; bx++)
{
INT32 offset;
polymaplink_t *plink; // haleyjd 02/22/06
if (bx < 0 || by < 0 || bx >= bmapwidth || by >= bmapheight)
continue;
offset = by*bmapwidth + bx;
// haleyjd 02/22/06: consider polyobject lines
plink = polyblocklinks[offset];
while (plink)
{
polyobj_t *po = plink->po;
if (po->validcount != validcount) // if polyobj hasn't been checked
{
sector_t *polysec;
fixed_t delta1, delta2, thingtop;
fixed_t polytop, polybottom;
po->validcount = validcount;
if (!P_PointInsidePolyobj(po, x, y) || !(po->flags & POF_SOLID))
{
plink = (polymaplink_t *)(plink->link.next);
continue;
}
// We're inside it! Yess...
polysec = po->lines[0]->backsector;
if (GETSECSPECIAL(polysec->special, 4) == 12)
{ // Camera noclip polyobj.
plink = (polymaplink_t *)(plink->link.next);
continue;
}
if (po->flags & POF_CLIPPLANES)
{
polytop = polysec->ceilingheight;
polybottom = polysec->floorheight;
}
else
{
polytop = INT32_MAX;
polybottom = INT32_MIN;
}
thingtop = midz + thiscam->height;
delta1 = midz - (polybottom + ((polytop - polybottom)/2));
delta2 = thingtop - (polybottom + ((polytop - polybottom)/2));
if (polytop > myfloorz && abs(delta1) < abs(delta2))
myfloorz = polytop;
if (polybottom < myceilingz && abs(delta1) >= abs(delta2))
myceilingz = polybottom;
}
plink = (polymaplink_t *)(plink->link.next);
}
}
}
// crushed camera
if (myceilingz <= myfloorz + thiscam->height && !resetcalled && !cameranoclip)
{
P_ResetCamera(player, thiscam);
return true;
}
// camera fit?
if (myceilingz != myfloorz
&& myceilingz - thiscam->height < z)
{
/* // no fit
if (!resetcalled && !cameranoclip)
{
P_ResetCamera(player, thiscam);
return true;
}
*/
z = myceilingz - thiscam->height-FixedMul(11*FRACUNIT, mo->scale);
// is the camera fit is there own sector
}
// Make the camera a tad smarter with 3d floors
if (newsubsec->sector->ffloors && !cameranoclip)
{
ffloor_t *rover;
for (rover = newsubsec->sector->ffloors; rover; rover = rover->next)
{
fixed_t topheight, bottomheight;
if ((rover->flags & FF_BLOCKOTHERS) && (rover->flags & FF_RENDERALL) && (rover->flags & FF_EXISTS) && GETSECSPECIAL(rover->master->frontsector->special, 4) == 12)
{
topheight = P_CameraGetFOFTopZ(thiscam, newsubsec->sector, rover, midx, midy, NULL);
bottomheight = P_CameraGetFOFBottomZ(thiscam, newsubsec->sector, rover, midx, midy, NULL);
if (bottomheight - thiscam->height < z
&& midz < bottomheight)
z = bottomheight - thiscam->height-FixedMul(11*FRACUNIT, mo->scale);
else if (topheight + thiscam->height > z
&& midz > topheight)
z = topheight;
if ((mo->z >= topheight && midz < bottomheight)
|| ((mo->z < bottomheight && mo->z+mo->height < topheight) && midz >= topheight))
{
// Can't see
if (!resetcalled)
P_ResetCamera(player, thiscam);
return true;
}
}
}
}
}
if (thiscam->z < thiscam->floorz && !cameranoclip)
thiscam->z = thiscam->floorz;
#endif // NOCLIPCAM
// point viewed by the camera
// this point is just 64 unit forward the player
dist = 64*mapobjectscale;
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;
thiscam->momz = FixedMul(z - thiscam->z, camspeed/2);
}
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);
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);
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);
return (x == thiscam->x && y == thiscam->y && z == thiscam->z && angle == thiscam->aiming);
}
boolean P_SpectatorJoinGame(player_t *player)
{
INT32 changeto = 0;
const char *text = NULL;
// Team changing isn't allowed.
if (!cv_allowteamchange.value)
return false;
// Team changing in Team Match and CTF
// Pressing fire assigns you to a team that needs players if allowed.
// Partial code reproduction from p_tick.c autobalance code.
// a surprise tool that will help us later...
if (G_GametypeHasTeams())
{
INT32 z, numplayersred = 0, numplayersblue = 0;
//find a team by num players, score, or random if all else fails.
for (z = 0; z < MAXPLAYERS; ++z)
if (playeringame[z])
{
if (players[z].ctfteam == 1)
++numplayersred;
else if (players[z].ctfteam == 2)
++numplayersblue;
}
// for z
if (numplayersblue > numplayersred)
changeto = 1;
else if (numplayersred > numplayersblue)
changeto = 2;
else if (bluescore > redscore)
changeto = 1;
else if (redscore > bluescore)
changeto = 2;
else
changeto = (P_RandomFixed() & 1) + 1;
if (!LUAh_TeamSwitch(player, changeto, true, false, false))
return false;
}
// no conditions that could cause the gamejoin to fail below this line
if (player->mo)
{
P_RemoveMobj(player->mo);
player->mo = NULL;
}
player->spectator = false;
player->pflags &= ~PF_WANTSTOJOIN;
player->spectatewait = 0;
player->ctfteam = changeto;
player->playerstate = PST_REBORN;
// Reset away view (some code referenced from Got_Teamchange)
{
UINT8 i = 0;
INT32 *localplayertable = (splitscreen_partied[consoleplayer] ? splitscreen_party[consoleplayer] : g_localplayers);
for (i = 0; i < r_splitscreen; i++)
{
if (localplayertable[i] == (player-players))
{
LUAh_ViewpointSwitch(player, player, true);
displayplayers[i] = (player-players);
break;
}
}
}
// a surprise tool that will help us later...
if (changeto == 1)
text = va("\x82*%s switched to the %c%s%c team.\n", player_names[player-players], '\x85', "RED", '\x82');
else if (changeto == 2)
text = va("\x82*%s switched to the %c%s%c team.\n", player_names[player-players], '\x85', "BLU", '\x82');
else
text = va("\x82*%s entered the game.", player_names[player-players]);
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)
{
sector_t *sector = player->mo->subsector->sector;
postimg_t *type = NULL;
INT32 *param;
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->awayviewtics && player->awayviewmobj && !P_MobjWasRemoved(player->awayviewmobj))
{
sector = player->awayviewmobj->subsector->sector;
pviewheight = player->awayviewmobj->z + 20*FRACUNIT;
}
for (i = 0; i <= (unsigned)r_splitscreen; i++)
{
if (player == &players[displayplayers[i]])
{
type = &postimgtype[i];
param = &postimgparam[i];
break;
}
}
// 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->flags & FF_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->flags & FF_EXISTS) || !(rover->flags & FF_SWIMMABLE) || rover->flags & FF_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_IsLocalPlayer(player) && !demo.playback)
{
legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p
}
if (netgame && !player->bot && !bossinfo.boss)
{
CON_LogMessage(va(M_GetText("%s ran out of time.\n"), player_names[player-players]));
}
player->pflags |= PF_NOCONTEST;
if (G_GametypeUsesLives())
{
K_PlayerLoseLife(player);
}
if (player->mo)
{
S_StopSound(player->mo);
P_DamageMobj(player->mo, NULL, NULL, 1, DMG_TIMEOVER);
}
P_EndingMusic(player);
if (!exitcountdown)
exitcountdown = 5*TICRATE;
}
// 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
/* set follower state with our weird hacks
the reason we do this is to avoid followers ever using actions (majormods, yikes!)
without having to touch p_mobj.c.
so we give it 1more tic and change the state when tic == 1 instead of 0
cool beans?
cool beans.
*/
static void P_SetFollowerState(mobj_t *f, INT32 state)
{
if (!f || P_MobjWasRemoved(f))
return; // safety net
// No, do NOT set the follower to S_NULL. Set it to S_INVISIBLE.
if (state == S_NULL)
{
state = S_INVISIBLE;
f->threshold = 1; // Threshold = 1 means stop doing anything related to setting states, so that we don't get out of S_INVISIBLE
}
// extravalue2 stores the last "first state" we used.
// because states default to idlestates, if we use an animation that uses an "ongoing" state line, don't reset it!
// this prevents it from looking very dumb
if (state == f->extravalue2)
return;
// we will save the state into extravalue2.
f->extravalue2 = state;
P_SetMobjStateNF(f, state);
if (f->state->tics > 0)
f->tics++;
}
//
//P_HandleFollower
//
//Handle the follower's spawning and moving along with the player. Do note that some of the stuff like the removal if a player doesn't exist anymore is handled in MT_FOLLOWER's thinker.
static void P_HandleFollower(player_t *player)
{
follower_t fl;
angle_t an;
fixed_t zoffs;
fixed_t sx, sy, sz, deltaz;
UINT16 color;
fixed_t bubble; // bubble scale (0 if no bubble)
mobj_t *bmobj; // temp bubble mobj
if (!player->followerready)
return; // we aren't ready to perform anything follower related yet.
// How about making sure our follower exists and is added before trying to spawn it n' all?
if (player->followerskin > numfollowers-1 || player->followerskin < -1)
{
//CONS_Printf("Follower skin invlaid. Setting to -1.\n");
player->followerskin = -1;
return;
}
// don't do anything if we can't have a follower to begin with. (It gets removed under those conditions)
if (player->spectator)
return;
if (player->followerskin < 0)
return;
// Before we do anything, let's be sure of where we're supposed to be
fl = followers[player->followerskin];
an = player->mo->angle + (fl.atangle)*ANG1; // it's aproximative but it really doesn't matter in the grand scheme of things...
zoffs = (fl.zoffs)*FRACUNIT;
bubble = fl.bubblescale; // 0 if no bubble to spawn.
// do you like angle maths? I certainly don't...
sx = player->mo->x + FixedMul((player->mo->scale*fl.dist), FINECOSINE((an)>>ANGLETOFINESHIFT));
sy = player->mo->y + FixedMul((player->mo->scale*fl.dist), FINESINE((an)>>ANGLETOFINESHIFT));
// interp info helps with stretchy fix
deltaz = (player->mo->z - player->mo->old_z);
// for the z coordinate, don't be a doof like Steel and forget that MFE_VERTICALFLIP exists :P
sz = player->mo->z + FixedMul(player->mo->scale, zoffs)*P_MobjFlip(player->mo);
if (player->mo->eflags & MFE_VERTICALFLIP)
sz += fl.height*player->mo->scale;
// finally, add a cool floating effect to the z height.
// not stolen from k_kart I swear!!
{
const fixed_t pi = (22<<FRACBITS) / 7; // loose approximation, this doesn't need to be incredibly precise
fixed_t sine = fl.bobamp * FINESINE((((8*pi*(fl.bobspeed)) * leveltime)>>ANGLETOFINESHIFT) & FINEMASK);
sz += FixedMul(player->mo->scale, sine)*P_MobjFlip(player->mo);
}
// Set follower colour
switch (player->followercolor)
{
case FOLLOWERCOLOR_MATCH: // "Match"
color = player->skincolor;
break;
case FOLLOWERCOLOR_OPPOSITE: // "Opposite"
color = skincolors[player->skincolor].invcolor;
break;
default:
color = player->followercolor;
if (!color || color > MAXSKINCOLORS+2) // Make sure this isn't garbage
color = player->skincolor; // "Match" as fallback.
break;
}
if (!player->follower) // follower doesn't exist / isn't valid
{
//CONS_Printf("Spawning follower...\n");
// so let's spawn one!
P_SetTarget(&player->follower, P_SpawnMobj(sx, sy, sz, MT_FOLLOWER));
P_SetFollowerState(player->follower, fl.idlestate);
P_SetTarget(&player->follower->target, player->mo); // we need that to know when we need to disappear
P_InitAngle(player->follower, player->mo->angle);
// This is safe to only spawn it here, the follower is removed then respawned when switched.
if (bubble)
{
bmobj = P_SpawnMobj(player->follower->x, player->follower->y, player->follower->z, MT_FOLLOWERBUBBLE_FRONT);
P_SetTarget(&player->follower->hnext, bmobj);
P_SetTarget(&bmobj->target, player->follower); // Used to know if we have to despawn at some point.
bmobj = P_SpawnMobj(player->follower->x, player->follower->y, player->follower->z, MT_FOLLOWERBUBBLE_BACK);
P_SetTarget(&player->follower->hnext->hnext, bmobj); // this seems absolutely stupid, I know, but this will make updating the momentums/flags of these a bit easier.
P_SetTarget(&bmobj->target, player->follower); // Ditto
}
player->follower->extravalue1 = 0; // extravalue1 is used to know what "state set" to use.
/*
0 = idle
1 = forwards
2 = hurt
3 = win
4 = lose
5 = hitconfirm (< this one uses ->movecount as timer to know when to end, and goes back to normal states afterwards, unless hurt)
*/
}
else // follower exists, woo!
{
// Safety net (2)
if (P_MobjWasRemoved(player->follower))
{
P_SetTarget(&player->follower, NULL); // Remove this and respawn one, don't crash the game if Lua decides to P_RemoveMobj this thing.
return;
}
// first of all, handle states following the same model as above:
if (player->follower->tics == 1)
P_SetFollowerState(player->follower, player->follower->state->nextstate);
// move the follower next to us (yes, this is really basic maths but it looks pretty damn clean in practice)!
// 02/09/2021: cast lag to int32 otherwise funny things happen since it was changed to uint32 in the struct
player->follower->momx = (sx - player->follower->x)/ (INT32)fl.horzlag;
player->follower->momy = (sy - player->follower->y)/ (INT32)fl.horzlag;
player->follower->z += (deltaz/ (INT32)fl.vertlag);
player->follower->momz = (sz - player->follower->z)/ (INT32)fl.vertlag;
player->follower->angle = player->mo->angle;
if (player->mo->colorized)
player->follower->color = player->mo->color;
else
player->follower->color = color;
player->follower->colorized = player->mo->colorized;
P_SetScale(player->follower, FixedMul(fl.scale, player->mo->scale));
K_GenericExtraFlagsNoZAdjust(player->follower, player->mo); // Not K_MatchGenericExtraFlag because the Z adjust it has only works properly if master & mo have the same Z height.
// Match how the player is being drawn
player->follower->renderflags = player->mo->renderflags;
// Make the follower invisible if we no contest'd rather than removing it. No one will notice the diff seriously.
if (player->pflags & PF_NOCONTEST)
player->follower->renderflags |= RF_DONTDRAW;
// if we're moving let's make the angle the direction we're moving towards. This is to avoid drifting / reverse looking awkward.
player->follower->angle = K_MomentumAngle(player->follower);
// Finally, if the follower has bubbles, move them, set their scale, etc....
// This is what I meant earlier by it being easier, now we can just use this weird lil loop to get the job done!
bmobj = player->follower->hnext; // will be NULL if there's no bubble
while (bmobj && !P_MobjWasRemoved(bmobj))
{
// match follower's momentums and (e)flags(2).
bmobj->momx = player->follower->momx;
bmobj->momy = player->follower->momy;
bmobj->z += (deltaz/ (INT32)fl.vertlag);
bmobj->momz = player->follower->momz;
P_SetScale(bmobj, FixedMul(bubble, player->mo->scale));
K_GenericExtraFlagsNoZAdjust(bmobj, player->follower);
bmobj->renderflags = player->mo->renderflags;
if (player->follower->threshold) // threshold means the follower was "despawned" with S_NULL (is actually just set to S_INVISIBLE)
P_SetMobjState(bmobj, S_INVISIBLE); // sooooo... let's do the same!
bmobj = bmobj->hnext; // switch to other bubble layer or exit
}
if (player->follower->threshold)
return; // Threshold means the follower was "despanwed" with S_NULL.
// However with how the code is factored, this is just a special case of S_INVISBLE to avoid having to add other player variables.
// handle follower animations. Could probably be better...
// hurt or dead
if (player->spinouttimer || player->mo->state == &states[S_KART_SPINOUT] || player->mo->health <= 0)
{
player->follower->movecount = 0; // cancel hit confirm.
player->follower->angle = player->drawangle; // spin out
if (player->follower->extravalue1 != 2)
{
player->follower->extravalue1 = 2;
P_SetFollowerState(player->follower, fl.hurtstate);
}
if (player->mo->health <= 0) // if dead, follow the player's z momentum exactly so they both look like they die at the same speed.
player->follower->momz = player->mo->momz;
}
else if (player->follower->movecount)
{
if (player->follower->extravalue1 != 5)
{
player->follower->extravalue1 = 5;
P_SetFollowerState(player->follower, fl.hitconfirmstate);
}
player->follower->movecount--;
}
else if (player->speed > 10*player->mo->scale) // animation for moving fast enough
{
if (player->follower->extravalue1 != 1)
{
player->follower->extravalue1 = 1;
P_SetFollowerState(player->follower, fl.followstate);
}
}
else // animations when nearly still. This includes winning and losing.
{
if (player->follower->extravalue1 != 0)
{
if (player->exiting) // win/ loss animations
{
if (K_IsPlayerLosing(player)) // L
{
if (player->follower->extravalue1 != 4)
{
player->follower->extravalue1 = 4;
P_SetFollowerState(player->follower, fl.losestate);
}
}
else // W
{
if (player->follower->extravalue1 != 3)
{
player->follower->extravalue1 = 3;
P_SetFollowerState(player->follower, fl.winstate);
}
}
}
else // normal standstill
{
player->follower->extravalue1 = 0;
P_SetFollowerState(player->follower, fl.idlestate);
}
}
}
}
}
/* 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 = 5*FRACUNIT;
}
else
{
tilt = ANGLE_11hh/2;
lowb = 15*FRACUNIT;
}
}
else
{
tilt = ANGLE_22h;
lowb = 10*FRACUNIT;
}
moma = FixedMul(FixedDiv(delta, ANGLE_90), tilt);
speed = abs( player->mo->momx + player->mo->momy );
if (speed < lowb)
{
// ease out tilt as we slow...
moma = FixedMul(moma, FixedDiv(speed, lowb));
}
return moma;
}
static void
DoABarrelRoll (player_t *player)
{
angle_t slope;
angle_t delta;
fixed_t smoothing;
if (player->respawn.state != RESPAWNST_NONE)
{
player->tilt = 0;
return;
}
if (player->exiting)
{
return;
}
slope = InvAngle(R_GetPitchRollAngle(player->mo, player));
if (AbsAngle(slope) < ANGLE_11hh)
{
slope = 0;
}
if (AbsAngle(slope) > ANGLE_45)
{
slope = slope & ANGLE_180 ? InvAngle(ANGLE_45) : ANGLE_45;
}
slope -= Quaketilt(player);
delta = slope - player->tilt;
smoothing = FixedDiv(AbsAngle(slope), ANGLE_45);
delta = FixedDiv(delta, 33 *
FixedDiv(FRACUNIT, FRACUNIT + smoothing));
if (delta)
player->tilt += delta;
else
player->tilt = slope;
}
//
// 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;
}
player->old_drawangle = player->drawangle;
player->old_viewrollangle = player->viewrollangle;
player->pflags &= ~PF_HITFINISHLINE;
if (player->awayviewmobj && P_MobjWasRemoved(player->awayviewmobj))
{
P_SetTarget(&player->awayviewmobj, NULL); // remove awayviewmobj asap if invalid
player->awayviewtics = 0; // reset to zero
}
// Run followes here. We need them to run even when we're dead to follow through what we're doing.
P_HandleFollower(player);
if (player->flashcount)
player->flashcount--;
if (player->awayviewtics && player->awayviewtics != -1)
player->awayviewtics--;
if (player->mo->hitlag > 0)
{
return;
}
// Track airtime
if (P_IsObjectOnGround(player->mo))
{
if (!P_PlayerInPain(player))
player->airtime = 0;
}
else
{
player->airtime++;
}
cmd = &player->cmd;
// SRB2kart
// Save the dir the player is holding
// to allow items to be thrown forward or backward.
if (cmd->buttons & BT_FORWARD)
{
player->throwdir = 1;
}
else if (cmd->buttons & BT_BACKWARD)
{
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->pflags & PF_ACCELDOWN))
{
player->kickstartaccel = 0;
}
else if (player->kickstartaccel < ACCEL_KICKSTART)
{
player->kickstartaccel++;
if ((player->kickstartaccel == ACCEL_KICKSTART) && P_IsLocalPlayer(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)
{
INT32 i;
// Check if all the players in the race have finished. If so, end the level.
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && !players[i].spectator)
{
if (!players[i].exiting && !(players[i].pflags & PF_NOCONTEST) && players[i].lives > 0)
break;
}
}
if (i == MAXPLAYERS && player->exiting == raceexittime+2) // finished
player->exiting = raceexittime+1;
#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_IsLocalPlayer(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)
{
LUAh_PlayerThink(player);
return;
}
}
}
// If it is set, start subtracting
// Don't allow it to go back to 0
if (player->exiting > 1 && (player->exiting < raceexittime+2 || !(gametyperules & GTR_CIRCUIT))) // SRB2kart - "&& player->exiting > 1"
player->exiting--;
if (player->exiting && exitcountdown)
player->exiting = 99; // SRB2kart
if (player->exiting == 2 || exitcountdown == 2)
{
if (server)
{
SendNetXCmd(XD_EXITLEVEL, NULL, 0);
}
}
}
// 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);
else if (
#else
if (player->spectator &&
#endif
(gametyperules & GTR_LIVES))
{
/*P_ConsiderAllGone()*/;
}
if (player->playerstate == PST_DEAD)
{
if (player->spectator)
player->mo->renderflags |= RF_GHOSTLY;
else
player->mo->renderflags &= ~RF_GHOSTLYMASK;
P_DeathThink(player);
LUAh_PlayerThink(player);
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 (!(exitcountdown && !racecountdown) && !(player->exiting || mapreset) && !(player->pflags & PF_NOCONTEST) && !stoppedclock)
{
if (leveltime >= starttime)
{
player->realtime = leveltime - starttime;
if (player == &players[consoleplayer])
{
if (player->spectator || !circuitmap)
curlap = 0;
else if (curlap != UINT32_MAX)
curlap++; // This is too complicated to sync to realtime, just sorta hope for the best :V
}
}
else
{
player->realtime = 0;
if (player == &players[consoleplayer])
curlap = 0;
}
}
if ((netgame || multiplayer) && player->spectator && 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;
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->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT)
{
P_DoZoomTube(player);
player->rmomx = player->rmomy = 0;
}
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
// check for buttons
if (cmd->buttons & BT_ACCELERATE)
player->pflags |= PF_ACCELDOWN;
else
player->pflags &= ~PF_ACCELDOWN;
if (cmd->buttons & BT_BRAKE)
player->pflags |= PF_BRAKEDOWN;
else
player->pflags &= ~PF_BRAKEDOWN;
// PF_LOOKDOWN handled in K_KartMoveAnimation
// 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->nocontrol && player->nocontrol < UINT16_MAX)
{
if (!(--player->nocontrol))
player->pflags &= ~PF_FAULT;
}
else
player->nocontrol = 0;
// Flash player after being hit.
if (!(player->hyudorotimer // SRB2kart - fixes Hyudoro not flashing when it should.
|| player->growshrinktimer > 0 // Grow doesn't flash either.
|| (player->respawn.state != RESPAWNST_NONE) // Respawn timer (for drop dash effect)
|| (player->pflags & PF_NOCONTEST) // NO CONTEST explosion
|| ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0 && player->karmadelay)))
{
if (player->flashing > 1 && player->flashing < K_GetKartFlashing(player)
&& (leveltime & 1))
player->mo->renderflags |= RF_DONTDRAW;
else
player->mo->renderflags &= ~RF_DONTDRAW;
}
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;
}
if (player->stairjank > 0)
{
player->stairjank--;
}
K_KartPlayerThink(player, cmd); // SRB2kart
DoABarrelRoll(player);
if (player->carry == CR_SLIDING)
player->carry = CR_NONE;
LUAh_PlayerThink(player);
}
//
// P_PlayerAfterThink
//
// Thinker for player after all other thinkers have run
//
void P_PlayerAfterThink(player_t *player)
{
camera_t *thiscam = NULL; // if not one of the displayed players, just don't bother
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
#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
for (i = 0; i <= r_splitscreen; i++)
{
if (player == &players[displayplayers[i]])
{
thiscam = &camera[i];
break;
}
}
if (player->playerstate == PST_DEAD)
{
if (player->followmobj)
{
P_RemoveMobj(player->followmobj);
P_SetTarget(&player->followmobj, NULL);
}
return;
}
if (thiscam)
{
if (!thiscam->chase) // bob view only if looking through the player's eyes
{
P_CalcHeight(player);
P_CalcPostImg(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;
}
}
// spectator invisibility and nogravity.
if ((netgame || multiplayer) && player->spectator)
{
player->mo->renderflags |= RF_DONTDRAW;
player->mo->flags |= MF_NOGRAVITY;
}
if (P_IsObjectOnGround(player->mo))
player->mo->pmomz = 0;
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 (LUAh_FollowMobj(player, player->followmobj) || P_MobjWasRemoved(player->followmobj))
{;}
else
{
switch (player->followmobj->type)
{
default:
var1 = 1;
var2 = 0;
A_CapeChase(player->followmobj);
break;
}
}
}
}
}
void P_SetPlayerAngle(player_t *player, angle_t angle)
{
P_ForceLocalAngle(player, angle);
player->angleturn = angle;
}
angle_t P_GetLocalAngle(player_t *player)
{
// this function is from vanilla srb2. can you tell?
// (hint: they have separate variables for all of this shit instead of arrays)
UINT8 i;
for (i = 0; i <= r_splitscreen; i++)
{
if (player == &players[displayplayers[i]])
return localangle[i];
}
return 0;
}
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;
break;
}
}
}
boolean P_PlayerFullbright(player_t *player)
{
return (player->invincibilitytimer > 0);
}