2014-03-15 16:59:03 +00:00
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
2022-03-03 19:24:46 +00:00
// Copyright (C) 1999-2022 by Sonic Team Junior.
2014-03-15 16:59:03 +00:00
//
// 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_tick.c
/// \brief Archiving: SaveGame I/O, Thinker, Ticker
# include "doomstat.h"
# include "g_game.h"
# include "p_local.h"
# include "z_zone.h"
# include "s_sound.h"
# include "st_stuff.h"
# include "p_polyobj.h"
# include "m_random.h"
# include "lua_script.h"
# include "lua_hook.h"
2020-10-10 18:08:24 +00:00
# include "m_perfstats.h"
2020-11-07 09:32:59 +00:00
# include "i_system.h" // I_GetPreciseTime
2019-10-06 22:40:52 +00:00
# include "r_fps.h"
2014-03-15 16:59:03 +00:00
// Object place
# include "m_cheat.h"
tic_t leveltime ;
//
// THINKERS
// All thinkers should be allocated by Z_Calloc
// so they can be operated on uniformly.
// The actual structures will vary in size,
// but the first element must be thinker_t.
//
2019-04-20 20:37:19 +00:00
// The entries will behave like both the head and tail of the lists.
thinker_t thlist [ NUM_THINKERLISTS ] ;
2014-03-15 16:59:03 +00:00
void Command_Numthinkers_f ( void )
{
INT32 num ;
INT32 count = 0 ;
actionf_p1 action ;
thinker_t * think ;
2019-07-12 23:42:03 +00:00
thinklistnum_t start = 0 ;
thinklistnum_t end = NUM_THINKERLISTS - 1 ;
thinklistnum_t i ;
2014-03-15 16:59:03 +00:00
if ( gamestate ! = GS_LEVEL )
{
CONS_Printf ( M_GetText ( " You must be in a level to use this. \n " ) ) ;
return ;
}
if ( COM_Argc ( ) < 2 )
{
CONS_Printf ( M_GetText ( " numthinkers <#>: Count number of thinkers \n " ) ) ;
CONS_Printf (
" \t 1: P_MobjThinker \n "
2018-10-07 14:00:58 +00:00
/*"\t2: P_RainThinker\n"
" \t 3: P_SnowThinker \n " */
" \t 2: P_NullPrecipThinker \n "
" \t 3: T_Friction \n "
" \t 4: T_Pusher \n "
" \t 5: P_RemoveThinkerDelayed \n " ) ;
2014-03-15 16:59:03 +00:00
return ;
}
num = atoi ( COM_Argv ( 1 ) ) ;
switch ( num )
{
case 1 :
2019-07-12 23:42:03 +00:00
start = end = THINK_MOBJ ;
2014-03-15 16:59:03 +00:00
action = ( actionf_p1 ) P_MobjThinker ;
CONS_Printf ( M_GetText ( " Number of %s: " ) , " P_MobjThinker " ) ;
break ;
2018-10-07 14:00:58 +00:00
/*case 2:
2014-03-15 16:59:03 +00:00
action = ( actionf_p1 ) P_RainThinker ;
CONS_Printf ( M_GetText ( " Number of %s: " ) , " P_RainThinker " ) ;
break ;
case 3 :
action = ( actionf_p1 ) P_SnowThinker ;
CONS_Printf ( M_GetText ( " Number of %s: " ) , " P_SnowThinker " ) ;
2018-10-07 14:00:58 +00:00
break ; */
case 2 :
2019-07-12 23:42:03 +00:00
start = end = THINK_PRECIP ;
2014-03-15 16:59:03 +00:00
action = ( actionf_p1 ) P_NullPrecipThinker ;
CONS_Printf ( M_GetText ( " Number of %s: " ) , " P_NullPrecipThinker " ) ;
break ;
2018-10-07 14:00:58 +00:00
case 3 :
2019-07-12 23:42:03 +00:00
start = end = THINK_MAIN ;
2014-03-15 16:59:03 +00:00
action = ( actionf_p1 ) T_Friction ;
CONS_Printf ( M_GetText ( " Number of %s: " ) , " T_Friction " ) ;
break ;
2018-10-07 14:00:58 +00:00
case 4 :
2019-07-12 23:42:03 +00:00
start = end = THINK_MAIN ;
2014-03-15 16:59:03 +00:00
action = ( actionf_p1 ) T_Pusher ;
CONS_Printf ( M_GetText ( " Number of %s: " ) , " T_Pusher " ) ;
break ;
2018-10-07 14:00:58 +00:00
case 5 :
2014-03-15 16:59:03 +00:00
action = ( actionf_p1 ) P_RemoveThinkerDelayed ;
CONS_Printf ( M_GetText ( " Number of %s: " ) , " P_RemoveThinkerDelayed " ) ;
break ;
default :
CONS_Printf ( M_GetText ( " That is not a valid number. \n " ) ) ;
return ;
}
2019-07-12 23:42:03 +00:00
for ( i = start ; i < = end ; i + + )
2014-03-15 16:59:03 +00:00
{
2019-07-12 23:42:03 +00:00
for ( think = thlist [ i ] . next ; think ! = & thlist [ i ] ; think = think - > next )
{
if ( think - > function . acp1 ! = action )
continue ;
2014-03-15 16:59:03 +00:00
2019-07-12 23:42:03 +00:00
count + + ;
}
2014-03-15 16:59:03 +00:00
}
CONS_Printf ( " %d \n " , count ) ;
}
void Command_CountMobjs_f ( void )
{
thinker_t * th ;
mobjtype_t i ;
INT32 count ;
if ( gamestate ! = GS_LEVEL )
{
CONS_Printf ( M_GetText ( " You must be in a level to use this. \n " ) ) ;
return ;
}
if ( COM_Argc ( ) > = 2 )
{
size_t j ;
for ( j = 1 ; j < COM_Argc ( ) ; j + + )
{
i = atoi ( COM_Argv ( j ) ) ;
if ( i > = NUMMOBJTYPES )
{
CONS_Printf ( M_GetText ( " Object number %d out of range (max %d). \n " ) , i , NUMMOBJTYPES - 1 ) ;
continue ;
}
count = 0 ;
2019-04-20 21:29:20 +00:00
for ( th = thlist [ THINK_MOBJ ] . next ; th ! = & thlist [ THINK_MOBJ ] ; th = th - > next )
2014-03-15 16:59:03 +00:00
{
2019-07-12 23:42:03 +00:00
if ( th - > function . acp1 = = ( actionf_p1 ) P_RemoveThinkerDelayed )
2014-03-15 16:59:03 +00:00
continue ;
if ( ( ( mobj_t * ) th ) - > type = = i )
count + + ;
}
CONS_Printf ( M_GetText ( " There are %d objects of type %d currently in the level. \n " ) , count , i ) ;
}
return ;
}
CONS_Printf ( M_GetText ( " Count of active objects in level: \n " ) ) ;
for ( i = 0 ; i < NUMMOBJTYPES ; i + + )
{
count = 0 ;
2019-04-20 21:29:20 +00:00
for ( th = thlist [ THINK_MOBJ ] . next ; th ! = & thlist [ THINK_MOBJ ] ; th = th - > next )
2014-03-15 16:59:03 +00:00
{
2019-07-12 23:42:03 +00:00
if ( th - > function . acp1 = = ( actionf_p1 ) P_RemoveThinkerDelayed )
2014-03-15 16:59:03 +00:00
continue ;
if ( ( ( mobj_t * ) th ) - > type = = i )
count + + ;
}
2014-08-27 03:56:30 +00:00
if ( count > 0 ) // Don't bother displaying if there are none of this type!
CONS_Printf ( " * %d: %d \n " , i , count ) ;
2014-03-15 16:59:03 +00:00
}
}
//
// P_InitThinkers
//
void P_InitThinkers ( void )
{
2019-04-20 20:37:19 +00:00
UINT8 i ;
for ( i = 0 ; i < NUM_THINKERLISTS ; i + + )
thlist [ i ] . prev = thlist [ i ] . next = & thlist [ i ] ;
2014-03-15 16:59:03 +00:00
}
// Adds a new thinker at the end of the list.
2019-04-20 20:37:19 +00:00
void P_AddThinker ( const thinklistnum_t n , thinker_t * thinker )
2014-03-15 16:59:03 +00:00
{
2019-07-12 23:42:03 +00:00
# ifdef PARANOIA
2019-08-07 16:39:04 +00:00
I_Assert ( n < NUM_THINKERLISTS ) ;
2019-07-12 23:42:03 +00:00
# endif
2019-04-20 20:37:19 +00:00
thlist [ n ] . prev - > next = thinker ;
thinker - > next = & thlist [ n ] ;
thinker - > prev = thlist [ n ] . prev ;
thlist [ n ] . prev = thinker ;
2014-03-15 16:59:03 +00:00
thinker - > references = 0 ; // killough 11/98: init reference counter to 0
}
//
// killough 11/98:
//
// Make currentthinker external, so that P_RemoveThinkerDelayed
// can adjust currentthinker when thinkers self-remove.
static thinker_t * currentthinker ;
//
// P_RemoveThinkerDelayed()
//
// Called automatically as part of the thinker loop in P_RunThinkers(),
// on nodes which are pending deletion.
//
// If this thinker has no more pointers referencing it indirectly,
// remove it, and set currentthinker to one node preceeding it, so
// that the next step in P_RunThinkers() will get its successor.
//
2019-07-12 23:42:03 +00:00
void P_RemoveThinkerDelayed ( thinker_t * thinker )
2014-03-15 16:59:03 +00:00
{
2019-07-12 23:42:03 +00:00
thinker_t * next ;
# ifdef PARANOIA
2019-07-14 13:21:32 +00:00
# define BEENAROUNDBIT (0x40000000) // has to be sufficiently high that it's unlikely to happen in regular gameplay. If you change this, pay attention to the bit pattern of INT32_MIN.
if ( thinker - > references & ~ BEENAROUNDBIT )
2014-03-15 16:59:03 +00:00
{
2019-07-14 13:21:32 +00:00
if ( thinker - > references & BEENAROUNDBIT ) // Usually gets cleared up in one frame; what's going on here, then?
CONS_Printf ( " Number of potentially faulty references: %d \n " , ( thinker - > references & ~ BEENAROUNDBIT ) ) ;
thinker - > references | = BEENAROUNDBIT ;
return ;
2014-03-15 16:59:03 +00:00
}
2019-07-14 13:21:32 +00:00
# undef BEENAROUNDBIT
# else
2019-07-12 23:42:03 +00:00
if ( thinker - > references )
return ;
2019-07-14 13:21:32 +00:00
# endif
2019-07-12 23:42:03 +00:00
/* Remove from main thinker list */
next = thinker - > next ;
/* Note that currentthinker is guaranteed to point to us,
* and since we ' re freeing our memory , we had better change that . So
* point it to thinker - > prev , so the iterator will correctly move on to
* thinker - > prev - > next = thinker - > next */
( next - > prev = currentthinker = thinker - > prev ) - > next = next ;
2022-04-13 03:39:40 +00:00
R_DestroyLevelInterpolators ( thinker ) ;
2019-07-12 23:42:03 +00:00
Z_Free ( thinker ) ;
2014-03-15 16:59:03 +00:00
}
//
// P_RemoveThinker
//
// Deallocation is lazy -- it will not actually be freed
// until its thinking turn comes up.
//
// killough 4/25/98:
//
// Instead of marking the function with -1 value cast to a function pointer,
// set the function to P_RemoveThinkerDelayed(), so that later, it will be
// removed automatically as part of the thinker process.
//
void P_RemoveThinker ( thinker_t * thinker )
{
LUA_InvalidateUserdata ( thinker ) ;
2019-07-12 23:42:03 +00:00
thinker - > function . acp1 = ( actionf_p1 ) P_RemoveThinkerDelayed ;
2014-03-15 16:59:03 +00:00
}
/*
* P_SetTarget
*
* This function is used to keep track of pointer references to mobj thinkers .
* In Doom , objects such as lost souls could sometimes be removed despite
* their still being referenced . In Boom , ' target ' mobj fields were tested
* during each gametic , and any objects pointed to by them would be prevented
* from being removed . But this was incomplete , and was slow ( every mobj was
* checked during every gametic ) . Now , we keep a count of the number of
* references , and delay removal until the count is 0.
*/
mobj_t * P_SetTarget ( mobj_t * * mop , mobj_t * targ )
{
if ( * mop ) // If there was a target already, decrease its refcount
( * mop ) - > thinker . references - - ;
if ( ( * mop = targ ) ! = NULL ) // Set new target and if non-NULL, increase its counter
targ - > thinker . references + + ;
return targ ;
}
//
// P_RunThinkers
//
// killough 4/25/98:
//
// Fix deallocator to stop using "next" pointer after node has been freed
// (a Doom bug).
//
// Process each thinker. For thinkers which are marked deleted, we must
// load the "next" pointer prior to freeing the node. In Doom, the "next"
// pointer was loaded AFTER the thinker was freed, which could have caused
// crashes.
//
// But if we are not deleting the thinker, we should reload the "next"
// pointer after calling the function, in case additional thinkers are
// added at the end of the list.
//
// killough 11/98:
//
// Rewritten to delete nodes implicitly, by making currentthinker
// external and using P_RemoveThinkerDelayed() implicitly.
//
static inline void P_RunThinkers ( void )
{
2019-04-20 20:37:19 +00:00
size_t i ;
for ( i = 0 ; i < NUM_THINKERLISTS ; i + + )
2014-03-15 16:59:03 +00:00
{
2021-10-25 17:49:15 +00:00
PS_START_TIMING ( ps_thlist_times [ i ] ) ;
2019-04-20 20:37:19 +00:00
for ( currentthinker = thlist [ i ] . next ; currentthinker ! = & thlist [ i ] ; currentthinker = currentthinker - > next )
{
2019-07-12 23:42:03 +00:00
# ifdef PARANOIA
2019-08-07 16:39:04 +00:00
I_Assert ( currentthinker - > function . acp1 ! = NULL ) ;
2019-07-12 23:42:03 +00:00
# endif
2014-03-15 16:59:03 +00:00
currentthinker - > function . acp1 ( currentthinker ) ;
2019-04-20 20:37:19 +00:00
}
2021-10-25 17:49:15 +00:00
PS_STOP_TIMING ( ps_thlist_times [ i ] ) ;
2014-03-15 16:59:03 +00:00
}
2019-04-20 20:37:19 +00:00
2014-03-15 16:59:03 +00:00
}
//
// P_DoAutobalanceTeams()
//
// Determine if the teams are unbalanced, and if so, move a player to the other team.
//
static void P_DoAutobalanceTeams ( void )
{
changeteam_union NetPacket ;
UINT16 usvalue ;
INT32 i = 0 ;
INT32 red = 0 , blue = 0 ;
INT32 redarray [ MAXPLAYERS ] , bluearray [ MAXPLAYERS ] ;
INT32 redflagcarrier = 0 , blueflagcarrier = 0 ;
INT32 totalred = 0 , totalblue = 0 ;
NetPacket . value . l = NetPacket . value . b = 0 ;
memset ( redarray , 0 , sizeof ( redarray ) ) ;
memset ( bluearray , 0 , sizeof ( bluearray ) ) ;
// Only do it if we have enough room in the net buffer to send it.
// Otherwise, come back next time and try again.
if ( sizeof ( usvalue ) > GetFreeXCmdSize ( ) )
return ;
//We have to store the players in an array with the rest of their team.
//We can then pick a random player to be forced to change teams.
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
if ( playeringame [ i ] & & players [ i ] . ctfteam )
{
if ( players [ i ] . ctfteam = = 1 )
{
if ( ! players [ i ] . gotflag )
{
redarray [ red ] = i ; //store the player's node.
red + + ;
}
else
redflagcarrier + + ;
}
else
{
if ( ! players [ i ] . gotflag )
{
bluearray [ blue ] = i ; //store the player's node.
blue + + ;
}
else
blueflagcarrier + + ;
}
}
}
totalred = red + redflagcarrier ;
totalblue = blue + blueflagcarrier ;
2017-07-04 12:17:29 +00:00
if ( ( abs ( totalred - totalblue ) > max ( 1 , ( totalred + totalblue ) / 8 ) ) )
2014-03-15 16:59:03 +00:00
{
if ( totalred > totalblue )
{
2016-03-27 14:33:15 +00:00
i = M_RandomKey ( red ) ;
2014-03-15 16:59:03 +00:00
NetPacket . packet . newteam = 2 ;
NetPacket . packet . playernum = redarray [ i ] ;
NetPacket . packet . verification = true ;
NetPacket . packet . autobalance = true ;
usvalue = SHORT ( NetPacket . value . l | NetPacket . value . b ) ;
SendNetXCmd ( XD_TEAMCHANGE , & usvalue , sizeof ( usvalue ) ) ;
}
2017-07-04 12:17:29 +00:00
else //if (totalblue > totalred)
2014-03-15 16:59:03 +00:00
{
2016-03-27 14:33:15 +00:00
i = M_RandomKey ( blue ) ;
2014-03-15 16:59:03 +00:00
NetPacket . packet . newteam = 1 ;
NetPacket . packet . playernum = bluearray [ i ] ;
NetPacket . packet . verification = true ;
NetPacket . packet . autobalance = true ;
usvalue = SHORT ( NetPacket . value . l | NetPacket . value . b ) ;
SendNetXCmd ( XD_TEAMCHANGE , & usvalue , sizeof ( usvalue ) ) ;
}
}
}
//
// P_DoTeamscrambling()
//
// If a team scramble has been started, scramble one person from the
// pre-made scramble array. Said array is created in TeamScramble_OnChange()
//
void P_DoTeamscrambling ( void )
{
changeteam_union NetPacket ;
UINT16 usvalue ;
NetPacket . value . l = NetPacket . value . b = 0 ;
// Only do it if we have enough room in the net buffer to send it.
// Otherwise, come back next time and try again.
if ( sizeof ( usvalue ) > GetFreeXCmdSize ( ) )
return ;
if ( scramblecount < scrambletotal )
{
if ( players [ scrambleplayers [ scramblecount ] ] . ctfteam ! = scrambleteams [ scramblecount ] )
{
NetPacket . packet . newteam = scrambleteams [ scramblecount ] ;
NetPacket . packet . playernum = scrambleplayers [ scramblecount ] ;
NetPacket . packet . verification = true ;
NetPacket . packet . scrambled = true ;
usvalue = SHORT ( NetPacket . value . l | NetPacket . value . b ) ;
SendNetXCmd ( XD_TEAMCHANGE , & usvalue , sizeof ( usvalue ) ) ;
}
scramblecount + + ; //Increment, and get to the next player when we come back here next time.
}
else
CV_SetValue ( & cv_teamscramble , 0 ) ;
}
static inline void P_DoSpecialStageStuff ( void )
{
2018-06-03 21:41:54 +00:00
boolean stillalive = false ;
2014-03-15 16:59:03 +00:00
INT32 i ;
// Can't drown in a special stage
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
if ( ! playeringame [ i ] | | players [ i ] . spectator )
continue ;
players [ i ] . powers [ pw_underwater ] = players [ i ] . powers [ pw_spacetime ] = 0 ;
}
2018-06-03 21:41:54 +00:00
//if (sstimer < 15*TICRATE+6 && sstimer > 7 && (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC))
//S_SpeedMusic(1.4f);
2014-03-15 16:59:03 +00:00
2018-06-03 21:41:54 +00:00
if ( sstimer & & ! objectplacing )
2014-03-15 16:59:03 +00:00
{
2018-06-03 21:41:54 +00:00
UINT16 countspheres = 0 ;
2014-03-15 16:59:03 +00:00
// Count up the rings of all the players and see if
// they've collected the required amount.
for ( i = 0 ; i < MAXPLAYERS ; i + + )
2019-11-27 09:03:21 +00:00
if ( playeringame [ i ] )
2014-03-15 16:59:03 +00:00
{
2018-06-03 21:41:54 +00:00
tic_t oldnightstime = players [ i ] . nightstime ;
countspheres + = players [ i ] . spheres ;
2014-03-15 16:59:03 +00:00
2019-11-27 09:03:21 +00:00
if ( ! oldnightstime )
continue ;
2014-03-15 16:59:03 +00:00
// If in water, deplete timer 6x as fast.
2021-11-27 13:22:21 +00:00
if ( players [ i ] . mo - > eflags & ( MFE_TOUCHWATER | MFE_UNDERWATER ) & & ! ( players [ i ] . powers [ pw_shield ] & ( ( players [ i ] . mo - > eflags & MFE_TOUCHLAVA ) ? SH_PROTECTFIRE : SH_PROTECTWATER ) ) )
2018-06-03 21:41:54 +00:00
players [ i ] . nightstime - = 5 ;
if ( - - players [ i ] . nightstime > 6 )
{
if ( P_IsLocalPlayer ( & players [ i ] ) & & oldnightstime > 10 * TICRATE & & players [ i ] . nightstime < = 10 * TICRATE )
S_ChangeMusicInternal ( " _drown " , false ) ;
stillalive = true ;
}
else if ( ! players [ i ] . exiting )
2014-03-15 16:59:03 +00:00
{
players [ i ] . exiting = ( 14 * TICRATE ) / 5 + 1 ;
2018-06-03 21:41:54 +00:00
players [ i ] . pflags & = ~ ( PF_GLIDING | PF_BOUNCING ) ;
players [ i ] . nightstime = 0 ;
if ( P_IsLocalPlayer ( & players [ i ] ) )
S_StartSound ( NULL , sfx_s3k66 ) ;
2014-03-15 16:59:03 +00:00
}
2018-06-03 21:41:54 +00:00
}
2014-03-15 16:59:03 +00:00
2018-06-03 21:41:54 +00:00
if ( stillalive )
{
if ( countspheres > = ssspheres )
{
// Halt all the players
for ( i = 0 ; i < MAXPLAYERS ; i + + )
2019-11-25 14:33:15 +00:00
if ( playeringame [ i ] & & ! players [ i ] . exiting )
2018-06-03 21:41:54 +00:00
{
players [ i ] . mo - > momx = players [ i ] . mo - > momy = 0 ;
players [ i ] . exiting = ( 14 * TICRATE ) / 5 + 1 ;
}
sstimer = 0 ;
P_GiveEmerald ( true ) ;
2018-06-09 20:12:56 +00:00
P_RestoreMusic ( & players [ consoleplayer ] ) ;
2018-06-03 21:41:54 +00:00
}
2014-03-15 16:59:03 +00:00
}
2018-06-03 21:41:54 +00:00
else
sstimer = 0 ;
2014-03-15 16:59:03 +00:00
}
}
static inline void P_DoTagStuff ( void )
{
INT32 i ;
// tell the netgame who the initial IT person is.
if ( leveltime = = TICRATE )
{
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
if ( players [ i ] . pflags & PF_TAGIT )
{
CONS_Printf ( M_GetText ( " %s is now IT! \n " ) , player_names [ i ] ) ; // Tell everyone who is it!
break ;
}
}
}
//increment survivor scores
if ( leveltime % TICRATE = = 0 & & leveltime > ( hidetime * TICRATE ) )
{
2014-11-12 00:55:07 +00:00
INT32 participants = 0 ;
2014-03-15 16:59:03 +00:00
2014-11-12 00:55:07 +00:00
for ( i = 0 ; i < MAXPLAYERS ; i + + )
2014-03-15 16:59:03 +00:00
{
2014-11-12 00:55:07 +00:00
if ( playeringame [ i ] & & ! players [ i ] . spectator )
participants + + ;
2014-03-15 16:59:03 +00:00
}
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
2014-11-12 00:55:07 +00:00
if ( playeringame [ i ] & & ! players [ i ] . spectator & & players [ i ] . playerstate = = PST_LIVE
2017-09-15 19:34:46 +00:00
& & ! ( players [ i ] . pflags & ( PF_TAGIT | PF_GAMETYPEOVER ) ) )
2014-11-12 00:55:07 +00:00
//points given is the number of participating players divided by two.
P_AddPlayerScore ( & players [ i ] , participants / 2 ) ;
2014-03-15 16:59:03 +00:00
}
}
}
static inline void P_DoCTFStuff ( void )
{
// Automatic team balance for CTF and team match
if ( leveltime % ( TICRATE * 5 ) = = 0 ) //only check once per five seconds for the sake of CPU conservation.
{
// Do not attempt to autobalance and scramble teams at the same time.
// Only the server should execute this. No verified admins, please.
if ( ( cv_autobalance . value & & ! cv_teamscramble . value ) & & cv_allowteamchange . value & & server )
P_DoAutobalanceTeams ( ) ;
}
// Team scramble code for team match and CTF.
if ( ( leveltime % ( TICRATE / 7 ) ) = = 0 )
{
// If we run out of time in the level, the beauty is that
// the Y_Ticker() team scramble code will pick it up.
if ( cv_teamscramble . value & & server )
P_DoTeamscrambling ( ) ;
}
}
//
// P_Ticker
//
void P_Ticker ( boolean run )
{
INT32 i ;
2020-01-22 02:05:08 +00:00
// Increment jointime and quittime even if paused
2014-03-15 16:59:03 +00:00
for ( i = 0 ; i < MAXPLAYERS ; i + + )
if ( playeringame [ i ] )
2020-01-22 02:05:08 +00:00
{
players [ i ] . jointime + + ;
if ( players [ i ] . quittime )
{
players [ i ] . quittime + + ;
2020-01-23 18:58:13 +00:00
2020-01-28 13:02:36 +00:00
if ( players [ i ] . quittime = = 30 * TICRATE & & G_TagGametype ( ) )
2020-01-23 18:58:13 +00:00
P_CheckSurvivors ( ) ;
2020-01-25 00:44:53 +00:00
if ( server & & players [ i ] . quittime > = ( tic_t ) FixedMul ( cv_rejointimeout . value , 60 * TICRATE )
2020-01-23 18:59:41 +00:00
& & ! ( players [ i ] . quittime % TICRATE ) )
2020-01-22 02:05:08 +00:00
SendKick ( i , KICK_MSG_PLAYER_QUIT ) ;
}
}
2014-03-15 16:59:03 +00:00
if ( objectplacing )
{
if ( OP_FreezeObjectplace ( ) )
{
P_MapStart ( ) ;
OP_ObjectplaceMovement ( & players [ 0 ] ) ;
P_MoveChaseCamera ( & players [ 0 ] , & camera , false ) ;
P_MapEnd ( ) ;
2019-08-04 11:03:57 +00:00
S_SetStackAdjustmentStart ( ) ;
2014-03-15 16:59:03 +00:00
return ;
}
}
// Check for pause or menu up in single player
2015-01-01 19:50:31 +00:00
if ( paused | | P_AutoPause ( ) )
2019-08-04 11:03:57 +00:00
{
S_SetStackAdjustmentStart ( ) ;
2014-03-15 16:59:03 +00:00
return ;
2019-08-04 11:03:57 +00:00
}
if ( ! S_MusicPaused ( ) )
S_AdjustMusicStackTics ( ) ;
2014-03-15 16:59:03 +00:00
postimgtype = postimgtype2 = postimg_none ;
P_MapStart ( ) ;
if ( run )
{
2022-04-16 07:40:10 +00:00
P_UpdateMobjInterpolators ( ) ;
2014-03-15 16:59:03 +00:00
if ( demorecording )
G_WriteDemoTiccmd ( & players [ consoleplayer ] . cmd , 0 ) ;
if ( demoplayback )
2020-11-09 23:55:00 +00:00
{
2020-11-10 09:17:25 +00:00
player_t * p = & players [ consoleplayer ] ;
G_ReadDemoTiccmd ( & p - > cmd , 0 ) ;
2020-11-10 11:06:47 +00:00
if ( ! cv_freedemocamera . value )
{
P_ForceLocalAngle ( p , p - > cmd . angleturn < < 16 ) ;
localaiming = p - > aiming ;
}
2020-11-09 23:55:00 +00:00
}
2014-03-15 16:59:03 +00:00
2021-10-25 17:49:15 +00:00
ps_lua_mobjhooks . value . i = 0 ;
ps_checkposition_calls . value . i = 0 ;
2020-10-10 18:08:24 +00:00
2020-12-12 11:06:57 +00:00
LUA_HOOK ( PreThinkFrame ) ;
2019-12-31 03:11:49 +00:00
2021-10-25 17:49:15 +00:00
PS_START_TIMING ( ps_playerthink_time ) ;
2014-03-15 16:59:03 +00:00
for ( i = 0 ; i < MAXPLAYERS ; i + + )
if ( playeringame [ i ] & & players [ i ] . mo & & ! P_MobjWasRemoved ( players [ i ] . mo ) )
P_PlayerThink ( & players [ i ] ) ;
2021-10-25 17:49:15 +00:00
PS_STOP_TIMING ( ps_playerthink_time ) ;
2014-03-15 16:59:03 +00:00
}
// Keep track of how long they've been playing!
2018-05-08 22:38:28 +00:00
if ( ! demoplayback ) // Don't increment if a demo is playing.
2018-05-08 22:36:47 +00:00
totalplaytime + + ;
2014-03-15 16:59:03 +00:00
2018-06-03 21:41:54 +00:00
if ( ! ( maptol & TOL_NIGHTS ) & & G_IsSpecialStage ( gamemap ) )
2014-03-15 16:59:03 +00:00
P_DoSpecialStageStuff ( ) ;
if ( runemeraldmanager )
P_EmeraldManager ( ) ; // Power stone mode
if ( run )
{
2021-10-25 17:49:15 +00:00
PS_START_TIMING ( ps_thinkertime ) ;
2014-03-15 16:59:03 +00:00
P_RunThinkers ( ) ;
2021-10-25 17:49:15 +00:00
PS_STOP_TIMING ( ps_thinkertime ) ;
2014-03-15 16:59:03 +00:00
// Run any "after all the other thinkers" stuff
for ( i = 0 ; i < MAXPLAYERS ; i + + )
if ( playeringame [ i ] & & players [ i ] . mo & & ! P_MobjWasRemoved ( players [ i ] . mo ) )
P_PlayerAfterThink ( & players [ i ] ) ;
2021-10-25 17:49:15 +00:00
PS_START_TIMING ( ps_lua_thinkframe_time ) ;
2020-11-17 12:14:45 +00:00
LUA_HookThinkFrame ( ) ;
2021-10-25 17:49:15 +00:00
PS_STOP_TIMING ( ps_lua_thinkframe_time ) ;
2014-03-15 16:59:03 +00:00
}
// Run shield positioning
P_RunShields ( ) ;
2016-02-09 10:39:16 +00:00
P_RunOverlays ( ) ;
2014-03-15 16:59:03 +00:00
P_UpdateSpecials ( ) ;
P_RespawnSpecials ( ) ;
// Lightning, rain sounds, etc.
P_PrecipitationEffects ( ) ;
if ( run )
leveltime + + ;
timeinmap + + ;
if ( G_TagGametype ( ) )
P_DoTagStuff ( ) ;
if ( G_GametypeHasTeams ( ) )
P_DoCTFStuff ( ) ;
if ( run )
{
2020-02-23 19:50:36 +00:00
if ( countdowntimer & & G_PlatformGametype ( ) & & ( ( gametyperules & GTR_CAMPAIGN ) | | leveltime > = 4 * TICRATE ) & & ! stoppedclock & & - - countdowntimer < = 0 )
2014-03-15 16:59:03 +00:00
{
countdowntimer = 0 ;
countdowntimeup = true ;
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
if ( ! playeringame [ i ] | | players [ i ] . spectator )
continue ;
if ( ! players [ i ] . mo )
continue ;
2017-07-04 13:58:58 +00:00
if ( multiplayer | | netgame )
players [ i ] . exiting = 0 ;
2015-02-13 16:15:58 +00:00
P_DamageMobj ( players [ i ] . mo , NULL , NULL , 1 , DMG_INSTAKILL ) ;
2014-03-15 16:59:03 +00:00
}
}
if ( countdown > 1 )
countdown - - ;
if ( countdown2 )
countdown2 - - ;
if ( quake . time )
{
fixed_t ir = quake . intensity > > 1 ;
/// \todo Calculate distance from epicenter if set and modulate the intensity accordingly based on radius.
quake . x = M_RandomRange ( - ir , ir ) ;
quake . y = M_RandomRange ( - ir , ir ) ;
quake . z = M_RandomRange ( - ir , ir ) ;
- - quake . time ;
}
else
quake . x = quake . y = quake . z = 0 ;
if ( metalplayback )
G_ReadMetalTic ( metalplayback ) ;
if ( metalrecording )
G_WriteMetalTic ( players [ consoleplayer ] . mo ) ;
if ( demorecording )
G_WriteGhostTic ( players [ consoleplayer ] . mo ) ;
if ( demoplayback ) // Use Ghost data for consistency checks.
G_ConsGhostTic ( ) ;
2014-11-12 00:55:07 +00:00
if ( modeattacking )
2014-03-15 16:59:03 +00:00
G_GhostTicker ( ) ;
2019-12-31 03:04:27 +00:00
2020-12-12 11:06:57 +00:00
LUA_HOOK ( PostThinkFrame ) ;
2014-03-15 16:59:03 +00:00
}
2022-04-13 01:45:49 +00:00
if ( run )
{
R_UpdateLevelInterpolators ( ) ;
}
2014-03-15 16:59:03 +00:00
P_MapEnd ( ) ;
// Z_CheckMemCleanup();
}
// Abbreviated ticker for pre-loading, calls thinkers and assorted things
void P_PreTicker ( INT32 frames )
{
INT32 i , framecnt ;
ticcmd_t temptic ;
postimgtype = postimgtype2 = postimg_none ;
2020-05-15 15:33:20 +00:00
if ( marathonmode & MA_INGAME )
marathonmode | = MA_INIT ;
2014-03-15 16:59:03 +00:00
for ( framecnt = 0 ; framecnt < frames ; + + framecnt )
{
P_MapStart ( ) ;
2020-12-12 11:06:57 +00:00
LUA_HOOK ( PreThinkFrame ) ;
2020-03-19 17:36:14 +00:00
2014-03-15 16:59:03 +00:00
for ( i = 0 ; i < MAXPLAYERS ; i + + )
if ( playeringame [ i ] & & players [ i ] . mo & & ! P_MobjWasRemoved ( players [ i ] . mo ) )
{
// stupid fucking cmd hack
// if it isn't for this, players can move in preticker time
// (and disrupt demo recording and other things !!)
memcpy ( & temptic , & players [ i ] . cmd , sizeof ( ticcmd_t ) ) ;
memset ( & players [ i ] . cmd , 0 , sizeof ( ticcmd_t ) ) ;
// correct angle on spawn...
2020-05-28 09:03:35 +00:00
players [ i ] . angleturn + = temptic . angleturn - players [ i ] . oldrelangleturn ;
players [ i ] . oldrelangleturn = temptic . angleturn ;
players [ i ] . cmd . angleturn = players [ i ] . angleturn ;
2014-03-15 16:59:03 +00:00
P_PlayerThink ( & players [ i ] ) ;
memcpy ( & players [ i ] . cmd , & temptic , sizeof ( ticcmd_t ) ) ;
}
P_RunThinkers ( ) ;
// Run any "after all the other thinkers" stuff
for ( i = 0 ; i < MAXPLAYERS ; i + + )
if ( playeringame [ i ] & & players [ i ] . mo & & ! P_MobjWasRemoved ( players [ i ] . mo ) )
P_PlayerAfterThink ( & players [ i ] ) ;
2020-11-17 12:14:45 +00:00
LUA_HookThinkFrame ( ) ;
2014-03-15 16:59:03 +00:00
// Run shield positioning
P_RunShields ( ) ;
2016-02-09 10:39:16 +00:00
P_RunOverlays ( ) ;
2014-03-15 16:59:03 +00:00
P_UpdateSpecials ( ) ;
P_RespawnSpecials ( ) ;
2020-12-12 11:06:57 +00:00
LUA_HOOK ( PostThinkFrame ) ;
2019-12-31 03:04:27 +00:00
2014-03-15 16:59:03 +00:00
P_MapEnd ( ) ;
}
2020-05-15 15:33:20 +00:00
if ( marathonmode & MA_INGAME )
marathonmode & = ~ MA_INIT ;
2014-03-15 16:59:03 +00:00
}