// 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_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 "k_color.h" // KartColor_Opposite #include "console.h" // CON_LogMessage #include "k_respawn.h" #include "k_bot.h" #include "k_grandprix.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)) { 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->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 */ 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->climbing || p->powers[pw_tailsfly] || (p->pflags & PF_JUMPED) || (p->pflags & PF_SPINNING)); } // 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].powers[pw_carry] = CR_NONE; P_SetTarget(&players[i].mo->tracer, emmo); if (pnum == 255) { pnum = i; continue; } if (i == pnum) continue; emmo->flags2 |= MFD_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); flag->angle = angle; angle += FixedAngle(120*FRACUNIT); P_SetTarget(&flag->target, player->mo); } } #if 0 // // P_ResetScore // // This is called when your chain is reset. void P_ResetScore(player_t *player) { // Formally a host for Chaos mode behavior player->scoreadd = 0; } #endif // // 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 == 255) lowest = players[i].laps; else if (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 pw_flashing, if both found return true // boolean P_PlayerInPain(player_t *player) { // no silly, sliding isn't pain if (!(player->pflags & PF_SLIDING) && player->mo->state == &states[player->mo->info->painstate] && player->powers[pw_flashing]) return true; if (player->mo->state == &states[S_KART_SQUISH]) 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->powers[pw_carry] = CR_NONE; player->onconveyor = 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. // void P_GivePlayerRings(player_t *player, INT32 num_rings) { if (!player->mo) return; if ((gametyperules & GTR_BUMPERS)) // No rings in Battle Mode return; player->kartstuff[k_rings] += num_rings; //player->totalring += num_rings; // Used for GP lives later if (player->rings > 20) player->rings = 20; // Caps at 20 rings, sorry! else if (player->rings < -20) player->rings = -20; // Chaotix ring debt! } // // 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) { //UINT32 oldscore; if (!((gametyperules & GTR_BUMPERS))) return; if (player->exiting) // srb2kart return; //oldscore = player->score; // Don't go above MAXSCORE. if (player->marescore + amount < MAXSCORE) player->marescore += amount; else player->marescore = MAXSCORE; // In team match, all awarded points are incremented to the team's running score. if (gametype == GT_TEAMMATCH) { if (player->ctfteam == 1) redscore += amount; else if (player->ctfteam == 2) bluescore += amount; } } // // P_PlayLivesJingle // void P_PlayLivesJingle(player_t *player) { if (player && !P_IsLocalPlayer(player)) return; if (use1upSound || cv_1upsound.value) S_StartSound(NULL, sfx_oneup); else { P_PlayJingle(player, JT_1UP); if (player) player->powers[pw_extralife] = extralifetics + 1; 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 = (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 = (players[i].powers[pw_underwater] && players[i].powers[pw_underwater] <= 11*TICRATE + 1); break; case JT_SUPER: // Super Sonic result = (players[i].powers[pw_super] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC)); break; case JT_GOVER: // Game Over result = (players[i].lives <= 0); break; case JT_NIGHTSTIMEOUT: // NiGHTS Time Out (10 seconds) case JT_SSTIMEOUT: result = (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->kartstuff[k_itemtype]) != KSHIELD_NONE) S_StartSound(source, sfx_s1a3); // Shield hit (no ring loss) else if (source->player && source->player->kartstuff[k_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; 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 if (r_splitscreen) { if (!((players[displayplayers[0]].exiting || (players[displayplayers[0]].pflags & PF_GAMETYPEOVER)) || (players[displayplayers[1]].exiting || (players[displayplayers[1]].pflags & PF_GAMETYPEOVER)) || ((r_splitscreen < 2) && (players[displayplayers[2]].exiting || (players[displayplayers[2]].pflags & PF_GAMETYPEOVER))) || ((r_splitscreen < 3) && (players[displayplayers[3]].exiting || (players[displayplayers[3]].pflags & PF_GAMETYPEOVER))))) return false; bestlocalplayer = &players[displayplayers[0]]; bestlocalpos = ((players[displayplayers[0]].pflags & PF_GAMETYPEOVER) ? MAXPLAYERS+1 : players[displayplayers[0]].kartstuff[k_position]); #define setbests(p) \ if (((players[p].pflags & PF_GAMETYPEOVER) ? MAXPLAYERS+1 : players[p].kartstuff[k_position]) < bestlocalpos) \ { \ bestlocalplayer = &players[p]; \ bestlocalpos = ((players[p].pflags & PF_GAMETYPEOVER) ? MAXPLAYERS+1 : players[p].kartstuff[k_position]); \ } setbests(displayplayers[1]); if (r_splitscreen > 1) setbests(displayplayers[2]); if (r_splitscreen > 2) setbests(displayplayers[3]); #undef setbests } else { if (!(player->exiting || (player->pflags & PF_GAMETYPEOVER))) return false; bestlocalplayer = player; bestlocalpos = ((player->pflags & PF_GAMETYPEOVER) ? MAXPLAYERS+1 : player->kartstuff[k_position]); } 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 (leveltime < (starttime + (TICRATE/2))) S_ChangeMusicInternal((encoremode ? "estart" : "kstart"), false); //S_StopMusic(); else // see also where time overs are handled - search for "lives = 2" in this file { INT32 wantedmus = 0; // 0 is level music, 1 is invincibility, 2 is grow if (r_splitscreen) { INT32 bestlocaltimer = 1; #define setbests(p) \ if (players[p].playerstate == PST_LIVE) \ { \ if (players[p].kartstuff[k_invincibilitytimer] > bestlocaltimer) \ { wantedmus = 1; bestlocaltimer = players[p].kartstuff[k_invincibilitytimer]; } \ else if (players[p].kartstuff[k_growshrinktimer] > bestlocaltimer) \ { wantedmus = 2; bestlocaltimer = players[p].kartstuff[k_growshrinktimer]; } \ } setbests(displayplayers[0]); setbests(displayplayers[1]); if (r_splitscreen > 1) setbests(displayplayers[2]); if (r_splitscreen > 2) setbests(displayplayers[3]); #undef setbests } else { if (player->playerstate == PST_LIVE) { if (player->kartstuff[k_invincibilitytimer] > 1) wantedmus = 1; else if (player->kartstuff[k_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) && !(mo->player && mo->player->pflags & PF_BOUNCING)) { /* // 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_IsLocalPlayer // // Returns true if player is // on the local machine. // boolean P_IsLocalPlayer(player_t *player) { UINT8 i; for (i = 0; i <= r_splitscreen; i++) // DON'T skip P1 { if (player == &players[g_localplayers[i]]) return true; } return false; } // // 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_SpawnShieldOrb // // Spawns the shield orb on the player // depending on which shield they are // supposed to have. // void P_SpawnShieldOrb(player_t *player) { mobjtype_t orbtype; thinker_t *th; mobj_t *shieldobj, *ov; #ifdef PARANOIA if (!player->mo) I_Error("P_SpawnShieldOrb: player->mo is NULL!\n"); #endif // SRB2Kart // TODO: Make our shields use this system if (LUAh_ShieldSpawn(player)) return; if (player->powers[pw_shield] & SH_FORCE) orbtype = MT_FORCE_ORB; else switch (player->powers[pw_shield] & SH_NOSTACK) { case SH_WHIRLWIND: orbtype = MT_WHIRLWIND_ORB; break; case SH_ATTRACT: orbtype = MT_ATTRACT_ORB; break; case SH_ELEMENTAL: orbtype = MT_ELEMENTAL_ORB; break; case SH_ARMAGEDDON: orbtype = MT_ARMAGEDDON_ORB; break; case SH_PITY: case SH_PINK: // PITY IN PINK orbtype = MT_PITY_ORB; break; case SH_FLAMEAURA: orbtype = MT_FLAMEAURA_ORB; break; case SH_BUBBLEWRAP: orbtype = MT_BUBBLEWRAP_ORB; break; case SH_THUNDERCOIN: orbtype = MT_THUNDERCOIN_ORB; break; default: return; } // blaze through the thinkers to see if an orb already exists! for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) continue; shieldobj = (mobj_t *)th; if (shieldobj->type == orbtype && shieldobj->target == player->mo) P_RemoveMobj(shieldobj); //kill the old one(s) } shieldobj = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, orbtype); shieldobj->flags2 |= MF2_SHIELD; P_SetTarget(&shieldobj->target, player->mo); if ((player->powers[pw_shield] & SH_NOSTACK) == SH_PINK) { shieldobj->color = SKINCOLOR_PINK; shieldobj->colorized = true; } else shieldobj->color = (UINT16)shieldobj->info->painchance; shieldobj->threshold = (player->powers[pw_shield] & SH_FORCE) ? SH_FORCE : (player->powers[pw_shield] & SH_NOSTACK); if (shieldobj->info->seestate) { ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY); P_SetTarget(&ov->target, shieldobj); P_SetMobjState(ov, shieldobj->info->seestate); P_SetTarget(&shieldobj->tracer, ov); } if (shieldobj->info->meleestate) { ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY); P_SetTarget(&ov->target, shieldobj); P_SetMobjState(ov, shieldobj->info->meleestate); } if (shieldobj->info->missilestate) { ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY); P_SetTarget(&ov->target, shieldobj); P_SetMobjState(ov, shieldobj->info->missilestate); } if (player->powers[pw_shield] & SH_FORCE) { //Copy and pasted from P_ShieldLook in p_mobj.c shieldobj->movecount = (player->powers[pw_shield] & SH_FORCEHP); if (shieldobj->movecount < 1) { if (shieldobj->info->painstate) P_SetMobjState(shieldobj,shieldobj->info->painstate); else shieldobj->drawflags |= MFD_SHADOW; } } } // // P_SwitchShield // // Handles the possibility of switching between // the non-stack layer of shields thoroughly, // then adds the desired one. // void P_SwitchShield(player_t *player, UINT16 shieldtype) { boolean donthavealready; // If you already have a bomb shield, use it! if ((shieldtype == SH_ARMAGEDDON) && (player->powers[pw_shield] & SH_NOSTACK) == SH_ARMAGEDDON) P_BlackOw(player); donthavealready = (shieldtype & SH_FORCE) ? (!(player->powers[pw_shield] & SH_FORCE) || (player->powers[pw_shield] & SH_FORCEHP) < (shieldtype & ~SH_FORCE)) : ((player->powers[pw_shield] & SH_NOSTACK) != shieldtype); if (donthavealready) { boolean stopshieldability = (shieldtype & SH_FORCE) ? (!(player->powers[pw_shield] & SH_FORCE)) : true; // Just in case. if (stopshieldability && player->pflags & PF_SHIELDABILITY) { player->pflags &= ~(PF_SPINNING|PF_SHIELDABILITY); // They'll still have PF_THOKKED... player->homing = 0; } player->powers[pw_shield] = shieldtype|(player->powers[pw_shield] & SH_STACK); P_SpawnShieldOrb(player); if (shieldtype & SH_PROTECTWATER) { if (player->powers[pw_underwater] && player->powers[pw_underwater] <= 12*TICRATE + 1) { player->powers[pw_underwater] = 0; P_RestoreMusic(player); } else player->powers[pw_underwater] = 0; if (player->powers[pw_spacetime] > 1) { player->powers[pw_spacetime] = 0; P_RestoreMusic(player); } } } } // // 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_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->sprite = mobj->sprite; ghost->sprite2 = mobj->sprite2; ghost->frame = mobj->frame; ghost->tics = -1; ghost->drawflags |= tr_trans50 << MFD_TRANSSHIFT; ghost->fuse = ghost->info->damage; ghost->skin = mobj->skin; ghost->standingslope = mobj->standingslope; #ifdef HWRENDER ghost->modeltilt = mobj->modeltilt; #endif ghost->sprxoff = mobj->sprxoff; ghost->spryoff = mobj->spryoff; ghost->sprzoff = mobj->sprzoff; 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); } 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->powers[pw_underwater] = 0; player->powers[pw_spacetime] = 0; 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 dorollstuff) { boolean clipmomz; I_Assert(player->mo != NULL); clipmomz = !(P_CheckDeathPitCollide(player->mo)); // SRB2Kart: removed lots of really vanilla-specific code here 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 it's an FF_SPINBUST, you can break it if you are in your spinning frames // (either from jumping or spindashing). if (rover->flags & FF_SPINBUST && (((player->pflags & PF_SPINNING) && !(player->pflags & PF_STARTDASH)) || (player->pflags & PF_JUMPED && !(player->pflags & PF_NOJUMPDAMAGE)))) goto bust; // You can always break it if you have CA_GLIDEANDCLIMB // or if you are bouncing on it // or you are using CA_TWINSPIN/CA2_MELEE. if (player->charability == CA_GLIDEANDCLIMB || (player->pflags & PF_BOUNCING) || ((player->charability == CA_TWINSPIN) && (player->panim == PA_ABILITY)) || (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)) goto bust; if (rover->flags & FF_STRONGBUST) continue; // If it's not an FF_STRONGBUST, you can break if you are spinning (and not jumping) // or you are super // or you are in dashmode with SF_DASHMODE // or you are drilling in NiGHTS // or you are recording for Metal Sonic if (!((player->pflags & PF_SPINNING) && !(player->pflags & PF_JUMPED)) && !(player->powers[pw_super]) && !(((player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE)) && (player->dashmode >= DASHMODE_THRESHOLD)) && !(player->pflags & PF_DRILLING) && !metalrecording) 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); if (((player->charability == CA_TWINSPIN) && (player->panim == PA_ABILITY)) || ((P_MobjFlip(player->mo)*player->mo->momz < 0) && (player->pflags & PF_BOUNCING || ((player->charability2 == CA2_MELEE) && (player->panim == PA_ABILITY2))))) { topheight -= player->mo->momz; bottomheight -= player->mo->momz; } // 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_BOUNCY)) { 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; if (player->pflags & PF_SPINNING) { player->pflags &= ~PF_SPINNING; player->pflags |= P_GetJumpFlags(player); player->pflags |= PF_THOKKED; } } else { player->mo->momx = -FixedMul(player->mo->momx,linedist); player->mo->momy = -FixedMul(player->mo->momy,linedist); if (player->pflags & PF_SPINNING) { player->pflags &= ~PF_SPINNING; player->pflags |= P_GetJumpFlags(player); player->pflags |= PF_THOKKED; } } if ((player->pflags & PF_SPINNING) && player->speed < FixedMul(1<mo->scale) && player->mo->momz) { player->pflags &= ~PF_SPINNING; player->pflags |= P_GetJumpFlags(player); } 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->powers[pw_invulnerability] && !player->kartstuff[k_invincibilitytimer]) return; player->mo->color = (UINT16)(SKINCOLOR_PINK + (leveltime % (numskincolors - SKINCOLOR_PINK))); // Resume normal music stuff. if (player->powers[pw_invulnerability] == 1 || player->kartstuff[k_invincibilitytimer] == 1) { if (!player->powers[pw_super]) { player->mo->color = player->skincolor; G_GhostAddColor((INT32) (player - players), GHC_NORMAL); } } } // // 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->powers[pw_shield] & SH_PROTECTWATER) || player->spectator) return; if (player->charflags & SF_MACHINE) { if (player->powers[pw_underwater] && P_RandomChance((128-(player->powers[pw_underwater]/4))*FRACUNIT/256)) { fixed_t r = player->mo->radius>>FRACBITS; x += (P_RandomRange(r, -r)<mo->height>>FRACBITS)<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) { ticcmd_t *cmd; angle_t movepushangle; // Analog //INT32 topspeed, acceleration, thrustfactor; fixed_t movepushforward = 0; angle_t dangle; // replaces old quadrants bits //boolean dangleflip = false; // SRB2kart - toaster //fixed_t normalspd = FixedMul(player->normalspeed, player->mo->scale); fixed_t oldMagnitude, newMagnitude; #ifdef ESLOPE 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 #endif // ESLOPE // 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); cmd = &player->cmd; if (player->kartstuff[k_drift] != 0) movepushangle = player->mo->angle-(ANGLE_45/5)*player->kartstuff[k_drift]; else if (player->kartstuff[k_spinouttimer] || player->kartstuff[k_wipeoutslow]) // if spun out, use the boost angle movepushangle = (angle_t)player->kartstuff[k_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); //dangleflip = true; } // anything else will leave both at 0, so no need to do anything else //{ SRB2kart 220217 - Toaster Code for misplaced thrust /* if (!player->kartstuff[k_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); } */ //} // Do not let the player control movement if not onground. // SRB2Kart: pogo spring and speed bumps are supposed to control like you're on the ground onground = (P_IsObjectOnGround(player->mo) || (player->kartstuff[k_pogospring])); player->aiming = cmd->aiming<mo->movefactor != FRACUNIT) // Friction-scaled acceleration... movepushforward = FixedMul(movepushforward, player->mo->movefactor); totalthrust.x += P_ReturnThrustX(player->mo, movepushangle, movepushforward); totalthrust.y += P_ReturnThrustY(player->mo, movepushangle, movepushforward); } else if (!(player->kartstuff[k_spinouttimer])) { K_MomentumToFacing(player); } 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); } } } if (K_PlayerUsesBotMovement(player)) { K_MomentumToFacing(player); } 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_SpectatorMovement // // Control for spectators in multiplayer // static void P_SpectatorMovement(player_t *player) { ticcmd_t *cmd = &player->cmd; player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */); ticruned++; if (!(cmd->angleturn & 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; // Aiming needed for SEENAMES, etc. // We may not need to fire as a spectator, but this is still handy! player->aiming = cmd->aiming<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->powers[pw_nocontrol]) { player->pflags |= PF_STASIS; if (!(player->powers[pw_nocontrol] & (1<<15))) player->pflags |= PF_JUMPSTASIS; } // note: don't unset stasis here if (player->spectator) { player->mo->eflags &= ~MFE_VERTICALFLIP; // deflip... P_SpectatorMovement(player); return; } ////////////////////// // MOVEMENT CODE // ////////////////////// { INT16 angle_diff, max_left_turn, max_right_turn; boolean add_delta = true; // Kart: store the current turn range for later use player->lturn_max[leveltime%MAXPREDICTTICS] = K_GetKartTurnValue(player, KART_FULLTURN)+1; player->rturn_max[leveltime%MAXPREDICTTICS] = K_GetKartTurnValue(player, -KART_FULLTURN)-1; if (leveltime >= introtime) { // KART: Don't directly apply angleturn! It may have been either A) forged by a malicious client, or B) not be a smooth turn due to a player dropping frames. // Instead, turn the player only up to the amount they're supposed to turn accounting for latency. Allow exactly 1 extra turn unit to try to keep old replays synced. angle_diff = cmd->angleturn - (player->mo->angle>>16); max_left_turn = player->lturn_max[(leveltime + MAXPREDICTTICS - cmd->latency) % MAXPREDICTTICS]; max_right_turn = player->rturn_max[(leveltime + MAXPREDICTTICS - cmd->latency) % MAXPREDICTTICS]; //CONS_Printf("----------------\nangle diff: %d - turning options: %d to %d - ", angle_diff, max_left_turn, max_right_turn); if (angle_diff > max_left_turn) angle_diff = max_left_turn; else if (angle_diff < max_right_turn) angle_diff = max_right_turn; else { // Try to keep normal turning as accurate to 1.0.1 as possible to reduce replay desyncs. player->mo->angle = cmd->angleturn<<16; add_delta = false; } //CONS_Printf("applied turn: %d\n", angle_diff); if (add_delta) { player->mo->angle += angle_diff<<16; player->mo->angle &= ~0xFFFF; // Try to keep the turning somewhat similar to how it was before? //CONS_Printf("leftover turn (%s): %5d or %4d%%\n", // player_names[player-players], // (INT16) (cmd->angleturn - (player->mo->angle>>16)), // (INT16) (cmd->angleturn - (player->mo->angle>>16)) * 100 / (angle_diff ? angle_diff : 1)); } } ticruned++; if ((cmd->angleturn & TICCMD_RECEIVED) == 0) ticmiss++; P_3dMovement(player); } // Kart frames if (player->kartstuff[k_squishedtimer] > 0) { if (player->mo->state != &states[S_KART_SQUISH]) P_SetPlayerMobjState(player->mo, S_KART_SQUISH); } else if (player->pflags & PF_SLIDING) { if (player->mo->state != &states[S_KART_SPIN]) P_SetPlayerMobjState(player->mo, S_KART_SPIN); player->frameangle -= ANGLE_22h; } else if (player->kartstuff[k_spinouttimer] > 0) { INT32 speed = max(1, min(8, player->kartstuff[k_spinouttimer]/8)); if (player->mo->state != &states[S_KART_SPIN]) P_SetPlayerMobjState(player->mo, S_KART_SPIN); if (speed == 1 && abs((signed)(player->mo->angle - player->frameangle)) < ANGLE_22h) player->frameangle = player->mo->angle; // Face forward at the end of the animation else player->frameangle -= (ANGLE_11hh * speed); } else if (player->powers[pw_nocontrol] && player->pflags & PF_SKIDDOWN) { if (player->mo->state != &states[S_KART_SPIN]) P_SetPlayerMobjState(player->mo, S_KART_SPIN); if (((player->powers[pw_nocontrol] + 5) % 20) < 10) player->frameangle += ANGLE_11hh; else player->frameangle -= ANGLE_11hh; } else { K_KartMoveAnimation(player); if (player->kartstuff[k_pogospring]) { player->frameangle += ANGLE_22h; } else { player->frameangle = player->mo->angle; if (player->kartstuff[k_drift] != 0) { INT32 a = (ANGLE_45 / 5) * player->kartstuff[k_drift]; player->frameangle += a; } } } player->mo->movefactor = FRACUNIT; // We're not going to do any more with this, so let's change it back for the next frame. //{ SRB2kart // Drifting sound // Start looping the sound now. if (leveltime % 50 == 0 && onground && player->kartstuff[k_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->kartstuff[k_drift] != 0) S_StartSound(player->mo, sfx_drift); // Ok, we'll stop now. else if (player->kartstuff[k_drift] == 0) S_StopSoundByID(player->mo, sfx_drift); K_MoveKartPlayer(player, onground); //} ////////////////// //GAMEPLAY STUFF// ////////////////// // If you're running fast enough, you can create splashes as you run in shallow water. #if 0 if (!player->climbing && ((!(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->pflags & PF_STARTDASH)) && leveltime % (TICRATE/7) == 0 && player->mo->momz == 0 && !(player->pflags & PF_SLIDING) && !player->spectator) { mobjtype_t splishtype = (player->mo->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH; mobj_t *water = P_SpawnMobj(player->mo->x - P_ReturnThrustX(NULL, player->mo->angle, player->mo->radius), player->mo->y - P_ReturnThrustY(NULL, player->mo->angle, player->mo->radius), ((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[splishtype].height, player->mo->scale) : player->mo->watertop), splishtype); if (player->mo->eflags & MFE_GOOWATER) S_StartSound(water, sfx_ghit); else if (player->mo->eflags & MFE_TOUCHLAVA) S_StartSound(water, sfx_splash); else S_StartSound(water, sfx_wslap); if (player->mo->eflags & MFE_VERTICALFLIP) { water->flags2 |= MF2_OBJECTFLIP; water->eflags |= MFE_VERTICALFLIP; } water->destscale = player->mo->scale; P_SetScale(water, player->mo->scale); } #endif if (!player->climbing && ((!(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->pflags & PF_STARTDASH)) && player->mo->momz == 0 && !(player->pflags & PF_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 = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); const fixed_t playerVisualRadius = player->mo->radius + 8*FRACUNIT; 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); water->angle = forwardangle - ANGLE_180 - ANGLE_22h; water->destscale = trailScale; 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); water->angle = forwardangle - ANGLE_180 - ANGLE_22h; water->destscale = trailScale; 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); water->angle = forwardangle - ANGLE_180 + ANGLE_22h; water->destscale = trailScale; 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); water->angle = forwardangle - ANGLE_180 + ANGLE_22h; water->destscale = trailScale; 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->kartstuff[k_sneakertimer] || player->kartstuff[k_flamedash]) && onground && (leveltime & 1)) K_SpawnBoostTrail(player); if (player->kartstuff[k_invincibilitytimer] > 0) K_SpawnSparkleTrail(player->mo); if (player->kartstuff[k_wipeoutslow] > 1 && (leveltime & 1)) K_SpawnWipeoutTrail(player->mo, false); K_DriftDustHandling(player->mo); // Crush test... if ((player->mo->ceilingz - player->mo->floorz < player->mo->height) && !(player->mo->flags & MF_NOCLIP)) { if ((netgame || multiplayer) && player->spectator) P_DamageMobj(player->mo, NULL, NULL, 1, DMG_SPECTATOR); // Respawn crushed spectators else { K_SquishPlayer(player, NULL, NULL); // SRB2kart - we don't kill when squished, we squish when squished. // 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->powers[pw_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_GetPreviousWaypoint(player->mo->tracer, false) : P_GetNextWaypoint(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->powers[pw_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); } if (player->mo->state != &states[S_KART_SPIN]) P_SetPlayerMobjState(player->mo, S_KART_SPIN); player->frameangle -= 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, 0); } 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) { const fixed_t ns = 60 * mapobjectscale; mobj_t *mo; angle_t fa; thinker_t *think; INT32 i; radius = FixedMul(radius, mapobjectscale); for (i = 0; i < 16; i++) { fa = (i*(FINEANGLES/16)); mo = P_SpawnMobj(inflictor->x, inflictor->y, inflictor->z, MT_SUPERSPARK); if (!P_MobjWasRemoved(mo)) { mo->momx = FixedMul(FINESINE(fa),ns); mo->momy = FixedMul(FINECOSINE(fa),ns); } } 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->flags & MF_BOSS || mo->type == MT_PLAYER) //don't OHKO bosses nor players! P_DamageMobj(mo, inflictor, source, 1, DMG_NUKE); else P_DamageMobj(mo, inflictor, source, 1000, DMG_NUKE); } } // // 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) { INT32 j = MAXPLAYERS; ticcmd_t *cmd = &player->cmd; player->deltaviewheight = 0; if (player->deadtimer < INT32_MAX) player->deadtimer++; if (player->bot) // don't allow bots to do any of the below, B_CheckRespawn does all they need for respawning already goto notrealplayer; if ((player->pflags & PF_GAMETYPEOVER) && (gametyperules & GTR_CIRCUIT)) { player->karthud[khud_timeovercam]++; if (player->mo) { player->mo->flags |= (MF_NOGRAVITY|MF_NOCLIP); player->mo->drawflags |= MFD_DONTDRAW; } } else player->karthud[khud_timeovercam] = 0; K_KartPlayerHUDUpdate(player); if (player->lives > 0 && !(player->pflags & PF_GAMETYPEOVER) && player->deadtimer > TICRATE) { player->playerstate = PST_REBORN; } // Keep time rolling if (!(exitcountdown && !racecountdown) && !(player->exiting || mapreset) && !(player->pflags & PF_GAMETYPEOVER)) { if (leveltime >= starttime) { player->realtime = leveltime - starttime; if (player == &players[consoleplayer]) { if (player->spectator || !circuitmap) curlap = 0; else 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; } } notrealplayer: 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.value < 0) CV_SetValue(&cv_cam_rotate, cv_cam_rotate.value + 360); else if (cv_cam_rotate.value > 359) CV_SetValue(&cv_cam_rotate, cv_cam_rotate.value % 360); } static void CV_CamRotate2_OnChange(void) { if (cv_cam2_rotate.value < 0) CV_SetValue(&cv_cam2_rotate, cv_cam2_rotate.value + 360); else if (cv_cam2_rotate.value > 359) CV_SetValue(&cv_cam2_rotate, cv_cam2_rotate.value % 360); } static void CV_CamRotate3_OnChange(void) { if (cv_cam3_rotate.value < 0) CV_SetValue(&cv_cam3_rotate, cv_cam3_rotate.value + 360); else if (cv_cam3_rotate.value > 359) CV_SetValue(&cv_cam3_rotate, cv_cam3_rotate.value % 360); } static void CV_CamRotate4_OnChange(void) { if (cv_cam4_rotate.value < 0) CV_SetValue(&cv_cam4_rotate, cv_cam4_rotate.value + 360); else if (cv_cam4_rotate.value > 359) CV_SetValue(&cv_cam4_rotate, cv_cam4_rotate.value % 360); } static CV_PossibleValue_t CV_CamSpeed[] = {{0, "MIN"}, {1*FRACUNIT, "MAX"}, {0, NULL}}; static CV_PossibleValue_t rotation_cons_t[] = {{1, "MIN"}, {25, "MAX"}, {0, NULL}}; static CV_PossibleValue_t CV_CamRotate[] = {{-720, "MIN"}, {720, "MAX"}, {0, NULL}}; static CV_PossibleValue_t multiplier_cons_t[] = {{0, "MIN"}, {3*FRACUNIT, "MAX"}, {0, NULL}}; consvar_t cv_cam_dist[MAXSPLITSCREENPLAYERS] = { {"cam_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam2_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam3_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam4_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL} }; consvar_t cv_cam_height[MAXSPLITSCREENPLAYERS] = { {"cam_height", "50", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam2_height", "50", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam3_height", "50", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam4_height", "50", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL} }; consvar_t cv_cam_still[MAXSPLITSCREENPLAYERS] = { {"cam_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam2_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam3_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam4_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL} }; consvar_t cv_cam_speed[MAXSPLITSCREENPLAYERS] = { {"cam_speed", "0.4", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam2_speed", "0.4", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam3_speed", "0.4", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam4_speed", "0.4", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL} }; consvar_t cv_cam_rotate[MAXSPLITSCREENPLAYERS] = { {"cam_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange, 0, NULL, NULL, 0, 0, NULL}, {"cam2_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange, 0, NULL, NULL, 0, 0, NULL}, {"cam3_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange, 0, NULL, NULL, 0, 0, NULL}, {"cam4_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange, 0, NULL, NULL, 0, 0, NULL} }; consvar_t cv_cam_rotspeed[MAXSPLITSCREENPLAYERS] = { {"cam_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam2_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam3_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}, {"cam4_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL} }; fixed_t t_cam_dist[MAXSPLITSCREENPLAYERS] = -42; fixed_t t_cam_height[MAXSPLITSCREENPLAYERS] = -42; fixed_t t_cam_rotate[MAXSPLITSCREENPLAYERS] = -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 // redefine this static fixed_t forwardmove = MAXPLMOVE<>16; static fixed_t angleturn[3] = {KART_FULLTURN/2, KART_FULLTURN, KART_FULLTURN/4}; // + slow turn 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 laim, th, tspeed, forward, axis; //i const INT32 speed = 1; // these ones used for multiple conditions boolean turnleft, turnright, mouseaiming; boolean invertmouse, lookaxis, usejoystick, kbl; angle_t lang; INT32 player_invert; INT32 screen_invert; ticcmd_t *cmd = &cameracmd; (void)cam; if (!demo.playback) return cmd; // empty cmd, no. lang = democam.localangle; laim = democam.localaiming; th = democam.turnheld; kbl = democam.keyboardlook; G_CopyTiccmd(cmd, I_BaseTiccmd(), 1); // empty, or external driver cmd->angleturn = (INT16)(lang >> 16); cmd->aiming = G_ClipAimingPitch(&laim); mouseaiming = true; invertmouse = cv_invertmouse.value; lookaxis = cv_lookaxis.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; // use two stage accelerative turning // on the keyboard and joystick if (turnleft || turnright) th += 1; else th = 0; if (th < SLOWTURNTICS) tspeed = 2; // slow turn else tspeed = speed; // let movement keys cancel each other out if (turnright && !(turnleft)) { cmd->angleturn = (INT16)(cmd->angleturn - (angleturn[tspeed])); } else if (turnleft && !(turnright)) { cmd->angleturn = (INT16)(cmd->angleturn + (angleturn[tspeed])); } cmd->angleturn = (INT16)(cmd->angleturn - ((mousex*(encoremode ? -1 : 1)*8))); 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 += forwardmove; if (PlayerInputDown(1, gc_aimbackward) || (usejoystick && axis > 0)) forward -= forwardmove; // 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 laim += (mlooky<<19)*player_invert*screen_invert; axis = PlayerJoyAxis(1, AXISLOOK); // spring back if not using keyboard neither mouselookin' if (!kbl && !lookaxis && !mouseaiming) laim = 0; if (PlayerInputDown(1, gc_lookup) || (axis < 0)) { laim += KB_LOOKSPEED * screen_invert; kbl = true; } else if (PlayerInputDown(1, gc_lookdown) || (axis > 0)) { laim -= KB_LOOKSPEED * screen_invert; kbl = true; } if (PlayerInputDown(1, gc_centerview)) // No need to put a spectator limit on this one though :V laim = 0; cmd->aiming = G_ClipAimingPitch(&laim); mousex = mousey = mlooky = 0; cmd->forwardmove += (SINT8)forward; if (cmd->forwardmove > MAXPLMOVE) cmd->forwardmove = MAXPLMOVE; else if (cmd->forwardmove < -MAXPLMOVE) cmd->forwardmove = -MAXPLMOVE; lang += (cmd->angleturn<<16); democam.localangle = lang; democam.localaiming = laim; democam.turnheld = th; 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<angle = cmd->angleturn<<16; // 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->drawflags |= MFD_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 = true; 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; if (!(thiscam == &camera[0] && (cv_cam_still[0].value) && !(thiscam == &camera[1] && cv_cam_still[1].value) && !(thiscam == &camera[2] && cv_cam_still[2].value) && !(thiscam == &camera[3] && cv_cam_still[3].value)) { 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 democam.soundmobj = NULL; // reset this each frame, we don't want the game crashing for stupid reasons now do we static fixed_t camsideshift[2] = {0, 0}; fixed_t shiftx = 0, shifty = 0; // 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; } mo = player->mo; #ifndef NOCLIPCAM cameranoclip = ((player->pflags & (PF_NOCLIP|PF_NIGHTSMODE)) || (mo->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT)) // Noclipping player camera noclips too!! || (leveltime < introtime)); // Kart intro cam #endif if ((player->pflags & PF_GAMETYPEOVER) && (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)) { if (player->spectator) // force cam off for spectators return true; if (!cv_chasecam.value && thiscam == &camera[0]) return true; if (!cv_chasecam2.value && thiscam == &camera[1]) return true; if (!cv_chasecam3.value && thiscam == &camera[2]) return true; if (!cv_chasecam4.value && thiscam == &camera[3]) return true; } if (!thiscam->chase && !resetcalled) { if (player == &players[consoleplayer]) focusangle = localangle[0]; else if (player == &players[displayplayers[1]]) focusangle = localangle[1]; else if (player == &players[displayplayers[2]]) focusangle = localangle[2]; else if (player == &players[displayplayers[3]]) focusangle = localangle[3]; else focusangle = mo->angle; if (thiscam == &camera[0]) camrotate = cv_cam_rotate.value; else if (thiscam == &camera[1]) camrotate = cv_cam2_rotate.value; else if (thiscam == &camera[2]) camrotate = cv_cam3_rotate.value; else if (thiscam == &camera[3]) camrotate = cv_cam4_rotate.value; else camrotate = 0; 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 if (sign) { focusangle = FixedAngle(sign->spawnpoint->angle << FRACBITS) + ANGLE_180; focusaiming = 0; } else if (player == &players[consoleplayer]) { focusangle = localangle[0]; focusaiming = localaiming[0]; } else if (player == &players[g_localplayers[1]]) { focusangle = localangle[1]; focusaiming = localaiming[1]; } else if (player == &players[g_localplayers[2]]) { focusangle = localangle[2]; focusaiming = localaiming[2]; } else if (player == &players[g_localplayers[3]]) { focusangle = localangle[3]; focusaiming = localaiming[3]; } else { focusangle = player->cmd.angleturn << 16; focusaiming = player->aiming; } if (P_CameraThinker(player, thiscam, resetcalled)) return true; lookback = ( player->cmd.buttons & BT_LOOKBACK ); if (thiscam == &camera[1]) // Camera 2 { num = 1; camspeed = cv_cam2_speed.value; camstill = cv_cam2_still.value; camrotate = cv_cam2_rotate.value; camdist = FixedMul(cv_cam2_dist.value, mapobjectscale); camheight = FixedMul(cv_cam2_height.value, mapobjectscale); } else if (thiscam == &camera[2]) // Camera 3 { num = 2; camspeed = cv_cam3_speed.value; camstill = cv_cam3_still.value; camrotate = cv_cam3_rotate.value; camdist = FixedMul(cv_cam3_dist.value, mapobjectscale); camheight = FixedMul(cv_cam3_height.value, mapobjectscale); } else if (thiscam == &camera[3]) // Camera 4 { num = 3; camspeed = cv_cam4_speed.value; camstill = cv_cam4_still.value; camrotate = cv_cam4_rotate.value; camdist = FixedMul(cv_cam4_dist.value, mapobjectscale); camheight = FixedMul(cv_cam4_height.value, mapobjectscale); } else // Camera 1 { num = 0; camspeed = cv_cam_speed.value; camstill = cv_cam_still.value; camorbit = cv_cam_orbit.value; camrotate = cv_cam_rotate.value; camdist = FixedMul(cv_cam_dist.value, mapobjectscale); camheight = FixedMul(cv_cam_height.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]--; } 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<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) && ((thiscam == &camera[0] && t_cam_rotate[0] != -42) || (thiscam == &camera[1] && t_cam_rotate[1] != -42) || (thiscam == &camera[2] && t_cam_rotate[2] != -42) || (thiscam == &camera[3] && t_cam_rotate[3] != -42))) { angle = FixedAngle(camrotate*FRACUNIT); thiscam->angle = angle; } // sets ideal cam pos dist = camdist; /* player->speed subtracts conveyors, janks up the camera */ speed = R_PointToDist2(0, 0, player->mo->momx, player->mo->momy); if (speed > K_GetKartSpeed(player, false)) dist += 4*(speed - K_GetKartSpeed(player, false)); dist += abs(thiscam->momz)/4; if (player->karthud[khud_boostcam]) dist -= FixedMul(11*dist/16, player->karthud[khud_boostcam]); 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 #ifdef GL_SHADERS/* just so we can't possibly forget about it */ && !cv_grshearing.value #endif ) 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->kartstuff[k_drift] != 0) { fixed_t panmax = (dist/5); INT32 driftval = K_GetKartDriftSparkValue(player); INT32 dc = player->kartstuff[k_driftcharge]; if (dc > driftval || dc < 0) dc = driftval; pan = FixedDiv(FixedMul((fixed_t)dc, panmax), driftval); if (pan > panmax) pan = panmax; if (player->kartstuff[k_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<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 && !((player->pflags & PF_NIGHTSMODE) && player->exiting)) 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) { // Team changing isn't allowed. if (!cv_allowteamchange.value) { if (P_IsLocalPlayer(player)) CONS_Printf(M_GetText("Server does not allow team change.\n")); //player->powers[pw_flashing] = TICRATE + 1; //to prevent message spam. } // 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. else if (G_GametypeHasTeams()) { INT32 changeto = 0; 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; if (player->mo) { P_RemoveMobj(player->mo); player->mo = NULL; } player->spectator = false; player->pflags &= ~PF_WANTSTOJOIN; player->kartstuff[k_spectatewait] = 0; player->ctfteam = changeto; player->playerstate = PST_REBORN; //Reset away view if (P_IsLocalPlayer(player) && displayplayers[0] != consoleplayer) { // Call ViewpointSwitch hooks here. // The viewpoint was forcibly changed. LUAh_ViewpointSwitch(player, &players[consoleplayer], true); displayplayers[0] = consoleplayer; } if (changeto == 1) CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[player-players], '\x85', M_GetText("Red team"), '\x80'); else if (changeto == 2) CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[player-players], '\x84', M_GetText("Blue team"), '\x80'); return true; // no more player->mo, cannot continue. } // Joining in game from firing. else { if (player->mo) { P_RemoveMobj(player->mo); player->mo = NULL; } player->spectator = false; player->pflags &= ~PF_WANTSTOJOIN; player->kartstuff[k_spectatewait] = 0; player->playerstate = PST_REBORN; //Reset away view if (P_IsLocalPlayer(player) && displayplayers[0] != consoleplayer) displayplayers[0] = consoleplayer; HU_AddChatText(va(M_GetText("\x82*%s entered the game."), player_names[player-players]), false); return true; // no more player->mo, cannot continue. } return false; } // 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; UINT8 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 <= 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...) if (P_FindSpecialLineFromTag(13, sector->tag, -1) != -1) *type = postimg_heat; else 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)) 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; if (P_FindSpecialLineFromTag(13, rover->master->frontsector->tag, -1) != -1) *type = postimg_heat; } } // 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<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_GAMETYPEOVER) { // 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) { CON_LogMessage(va(M_GetText("%s ran out of time.\n"), player_names[player-players])); } player->pflags |= PF_GAMETYPEOVER; if (G_GametypeUsesLives()) { K_PlayerLoseLife(player); } if (player->mo) { S_StopSound(player->mo); P_DamageMobj(player->mo, NULL, NULL, 10000); } P_EndingMusic(player); if (!exitcountdown) exitcountdown = 5*TICRATE; } // 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)); } /* 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; UINT8 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)); // 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<>ANGLETOFINESHIFT) & FINEMASK); sz += FixedMul(player->mo->scale, sine)*P_MobjFlip(player->mo); } // Set follower colour switch (player->followercolor) { case MAXSKINCOLORS: // "Match" color = player->skincolor; break; case MAXSKINCOLORS+1: // "Opposite" color = KartColor_Opposite[player->skincolor*2]; 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 player->follower->angle = 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)! player->follower->momx = (sx - player->follower->x)/fl.horzlag; player->follower->momy = (sy - player->follower->y)/fl.horzlag; player->follower->momz = (sz - player->follower->z)/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->drawflags = player->mo->drawflags; // Make the follower invisible if we no contest'd rather than removing it. No one will notice the diff seriously. // Also make the follower invisible if we choose not to have it displayed because it isn't ours. (also quick hacky check for f12) if (player->pflags & PF_GAMETYPEOVER || (!cv_showfollowers.value && (!P_IsDisplayPlayer(player) || displayplayers[0] != consoleplayer) )) player->follower->drawflags |= MFD_DONTDRAW; if (player->speed && (player->follower->momx || player->follower->momy)) player->follower->angle = R_PointToAngle2(0, 0, player->follower->momx, player->follower->momy); // if we're moving let's make the angle the direction we're moving towards. This is to avoid drifting / reverse looking awkward. // Make sure the follower itself is also moving however, otherwise we'll be facing angle 0 // 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->momz = player->follower->momz; P_SetScale(bmobj, FixedMul(bubble, player->mo->scale)); K_GenericExtraFlagsNoZAdjust(bmobj, player->follower); bmobj->drawflags = player->mo->drawflags; 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->kartstuff[k_spinouttimer] || player->mo->state == &states[S_KART_SPIN] || player->mo->health <= 0) { player->follower->movecount = 0; // cancel hit confirm. player->follower->angle = player->frameangle; // 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); } } } } } // // 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; } #ifdef SEENAMES if (netgame && player == &players[displayplayers[0]] && !(leveltime % (TICRATE/5)) && !r_splitscreen) { seenplayer = NULL; if (cv_seenames.value && cv_allowseenames.value) { mobj_t *mo = P_SpawnNameFinder(player->mo, MT_NAMECHECK); if (mo) { short int i; mo->flags |= MF_NOCLIPHEIGHT; for (i = 0; i < 32; i++) { // Debug drawing // if (i&1) // P_SpawnMobj(mo->x, mo->y, mo->z, MT_SPARK); if (P_RailThinker(mo)) break; // mobj was removed (missile hit a wall) or couldn't move } } } } #endif 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--; // Track airtime if (P_IsObjectOnGround(player->mo)) { 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->kartstuff[k_throwdir] = 1; } else if (cmd->buttons & BT_BACKWARD) { player->kartstuff[k_throwdir] = -1; } else { player->kartstuff[k_throwdir] = 0; } #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_GAMETYPEOVER) && 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_GAMETYPEOVER) && player->lives > 0) { P_DoTimeOver(player); if (player->playerstate == PST_DEAD) { 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->drawflags |= MFD_SHADOW; else player->mo->drawflags &= ~(MFD_TRANSMASK|MFD_BRIGHTMASK); 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->mo->health = 1; player->health = 1; } // SRB2kart 010217 if (leveltime < introtime) { player->powers[pw_nocontrol] = 2; } // Synchronizes the "real" amount of time spent in the level. if (!player->exiting && !stoppedclock) { if (gametyperules & GTR_CIRCUIT) { player->realtime = leveltime - starttime; if (player == &players[consoleplayer]) { if (player->spectator || !circuitmap) curlap = 0; else 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->powers[pw_flashing]) { player->pflags ^= PF_WANTSTOJOIN; player->powers[pw_flashing] = TICRATE/2 + 1; /*if (P_SpectatorJoinGame(player)) return; // player->mo was removed.*/ } // Even if not NiGHTS, pull in nearby objects when walking around as John Q. Elliot. if (!objectplacing && !((netgame || multiplayer) && player->spectator)) { thinker_t *th; mobj_t *mo2; fixed_t x = player->mo->x; fixed_t y = player->mo->y; fixed_t z = player->mo->z; for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) continue; mo2 = (mobj_t *)th; if (!(mo2->type == MT_RING || mo2->type == MT_COIN || mo2->type == MT_BLUESPHERE || mo2->type == MT_BOMBSPHERE || mo2->type == MT_NIGHTSCHIP || mo2->type == MT_NIGHTSSTAR)) continue; if (mo2->flags2 & MF2_NIGHTSPULL) continue; if (P_AproxDistance(P_AproxDistance(mo2->x - x, mo2->y - y), mo2->z - z) > FixedMul(128*FRACUNIT, player->mo->scale)) continue; // Yay! The thing's in reach! Pull it in! mo2->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT; mo2->flags2 |= MF2_NIGHTSPULL; // New NiGHTS attract speed dummied out because the older behavior // is exploited as a mechanic. Uncomment to enable. mo2->movefactor = 0; // 40*FRACUNIT; // initialize the NightsItemChase timer P_SetTarget(&mo2->tracer, player->mo); } } if (player->linktimer && !player->powers[pw_nights_linkfreeze]) { if (--player->linktimer <= 0) // Link timer player->linkcount = 0; } 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; P_ResetScore(player); } else { // Move around. P_MovePlayer(player); } player->mo->movefactor = FRACUNIT; // We're not going to do any more with this, so let's change it back for the next frame. // Unset statis flags after moving. // In other words, if you manually set stasis via code, // it lasts for one tic. player->pflags &= ~PF_FULLSTASIS; 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 #if 1 // "Blur" a bit when you have speed shoes and are going fast enough if ((player->powers[pw_super] || player->powers[pw_sneakers]) && (player->speed + abs(player->mo->momz)) > FixedMul(20*FRACUNIT,player->mo->scale)) { UINT8 i; mobj_t *gmobj = P_SpawnGhostMobj(player->mo); gmobj->fuse = 2; if (gmobj->tracer) gmobj->tracer->fuse = 2; if (leveltime & 1) { gmobj->frame &= ~FF_TRANSMASK; gmobj->frame |= tr_trans70<tracer) { gmobj->tracer->frame &= ~FF_TRANSMASK; gmobj->tracer->frame |= tr_trans70<drawflags |= MFD_DONTDRAW; break; } } } #endif // check for use if (cmd->buttons & BT_BRAKE) player->pflags |= PF_USEDOWN; else player->pflags &= ~PF_USEDOWN; // IF PLAYER NOT HERE THEN FLASH END IF if (player->quittime && player->powers[pw_flashing] < flashingtics - 1 && !player->gotflag) player->powers[pw_flashing] = flashingtics - 1; // Counters, time dependent power ups. // Time Bonus & Ring Bonus count settings // Strength counts up to diminish fade. if (player->powers[pw_flashing] && player->powers[pw_flashing] < UINT16_MAX && (player->spectator || player->powers[pw_flashing] < K_GetKartFlashing(player))) player->powers[pw_flashing]--; if (player->powers[pw_nocontrol] & ((1<<15)-1) && player->powers[pw_nocontrol] < UINT16_MAX) { if (!(--player->powers[pw_nocontrol])) player->pflags &= ~PF_SKIDDOWN; } else player->powers[pw_nocontrol] = 0; if (player->powers[pw_ignorelatch] & ((1<<15)-1) && player->powers[pw_ignorelatch] < UINT16_MAX) player->powers[pw_ignorelatch]--; else player->powers[pw_ignorelatch] = 0; //pw_super acts as a timer now if (player->powers[pw_super]) player->powers[pw_super]++; // Flash player after being hit. if (!(//player->pflags & PF_NIGHTSMODE || player->kartstuff[k_hyudorotimer] // SRB2kart - fixes Hyudoro not flashing when it should. || player->kartstuff[k_growshrinktimer] > 0 // Grow doesn't flash either. || (player->respawn.state != RESPAWNST_NONE) // Respawn timer (for drop dash effect) || (player->pflags & PF_GAMETYPEOVER) // NO CONTEST explosion || ((gametyperules & GTR_BUMPERS) && player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || leveltime < starttime)) // Level intro { if (player->powers[pw_flashing] > 0 && player->powers[pw_flashing] < K_GetKartFlashing(player) && (leveltime & 1)) player->mo->drawflags |= MFD_DONTDRAW; else player->mo->drawflags &= ~MFD_DONTDRAW; } player->pflags &= ~PF_SLIDING; K_KartPlayerThink(player, cmd); // SRB2kart LUAh_PlayerThink(player); } // Checks if the mobj is above lava. Used by Pterabyte. static boolean P_MobjAboveLava(mobj_t *mobj) { sector_t *sector = mobj->subsector->sector; if (sector->ffloors) { ffloor_t *rover; for (rover = sector->ffloors; rover; rover = rover->next) { if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_SWIMMABLE) || GETSECSPECIAL(rover->master->frontsector->special, 1) != 3) continue; if (mobj->eflags & MFE_VERTICALFLIP) { if (*rover->bottomheight <= mobj->ceilingz && *rover->bottomheight >= mobj->z) return true; } else { if (*rover->topheight >= mobj->floorz && *rover->topheight <= mobj->z) return true; } } } return false; } // // P_PlayerAfterThink // // Thinker for player after all other thinkers have run // void P_PlayerAfterThink(player_t *player) { ticcmd_t *cmd; //INT32 oldweapon = player->currentweapon; // SRB2kart - unused 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 cmd = &player->cmd; #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 (player->pflags & PF_SLIDING) P_SetPlayerMobjState(player->mo, player->mo->info->painstate); 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->drawflags |= MFD_DONTDRAW; player->mo->flags |= MF_NOGRAVITY; } if (P_IsObjectOnGround(player->mo)) player->mo->pmomz = 0; K_KartPlayerAfterThink(player); if (player->powers[pw_dye]) { player->mo->colorized = true; player->mo->color = player->powers[pw_dye]; } 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) { INT16 delta = (INT16)(angle >> 16) - player->angleturn; P_ForceLocalAngle(player, P_GetLocalAngle(player) + (delta << 16)); player->angleturn += delta; } void P_SetLocalAngle(player_t *player, angle_t angle) { INT16 delta = (INT16)((angle - P_GetLocalAngle(player)) >> 16); UINT8 i; P_ForceLocalAngle(player, P_GetLocalAngle(player) + (angle_t)(delta << 16)); for (i = 0; i <= splitscreen; i++) { if (player == &players[g_localplayers[i]]) { ticcmd_oldangleturn[i] += delta; break; } } } 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 <= splitscreen; i++) { if (player == &players[g_localplayers[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 < MAXSPLITSCREENPLAYERS; i++) { if (player == &players[g_localplayers[i]]) { localangle[i] = angle; break; } } } boolean P_PlayerFullbright(player_t *player) { return (player->kartstuff[k_invincibilitytimer] > 0); }