2014-10-09 18:15:26 +00:00
// Copyright (C) 1999-2000 Id Software, Inc.
//
# include "g_active.h"
# include "g_client.h"
# include "g_spawn.h"
# include "g_cmds.h"
# include "g_items.h"
# include "g_combat.h"
# include "g_lua.h"
# include "g_mover.h"
# include "g_syscalls.h"
2014-10-24 23:00:31 +00:00
# include "g_logger.h"
2014-10-09 18:15:26 +00:00
2014-11-10 19:56:17 +00:00
extern void ammo_station_finish_spawning ( gentity_t * self ) ;
2014-10-09 18:15:26 +00:00
/*
= = = = = = = = = = = = = =
TryUse
= = = = = = = = = = = = = =
*/
2014-11-10 19:56:17 +00:00
static const double USE_DISTANCE = 64.0 ;
2014-10-09 18:15:26 +00:00
/**
* Try and use an entity in the world , directly ahead of us
*/
2014-11-10 19:56:17 +00:00
static void TryUse ( gentity_t * ent )
2014-10-09 18:15:26 +00:00
{
2018-06-30 14:47:06 +00:00
gentity_t * target ;
trace_t trace ;
vec3_t src , dest , vf ;
clientSession_t * sess ;
if ( ent = = NULL )
{
return ;
}
sess = & ent - > client - > sess ;
VectorCopy ( ent - > r . currentOrigin , src ) ;
src [ 2 ] + = ent - > client - > ps . viewheight * ent - > client - > pers . pms_height ; //TiM - include height offset for tall players
AngleVectors ( ent - > client - > ps . viewangles , vf , NULL , NULL ) ;
//extend to find end of use trace
VectorMA ( src , - 6 , vf , src ) ; //in case we're inside something?
VectorMA ( src , 134 , vf , dest ) ; //128+6
//Trace ahead to find a valid target
memset ( & trace , 0 , sizeof ( trace_t ) ) ;
trap_Trace ( & trace , src , vec3_origin , vec3_origin , dest , ent - > s . number , MASK_OPAQUE | CONTENTS_BODY | CONTENTS_ITEM | CONTENTS_CORPSE ) ;
if ( trace . fraction = = 1.0f | | trace . entityNum < 0 )
{
//FIXME: Play a failure sound
return ;
}
target = & g_entities [ trace . entityNum ] ;
//Check for a use command
2018-07-18 18:13:45 +00:00
if ( ( target ! = NULL ) & & ( target - > use ! = NULL ) & & ( target - > type = = EntityType : : ENT_FUNC_USABLE ) )
2018-06-30 14:47:06 +00:00
{ //usable brush
if ( target - > team & & atoi ( target - > team ) ! = 0 )
{ //usable has a team
if ( atoi ( target - > team ) ! = sess - > sessionTeam )
{ //not on my team
//TiM - return
return ;
}
}
//FIXME: play sound?
target - > use ( target , ent , ent ) ;
2014-10-09 18:15:26 +00:00
# ifdef G_LUA
2018-06-30 14:47:06 +00:00
if ( target - > luaUse )
LuaHook_G_EntityUse ( target - > luaUse , target - g_entities , ent - g_entities , ent - g_entities ) ;
2014-10-09 18:15:26 +00:00
# endif
2018-06-30 14:47:06 +00:00
return ;
}
2018-07-18 18:13:45 +00:00
else if ( ( target ! = NULL ) & & ( target - > use ! = NULL ) & & ( ent - > type = = EntityType : : ENT_MISC_AMMOSTATION ) )
2018-06-30 14:47:06 +00:00
{ //ammo station
if ( sess - > sessionTeam )
{
if ( target - > team )
{
if ( atoi ( target - > team ) ! = sess - > sessionTeam )
{
//FIXME: play sound?
return ;
}
}
}
target - > use ( target , ent , ent ) ;
2014-10-09 18:15:26 +00:00
# ifdef G_LUA
2018-06-30 14:47:06 +00:00
if ( target - > luaUse )
LuaHook_G_EntityUse ( target - > luaUse , target - g_entities , ent - g_entities , ent - g_entities ) ;
2014-10-09 18:15:26 +00:00
# endif
2018-06-30 14:47:06 +00:00
return ;
}
else if ( ( target & & target - > s . number = = ENTITYNUM_WORLD ) | | ( target - > s . pos . trType = = TR_STATIONARY & & ! ( trace . surfaceFlags & SURF_NOIMPACT ) & & ! target - > takedamage ) )
{
return ;
}
//FIXME: Play a failure sound
2014-10-09 18:15:26 +00:00
}
/*
= = = = = = = = = = = = = = =
P_DamageFeedback
= = = = = = = = = = = = = = =
*/
/**
* Called just before a snapshot is sent to the given player .
* Totals up all damage and generates both the player_state_t
* damage values to that client for pain blends and kicks , and
* global pain sound events for all clients .
*/
2018-06-30 14:47:06 +00:00
static void P_DamageFeedback ( gentity_t * player )
{
gclient_t * client ;
float count ;
vec3_t angles ;
playerState_t * ps ;
client = player - > client ;
ps = & client - > ps ;
if ( client - > ps . pm_type = = PM_DEAD )
{
return ;
}
// total points of damage shot at the player this frame
count = client - > damage_blood + client - > damage_armor ;
if ( count = = 0 )
{
return ; // didn't take any damage
}
if ( count > 255 )
{
count = 255 ;
}
// send the information to the client
// world damage (falling, slime, etc) uses a special code
// to make the blend blob centered instead of positional
if ( client - > damage_fromWorld )
{
ps - > damagePitch = 255 ;
ps - > damageYaw = 255 ;
client - > damage_fromWorld = qfalse ;
}
else
{
vectoangles ( client - > damage_from , angles ) ;
ps - > damagePitch = angles [ PITCH ] / 360.0 * 256 ;
ps - > damageYaw = angles [ YAW ] / 360.0 * 256 ;
}
// play an apropriate pain sound
if ( ( level . time > player - > pain_debounce_time ) & & ! ( player - > flags & FL_GODMODE ) )
{
player - > pain_debounce_time = level . time + 700 ;
G_AddEvent ( player , EV_PAIN , player - > health ) ;
ps - > damageEvent + + ;
}
ps - > damageCount = client - > damage_blood ;
if ( ps - > damageCount > 255 )
{
ps - > damageCount = 255 ;
}
ps - > damageShieldCount = client - > damage_armor ;
if ( ps - > damageShieldCount > 255 )
{
ps - > damageShieldCount = 255 ;
}
//
// clear totals
//
client - > damage_blood = 0 ;
client - > damage_armor = 0 ;
client - > damage_knockback = 0 ;
2014-10-09 18:15:26 +00:00
}
/*
= = = = = = = = = = = = =
P_WorldEffects
= = = = = = = = = = = = =
*/
/**
* Check for lava / slime contents and drowning
*/
2018-06-30 14:47:06 +00:00
static void P_WorldEffects ( gentity_t * ent )
{
int32_t waterlevel ;
if ( ent - > client - > noclip )
{
ent - > client - > airOutTime = level . time + 12000 ; // don't need air
return ;
}
waterlevel = ent - > waterlevel ;
//
// check for drowning
//
if ( waterlevel = = 3 & & ! ( ent - > watertype & CONTENTS_LADDER ) )
{
// envirosuit give air, techs can't drown
if ( g_classData [ ent - > client - > sess . sessionClass ] . isMarine )
{
ent - > client - > airOutTime = level . time + 10000 ;
}
// if out of air, start drowning
if ( ent - > client - > airOutTime < level . time )
{
// drown!
ent - > client - > airOutTime + = 1000 ;
if ( ent - > health > 1 )
{ //TiM : used to be 0, but to fix red's medic code
// take more damage the longer underwater
ent - > damage + = 2 ;
if ( ent - > damage > 15 )
ent - > damage = 15 ;
// play a gurp sound instead of a normal pain sound
if ( ent - > health < = ent - > damage )
{
G_Sound ( ent , G_SoundIndex ( " *drown.wav " ) ) ;
}
else if ( rand ( ) & 1 )
{
G_Sound ( ent , G_SoundIndex ( " sound/player/gurp1.wav " ) ) ;
}
else
{
G_Sound ( ent , G_SoundIndex ( " sound/player/gurp2.wav " ) ) ;
}
// don't play a normal pain sound
ent - > pain_debounce_time = level . time + 200 ;
G_Combat_Damage ( ent , NULL , NULL , NULL , NULL ,
ent - > damage , DAMAGE_NO_ARMOR , MOD_WATER ) ;
}
}
}
else
{
ent - > client - > airOutTime = level . time + 12000 ;
ent - > damage = 2 ;
}
//
// check for sizzle damage (move to pmove?)
//
if ( waterlevel & &
( ent - > watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) )
{
if ( ent - > health > 0
& & ent - > pain_debounce_time < level . time )
{
if ( ent - > watertype & CONTENTS_LAVA )
{
G_Combat_Damage ( ent , NULL , NULL , NULL , NULL ,
30 * waterlevel , 0 , MOD_LAVA ) ;
}
if ( ent - > watertype & CONTENTS_SLIME )
{
G_Combat_Damage ( ent , NULL , NULL , NULL , NULL ,
10 * waterlevel , 0 , MOD_SLIME ) ;
}
}
}
2014-10-09 18:15:26 +00:00
}
/*
= = = = = = = = = = = = = = =
G_SetClientSound
= = = = = = = = = = = = = = =
*/
2014-11-10 19:56:17 +00:00
static void G_SetClientSound ( gentity_t * ent )
2014-10-09 18:15:26 +00:00
{ // 3/28/00 kef -- this is dumb.
2018-06-30 14:47:06 +00:00
if ( ent - > waterlevel & & ( ent - > watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) )
ent - > s . loopSound = level . snd_fry ;
else
ent - > s . loopSound = 0 ;
2014-10-09 18:15:26 +00:00
}
//==============================================================
/*
= = = = = = = = = = = = = =
ClientImpacts
= = = = = = = = = = = = = =
*/
2018-06-30 14:47:06 +00:00
static void ClientImpacts ( gentity_t * ent , pmove_t * pm )
{
int32_t i , j ;
trace_t trace ;
gentity_t * other ;
memset ( & trace , 0 , sizeof ( trace ) ) ;
for ( i = 0 ; i < pm - > numtouch ; i + + )
{
for ( j = 0 ; j < i ; j + + )
{
if ( pm - > touchents [ j ] = = pm - > touchents [ i ] )
{
break ;
}
}
if ( j ! = i )
{
continue ; // duplicated
}
other = & g_entities [ pm - > touchents [ i ] ] ;
if ( ( ent - > r . svFlags & SVF_BOT ) & & ( ent - > touch ) )
{
ent - > touch ( ent , other , & trace ) ;
}
if ( ! other - > touch )
{
continue ;
}
other - > touch ( other , ent , & trace ) ;
}
2014-10-09 18:15:26 +00:00
}
2018-06-30 14:47:06 +00:00
void G_TouchTriggers ( gentity_t * ent )
{
int32_t i , num ;
int32_t touch [ MAX_GENTITIES ] ;
gentity_t * hit ;
trace_t trace ;
vec3_t mins , maxs ;
vec3_t range = { 40 , 40 , 52 } ;
playerState_t * ps ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
if ( ent = = NULL | | ent - > client = = NULL )
{
return ;
}
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
ps = & ent - > client - > ps ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
// dead clients don't activate triggers!
if ( ps - > stats [ STAT_HEALTH ] < = 0 )
{
return ;
}
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
VectorSubtract ( ps - > origin , range , mins ) ;
VectorAdd ( ps - > origin , range , maxs ) ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
num = trap_EntitiesInBox ( mins , maxs , touch , MAX_GENTITIES ) ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
// can't use ent->absmin, because that has a one unit pad
VectorAdd ( ps - > origin , ent - > r . mins , mins ) ;
VectorAdd ( ps - > origin , ent - > r . maxs , maxs ) ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
for ( i = 0 ; i < num ; i + + )
{
hit = & g_entities [ touch [ i ] ] ;
2014-10-09 18:15:26 +00:00
# ifdef G_LUA
2018-06-30 14:47:06 +00:00
if ( hit - > luaTouch )
{
LuaHook_G_EntityTouch ( hit - > luaTouch , hit - > s . number , ent - > s . number ) ;
}
2014-10-09 18:15:26 +00:00
# endif
2018-06-30 14:47:06 +00:00
if ( ! hit - > touch & & ! ent - > touch )
{
continue ;
}
if ( ! ( hit - > r . contents & CONTENTS_TRIGGER ) )
{
continue ;
}
// ignore most entities if a spectator
if ( ent - > client - > sess . sessionTeam = = TEAM_SPECTATOR )
{
// this is ugly but adding a new ET_? type will
// most likely cause network incompatibilities
if ( hit - > s . eType ! = ET_TELEPORT_TRIGGER & & hit - > touch ! = G_Mover_TouchDoorTrigger )
{
continue ;
}
}
// use seperate code for determining if an item is picked up
// so you don't have to actually contact its bounding box
if ( hit - > s . eType = = ET_ITEM )
{
if ( ! BG_PlayerTouchesItem ( & ent - > client - > ps , & hit - > s , level . time ) )
{
continue ;
}
}
else
{
if ( ! trap_EntityContact ( mins , maxs , hit ) )
{
continue ;
}
}
memset ( & trace , 0 , sizeof ( trace ) ) ;
if ( hit - > touch )
{
hit - > touch ( hit , ent , & trace ) ;
}
if ( ( ent - > r . svFlags & SVF_BOT ) & & ( ent - > touch ) )
{
ent - > touch ( ent , hit , & trace ) ;
}
}
2014-10-09 18:15:26 +00:00
}
/*
= = = = = = = = = = = = = = = = =
SpectatorThink
= = = = = = = = = = = = = = = = =
*/
2018-06-30 14:47:06 +00:00
static void SpectatorThink ( gentity_t * ent , usercmd_t * ucmd )
{
pmove_t pm ;
gclient_t * client ;
client = ent - > client ;
if ( client - > sess . spectatorState ! = SPECTATOR_FOLLOW )
{
client - > ps . pm_type = PM_SPECTATOR ;
client - > ps . speed = 400 ; // faster than normal
// set up for pmove
memset ( & pm , 0 , sizeof ( pm ) ) ;
pm . ps = & client - > ps ;
pm . cmd = * ucmd ;
pm . tracemask = MASK_PLAYERSOLID & ~ CONTENTS_BODY ; // spectators can fly through bodies
pm . trace = trap_Trace ;
pm . pointcontents = trap_PointContents ;
// perform a pmove
Pmove ( & pm ) ;
// save results of pmove
VectorCopy ( client - > ps . origin , ent - > s . origin ) ;
G_TouchTriggers ( ent ) ;
trap_UnlinkEntity ( ent ) ;
}
client - > oldbuttons = client - > buttons ;
client - > buttons = ucmd - > buttons ;
// attack button cycles through spectators
if ( ( client - > buttons & BUTTON_ATTACK ) & & ! ( client - > oldbuttons & BUTTON_ATTACK ) )
{
Cmd_FollowCycle_f ( ent , 1 ) ;
}
else if ( ( client - > buttons & BUTTON_ALT_ATTACK ) & & ! ( client - > oldbuttons & BUTTON_ALT_ATTACK ) )
{
if ( ent - > client - > sess . spectatorState = = SPECTATOR_FOLLOW )
{
StopFollowing ( ent ) ;
}
}
2014-10-09 18:15:26 +00:00
}
/*
= = = = = = = = = = = = = = = = =
ClientInactivityTimer
= = = = = = = = = = = = = = = = =
*/
/**
* Returns qfalse if the client is dropped
*/
2014-11-10 19:56:17 +00:00
static qboolean ClientInactivityTimer ( gclient_t * client )
2014-10-09 18:15:26 +00:00
{
2018-06-30 14:47:06 +00:00
usercmd_t * cmd = & client - > pers . cmd ;
if ( g_inactivity . integer = = 0 )
{
// give everyone some time, so if the operator sets g_inactivity during
// gameplay, everyone isn't kicked
client - > inactivityTime = level . time + 60 * 1000 ;
client - > inactivityWarning = qfalse ;
}
else if ( cmd - > forwardmove | |
cmd - > rightmove | |
cmd - > upmove | |
( cmd - > buttons & BUTTON_ATTACK ) | |
( cmd - > buttons & BUTTON_ALT_ATTACK ) )
{
client - > inactivityTime = level . time + g_inactivity . integer * 1000 ;
client - > inactivityWarning = qfalse ;
}
else if ( ! client - > pers . localClient )
{
if ( level . time > client - > inactivityTime )
{
trap_DropClient ( client - level . clients , " Dropped due to inactivity " ) ;
return qfalse ;
}
if ( level . time > client - > inactivityTime - 10000 & & ! client - > inactivityWarning )
{
client - > inactivityWarning = qtrue ;
trap_SendServerCommand ( client - level . clients , " cp \" Ten seconds until inactivity drop! \n \" " ) ;
}
}
return qtrue ;
2014-10-09 18:15:26 +00:00
}
/*
= = = = = = = = = = = = = = = = = =
TimedMessage
RPG - X - RedTechie : Returns the message requested .
If the message is blank go to next . ( Based off of SFEFMOD )
TiM : Huh . . . O_o . Damn Red , you ' re right . If the admin
puts in values , but not in a consistent consecutive order ,
we ' ll get some mighty painful errors . > . <
I guess what we really need is a for loop that goes
thru , and checks to see if each and every CVAR has a value
currently in it . . . .
= = = = = = = = = = = = = = = = = =
*/
/**
* \ author Ubergames
*/
2018-06-30 14:47:06 +00:00
std : : string TimedMessage ( )
{
if ( level . timedMessages . empty ( ) )
{
return " ^1RPG-X ERROR: No messages to display " ;
}
if ( level . timedMessageIndex > = level . timedMessages . size ( ) )
{
level . timedMessageIndex = 0 ;
}
auto message = level . timedMessages . at ( level . timedMessageIndex ) ;
level . timedMessageIndex + + ;
return message ;
2014-10-09 18:15:26 +00:00
}
/*
= = = = = = = = = = = = = = = = = =
ClientTimerActions
= = = = = = = = = = = = = = = = = =
*/
/**
* Actions that happen once a second
*/
2018-06-30 14:47:06 +00:00
static void ClientTimerActions ( gentity_t * ent , int32_t msec )
{
gclient_t * client ;
float messageTime ;
client = ent - > client ;
client - > timeResidual + = msec ;
if ( rpg_timedmessagetime . value > 0.0f )
{
//Make sure its not less then one //TiM: Well... we can have under 1, just not toooo far under 1
if ( rpg_timedmessagetime . value < 0.2f )
{ //1
messageTime = 0.2f ;
}
else
{
messageTime = rpg_timedmessagetime . value ;
}
if ( level . time > ( level . message + ( messageTime * 60000 ) ) )
{
level . message = level . time ;
//TiM - There. So with this working in conjunction with that reset
//code above, this should be more efficient. :)
auto message = " cp \" " + TimedMessage ( ) + " \n \" " ; //Since we're working with a gloabl scope variable, there's no need for this thing to have parameters:)
trap_SendServerCommand ( - 1 , message . c_str ( ) ) ; //Shows the message on their main screen
}
}
while ( client - > timeResidual > = 1000 )
{
client - > timeResidual - = 1000 ;
if ( ent - > health > client - > ps . stats [ STAT_MAX_HEALTH ] )
{
ent - > health - - ;
}
// NOW IT ONCE AGAIN counts down armor when over max, once per second
if ( client - > ps . stats [ STAT_ARMOR ] > client - > ps . stats [ STAT_MAX_HEALTH ] )
{
client - > ps . stats [ STAT_ARMOR ] - - ;
}
if ( ! client - > ps . stats [ STAT_HOLDABLE_ITEM ] )
{ //holding nothing...
if ( client - > ps . stats [ STAT_USEABLE_PLACED ] > 0 )
{ //we're in some kind of countdown
//so count down
client - > ps . stats [ STAT_USEABLE_PLACED ] - - ;
}
}
}
2014-10-09 18:15:26 +00:00
}
/*
= = = = = = = = = = = = = = = = = = = =
ClientIntermissionThink
= = = = = = = = = = = = = = = = = = = =
*/
2018-06-30 14:47:06 +00:00
static void ClientIntermissionThink ( gclient_t * client )
{
client - > ps . eFlags & = ~ EF_TALK ;
client - > ps . eFlags & = ~ EF_FIRING ;
// the level will exit when everyone wants to or after timeouts
// swap and latch button actions
client - > oldbuttons = client - > buttons ;
client - > buttons = client - > pers . cmd . buttons ;
if ( g_gametype . integer ! = GT_SINGLE_PLAYER )
{
if ( client - > buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client - > oldbuttons ^ client - > buttons ) )
{
client - > readyToExit = ( qboolean ) ( ! client - > readyToExit ) ;
}
}
2014-10-09 18:15:26 +00:00
}
typedef struct detHit_s
{
2018-06-30 14:47:06 +00:00
gentity_t * detpack ;
gentity_t * player ;
int32_t time ;
2014-10-09 18:15:26 +00:00
} detHit_t ;
2018-06-30 14:47:06 +00:00
enum g_activeDetPackLimits_e
{
MAX_DETHITS = 32 // never define this to be 0
2014-11-10 19:56:17 +00:00
} ;
2014-10-09 18:15:26 +00:00
static detHit_t detHits [ MAX_DETHITS ] ;
static qboolean bDetInit = qfalse ;
//-----------------------------------------------------------------------------DECOY TEMP
2014-11-10 19:56:17 +00:00
extern qboolean FinishSpawningDecoy ( gentity_t * ent , int32_t itemIndex ) ;
2014-10-09 18:15:26 +00:00
//-----------------------------------------------------------------------------DECOY TEMP
2014-11-10 19:56:17 +00:00
static const uint16_t DETPACK_DAMAGE = 750 ;
static const uint16_t DETPACK_RADIUS = 500 ;
2014-10-09 18:15:26 +00:00
2014-11-10 19:56:17 +00:00
void detpack_shot ( gentity_t * self , gentity_t * inflictor , gentity_t * attacker , int32_t damage , int32_t meansOfDeath )
2014-10-09 18:15:26 +00:00
{
2018-06-30 14:47:06 +00:00
int32_t i = 0 ;
gentity_t * ent = NULL ;
//so we can't be blown up by things we're blowing up
self - > takedamage = qfalse ;
G_TempEntity ( self - > s . origin , EV_GRENADE_EXPLODE ) ;
G_Combat_RadiusDamage ( self - > s . origin , self - > parent ? self - > parent : self , DETPACK_DAMAGE * 0.125 , DETPACK_RADIUS * 0.25 ,
self , DAMAGE_ALL_TEAMS , MOD_DETPACK ) ;
// we're blowing up cuz we've been shot, so make sure we remove ourselves
//from our parent's inventory (so to speak)
for ( i = 0 ; i < MAX_CLIENTS ; i + + )
{
if ( ( ( ent = & g_entities [ i ] ) ! = NULL ) & & ent - > inuse & & ( self - > parent = = ent ) )
{
ent - > client - > ps . stats [ STAT_USEABLE_PLACED ] = 0 ;
ent - > client - > ps . stats [ STAT_HOLDABLE_ITEM ] = 0 ;
break ;
}
}
G_FreeEntity ( self ) ;
2014-10-09 18:15:26 +00:00
}
/**
* Place the detpack
*/
static qboolean PlaceDetpack ( gentity_t * ent )
{
2018-06-30 14:47:06 +00:00
gentity_t * detpack = NULL ;
static gitem_t * detpackItem = NULL ;
float detDistance = 80 ;
trace_t tr ;
vec3_t fwd ;
vec3_t right ;
vec3_t up ;
vec3_t end ;
vec3_t mins = { - 16 , - 16 , - 16 } ;
vec3_t maxs = { 16 , 16 , 16 } ;
playerState_t * ps = & ent - > client - > ps ;
if ( detpackItem = = NULL )
{
detpackItem = BG_FindItemForHoldable ( HI_DETPACK ) ;
}
// make sure our detHit info is init'd
if ( bDetInit = = qfalse )
{
memset ( detHits , 0 , MAX_DETHITS * sizeof ( detHit_t ) ) ;
bDetInit = qtrue ;
}
// can we place this in front of us?
AngleVectors ( ps - > viewangles , fwd , right , up ) ;
fwd [ 2 ] = 0 ;
VectorMA ( ps - > origin , detDistance , fwd , end ) ;
memset ( & tr , 0 , sizeof ( trace_t ) ) ;
trap_Trace ( & tr , ps - > origin , mins , maxs , end , ent - > s . number , MASK_SHOT ) ;
if ( tr . fraction > 0.9 )
{
// got enough room so place the detpack
detpack = G_Spawn ( ) ;
G_SpawnItem ( detpack , detpackItem ) ;
detpack - > physicsBounce = 0.0f ; //detpacks are *not* bouncy
VectorMA ( ps - > origin , detDistance + mins [ 0 ] , fwd , detpack - > s . origin ) ;
if ( FinishSpawningDetpack ( detpack , detpackItem - bg_itemlist ) = = qfalse )
{
return qfalse ;
}
VectorNegate ( fwd , fwd ) ;
vectoangles ( fwd , detpack - > s . angles ) ;
detpack - > think = DetonateDetpack ;
detpack - > nextthink = level . time + 120000 ; // if not used after 2 minutes it blows up anyway
detpack - > parent = ent ;
return qtrue ;
}
else
{
// no room
return qfalse ;
}
2014-10-09 18:15:26 +00:00
}
/**
* Was a player hit by a detpack .
*/
static qboolean PlayerHitByDet ( gentity_t * det , gentity_t * player )
{
2018-06-30 14:47:06 +00:00
int32_t i = 0 ;
if ( ! bDetInit )
{
// detHit stuff not initialized. who knows what's going on?
return qfalse ;
}
for ( i = 0 ; i < MAX_DETHITS ; i + + )
{
if ( ( detHits [ i ] . detpack = = det ) & & ( detHits [ i ] . player = = player ) )
{
return qtrue ;
}
}
return qfalse ;
2014-10-09 18:15:26 +00:00
}
/**
* Addes a player to the detpack hits
*/
static void AddPlayerToDetHits ( gentity_t * det , gentity_t * player )
{
2018-06-30 14:47:06 +00:00
int32_t i = 0 ;
detHit_t * lastHit = NULL ;
detHit_t * curHit = NULL ;
for ( i = 0 ; i < MAX_DETHITS ; i + + )
{
if ( 0 = = detHits [ i ] . time )
{
// empty slot. add our player here.
detHits [ i ] . detpack = det ;
detHits [ i ] . player = player ;
detHits [ i ] . time = level . time ;
}
lastHit = & detHits [ i ] ;
}
// getting here means we've filled our list of detHits, so begin recycling them, starting with the oldest hit.
curHit = & detHits [ 0 ] ;
while ( lastHit - > time < curHit - > time )
{
lastHit = curHit ;
curHit + + ;
// just a safety check here
if ( curHit = = & detHits [ 0 ] )
{
break ;
}
}
curHit - > detpack = det ;
curHit - > player = player ;
curHit - > time = level . time ;
2014-10-09 18:15:26 +00:00
}
/**
* Clear the hits for this detpack
*/
static void ClearThisDetpacksHits ( gentity_t * det )
{
2018-06-30 14:47:06 +00:00
int32_t i = 0 ;
for ( i = 0 ; i < MAX_DETHITS ; i + + )
{
if ( detHits [ i ] . detpack = = det )
{
detHits [ i ] . player = NULL ;
detHits [ i ] . detpack = NULL ;
detHits [ i ] . time = 0 ;
}
}
2014-10-09 18:15:26 +00:00
}
static void DetpackBlammoThink ( gentity_t * ent )
{
2018-06-30 14:47:06 +00:00
int32_t i = 0 ;
int32_t lifetime = 3000 ; // how long (in msec) the shockwave lasts
int32_t knockback = 400 ;
float curBlastRadius = 50.0 * ent - > count ;
float radius = 0 ;
vec3_t vTemp ;
trace_t tr ;
gentity_t * pl = NULL ;
if ( ent - > count + + > ( lifetime * 0.01 ) )
{
ClearThisDetpacksHits ( ent ) ;
G_FreeEntity ( ent ) ;
return ;
}
// get a list of players within the blast radius at this time.
// only hit the ones we can see from the center of the explosion.
for ( i = 0 ; i < MAX_CLIENTS ; i + + )
{
if ( g_entities [ i ] . client & & g_entities [ i ] . takedamage )
{
pl = & g_entities [ i ] ;
VectorSubtract ( pl - > s . pos . trBase , ent - > s . origin , vTemp ) ;
radius = VectorNormalize ( vTemp ) ;
if ( ( radius < = curBlastRadius ) & & ! PlayerHitByDet ( ent , pl ) )
{
// within the proper radius. do we have LOS to the player?
memset ( & tr , 0 , sizeof ( trace_t ) ) ;
trap_Trace ( & tr , ent - > s . origin , NULL , NULL , pl - > s . pos . trBase , ent - > s . number , MASK_SHOT ) ;
if ( tr . entityNum = = i )
{
// oh yeah. you're gettin' hit.
AddPlayerToDetHits ( ent , pl ) ;
VectorMA ( pl - > client - > ps . velocity , knockback , vTemp , pl - > client - > ps . velocity ) ;
// make sure the player goes up some
if ( pl - > client - > ps . velocity [ 2 ] < 100 )
{
pl - > client - > ps . velocity [ 2 ] = 100 ;
}
if ( ! pl - > client - > ps . pm_time )
{
int32_t t ;
t = knockback * 2 ;
if ( t < 50 )
{
t = 50 ;
}
if ( t > 200 )
{
t = 200 ;
}
pl - > client - > ps . pm_time = t ;
pl - > client - > ps . pm_flags | = PMF_TIME_KNOCKBACK ;
}
}
}
}
}
ent - > nextthink = level . time + FRAMETIME ;
2014-10-09 18:15:26 +00:00
}
/**
* Detonate a detpack
*/
2018-06-30 14:47:06 +00:00
void DetonateDetpack ( gentity_t * ent )
{
// find all detpacks. the one whose parent is ent...blow up
gentity_t * detpack = NULL ;
char * classname = BG_FindClassnameForHoldable ( HI_DETPACK ) ;
if ( classname = = NULL )
{
return ;
}
while ( ( detpack = G_Find ( detpack , FOFS ( classname ) , classname ) ) ! = NULL )
{
if ( detpack - > parent = = ent )
{
// found it. BLAMMO!
// play explosion sound to all clients
gentity_t * te = NULL ;
te = G_TempEntity ( detpack - > s . pos . trBase , EV_GLOBAL_SOUND ) ;
te - > s . eventParm = G_SoundIndex ( " sound/weapons/explosions/detpakexplode.wav " ) ; //cgs.media.detpackExplodeSound
te - > r . svFlags | = SVF_BROADCAST ;
//so we can't be blown up by things we're blowing up
detpack - > takedamage = qfalse ;
G_AddEvent ( detpack , EV_DETPACK , 0 ) ;
G_Combat_RadiusDamage ( detpack - > s . origin , detpack - > parent , DETPACK_DAMAGE , DETPACK_RADIUS ,
detpack , DAMAGE_HALF_NOTLOS | DAMAGE_ALL_TEAMS , MOD_DETPACK ) ;
// just turn the model invisible and let the entity think for a bit to deliver a shockwave
//G_FreeEntity(detpack);
detpack - > classname = NULL ;
detpack - > s . modelindex = 0 ;
detpack - > think = DetpackBlammoThink ;
detpack - > count = 1 ;
detpack - > nextthink = level . time + FRAMETIME ;
return ;
}
else if ( detpack = = ent ) // if detpack == ent, we're blowing up this detpack cuz it's been sitting too long
{
detpack_shot ( detpack , NULL , NULL , 0 , 0 ) ;
return ;
}
}
// hmm. couldn't find it.
detpack = NULL ;
2014-10-09 18:15:26 +00:00
}
2014-11-10 19:56:17 +00:00
static const uint8_t SHIELD_HEALTH = 250 ;
static const uint8_t SHIELD_HEALTH_DEC = 10 ; // 25 seconds
static const uint16_t MAX_SHIELD_HEIGHT = 1022 ; //254
static const uint16_t MAX_SHIELD_HALFWIDTH = 1023 ; //255
static const uint8_t SHIELD_HALFTHICKNESS = 4 ;
static const uint8_t SHIELD_PLACEDIST = 64 ;
2014-10-09 18:15:26 +00:00
2014-11-10 19:56:17 +00:00
static qhandle_t shieldAttachSound = 0 ;
static qhandle_t shieldActivateSound = 0 ;
static qhandle_t shieldDamageSound = 0 ;
2014-10-09 18:15:26 +00:00
//RPG-X: - RedTechie Added shild ZAPZPZAAAAAAppPPP sound!
2014-11-10 19:56:17 +00:00
static qhandle_t shieldMurderSound = 0 ;
static qhandle_t shieldDeactivateSound = 0 ;
2014-10-09 18:15:26 +00:00
void G_Active_ShieldRemove ( gentity_t * self )
{
2018-06-30 14:47:06 +00:00
self - > think = G_FreeEntity ;
self - > nextthink = level . time + 300 ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
self - > s . eFlags | = EF_ITEMPLACEHOLDER ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
// Play raising sound...
G_AddEvent ( self , EV_GENERAL_SOUND , shieldDeactivateSound ) ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
return ;
2014-10-09 18:15:26 +00:00
}
/**
* The think function of a forcefield
* Does not do much anymore , once , counted down the health of a forcefield and removed
* it when healt got equal or below zero .
*/
void ShieldThink ( gentity_t * self )
{
2018-06-30 14:47:06 +00:00
self - > s . eFlags & = ~ ( EF_ITEMPLACEHOLDER | EF_NODRAW ) ;
self - > nextthink = 0 ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
return ;
2014-10-09 18:15:26 +00:00
}
/**
* The shield was damaged to below zero health .
*/
void ShieldDie ( gentity_t * self , gentity_t * inflictor , gentity_t * attacker , int32_t damage , int32_t mod )
{
2018-06-30 14:47:06 +00:00
// Play damaging sound...
G_AddEvent ( self , EV_GENERAL_SOUND , shieldDamageSound ) ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
G_Active_ShieldRemove ( self ) ;
2014-10-09 18:15:26 +00:00
}
/**
* The shield had damage done to it . Make it flicker .
*/
void ShieldPain ( gentity_t * self , gentity_t * attacker , int32_t damage )
{
2018-06-30 14:47:06 +00:00
// Set the itemplaceholder flag to indicate the the shield drawing that the shield pain should be drawn.
self - > s . eFlags | = EF_ITEMPLACEHOLDER ;
self - > think = ShieldThink ;
self - > nextthink = level . time + 400 ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
// Play damaging sound...
G_AddEvent ( self , EV_GENERAL_SOUND , shieldDamageSound ) ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
return ;
2014-10-09 18:15:26 +00:00
}
/**
* Try to turn the shield back on after a delay .
*/
void ShieldGoSolid ( gentity_t * self )
{
2018-06-30 14:47:06 +00:00
trace_t tr ;
if ( self - > health < = 0 )
{
G_Active_ShieldRemove ( self ) ;
return ;
}
memset ( & tr , 0 , sizeof ( trace_t ) ) ;
trap_Trace ( & tr , self - > r . currentOrigin , self - > r . mins , self - > r . maxs , self - > r . currentOrigin , self - > s . number , CONTENTS_BODY ) ;
if ( tr . startsolid )
{ // gah, we can't activate yet
self - > nextthink = level . time + 200 ;
self - > think = ShieldGoSolid ;
trap_LinkEntity ( self ) ;
}
else
{ // get hard... huh-huh...
self - > r . contents = CONTENTS_SOLID ;
self - > s . eFlags & = ~ ( EF_ITEMPLACEHOLDER ) ;
self - > nextthink = level . time + 1000 ;
self - > think = ShieldThink ;
self - > takedamage = qtrue ; //RPG-X: - RedTechie use to be qtrue //TiM - made true again. should be okay so long as the health isn't decremented
trap_LinkEntity ( self ) ;
}
return ;
2014-10-09 18:15:26 +00:00
}
/**
* Turn the shield off to allow a friend to pass through .
*/
//RPG-X J2J EDIT here:
void ShieldGoNotSolid ( gentity_t * self )
{
2018-06-30 14:47:06 +00:00
// make the shield non-solid very briefly
self - > r . contents = CONTENTS_NONE ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
// nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages
self - > nextthink = level . time + 200 ;
self - > think = ShieldGoSolid ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
//TiM - Make the field visible
self - > s . eFlags | = EF_ITEMPLACEHOLDER ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
self - > takedamage = qfalse ;
trap_LinkEntity ( self ) ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
// Play raising sound...
G_AddEvent ( self , EV_GENERAL_SOUND , shieldActivateSound ) ;
2014-10-09 18:15:26 +00:00
}
/**
* Somebody ( a player ) has touched the shield . See if it is a " friend " .
*/
void ShieldTouch ( gentity_t * self , gentity_t * other , trace_t * trace )
{
2018-06-30 14:47:06 +00:00
if ( other = = NULL | | other - > client = = NULL )
return ;
if ( G_Client_IsAdmin ( other ) | | ( rpg_borgAdapt . integer & & rpg_borgMoveThroughFields . integer ! = 0 & & G_Client_IsBorg ( other ) ) /*other->client->sess.sessionClass == PC_ADMIN*/ )
{
ShieldGoNotSolid ( self ) ;
}
//RPG-X:J2J Damage for shields
else
{
if ( ( int32_t ) ( self - > s . angles [ PITCH ] ) = = 0 )
{
vec3_t dir ;
//OPTIMIZE ME: If anyone can figure a quick, non hacky way to get the normal vector
//of the side of the forcefield they touch, PLEASE put it here lol. THis way works, but feels very half-assed lol
//Get the directional vector
VectorSubtract ( self - > r . currentOrigin , other - > r . currentOrigin , dir ) ;
VectorNormalize ( dir ) ;
//depending on angle, negate the perpendicular direction (else they zap back sideways)
//and set the other as absolute
if ( self - > s . angles [ YAW ] = = 0 )
{
dir [ 1 ] = 0.0 ;
if ( dir [ 0 ] < 0 )
dir [ 0 ] = - 1.0 ;
else
dir [ 0 ] = 1.0 ;
}
else
{
dir [ 0 ] = 0.0 ;
if ( dir [ 1 ] < 0 )
dir [ 1 ] = - 1.0 ;
else
dir [ 1 ] = 1.0 ;
}
//Invert it otherwise we'd be like, sucked into the field lol
VectorScale ( dir , - 1 , dir ) ;
//Un-normalize it to represent a scalar quantity
VectorScale ( dir , 50 , dir ) ;
//Copy it straight to our velocity (this will mean the player will literally be thrown back)
VectorCopy ( dir , other - > client - > ps . velocity ) ;
other - > client - > ps . pm_time = 160 ; // hold time
other - > client - > ps . pm_flags | = PMF_TIME_KNOCKBACK ;
other - > flags | = EF_MOVER_STOP ; //Attempt to not let non admins thru.
//RPG-X: RedTechie - Added code for zap sound also a cvar to control damage a non admin walks into if cvar is set to 0 disables health from being drained (happens every 1 second)
//if ( level.time >= level.message + 1000 ) {
// level.message = level.time;
G_AddEvent ( self , EV_GENERAL_SOUND , shieldMurderSound ) ; //RPG-X: RedTechie - ZAPtacular! sound to my ears
if ( rpg_forcefielddamage . integer ! = 0 )
{
if ( rpg_forcefielddamage . integer > 999 )
{
rpg_forcefielddamage . integer = 999 ;
}
other - > health - = rpg_forcefielddamage . integer ;
//RPG-X: RedTechie - Fixed free ent if medic revive on
if ( rpg_medicsrevive . integer = = 1 )
{
if ( other - > health < = 1 )
{
other - > client - > ps . stats [ STAT_WEAPONS ] = ( 1 < < WP_0 ) ;
other - > client - > ps . stats [ STAT_HOLDABLE_ITEM ] = HI_NONE ;
other - > client - > ps . stats [ STAT_HEALTH ] = other - > health = 1 ;
G_Client_Die ( other , other , other , 1 , MOD_FORCEFIELD ) ;
}
}
else
{
if ( other - > health < = 1 )
{
other - > client - > ps . stats [ STAT_HEALTH ] = other - > health = 0 ;
G_Client_Die ( other , other , other , 100000 , MOD_FORCEFIELD ) ;
}
}
}
//TiM: make it flicker when touched, and then throw bak the person
self - > s . eFlags | = EF_ITEMPLACEHOLDER ;
self - > nextthink = level . time + 150 ;
self - > think = ShieldThink ;
}
}
2014-10-09 18:15:26 +00:00
}
/**
* After a short delay , create the shield by expanding in all directions .
*/
void CreateShield ( gentity_t * ent )
{
2018-06-30 14:47:06 +00:00
trace_t tr ;
vec3_t end , posTraceEnd , negTraceEnd , start ;
int32_t height , posWidth , negWidth , halfWidth = 0 ;
qboolean xaxis ;
int32_t paramData = 0 ;
// trace upward to find height of shield
VectorCopy ( ent - > r . currentOrigin , end ) ;
end [ 2 ] + = MAX_SHIELD_HEIGHT ;
memset ( & tr , 0 , sizeof ( trace_t ) ) ;
trap_Trace ( & tr , ent - > r . currentOrigin , NULL , NULL , end , ent - > s . number , MASK_SHOT ) ;
height = ( int32_t ) ( MAX_SHIELD_HEIGHT * tr . fraction ) ;
// use angles to find the proper axis along which to align the shield
VectorCopy ( ent - > r . currentOrigin , posTraceEnd ) ;
VectorCopy ( ent - > r . currentOrigin , negTraceEnd ) ;
if ( ( int32_t ) ( ent - > s . angles [ YAW ] ) = = 0 ) // shield runs along y-axis
{
ent - > s . eFlags | = EF_SHIELD_BOX_Y ;
posTraceEnd [ 1 ] + = MAX_SHIELD_HALFWIDTH ;
negTraceEnd [ 1 ] - = MAX_SHIELD_HALFWIDTH ;
xaxis = qfalse ;
}
else // shield runs along x-axis
{
ent - > s . eFlags | = EF_SHIELD_BOX_X ;
posTraceEnd [ 0 ] + = MAX_SHIELD_HALFWIDTH ;
negTraceEnd [ 0 ] - = MAX_SHIELD_HALFWIDTH ;
xaxis = qtrue ;
}
// trace horizontally to find extend of shield
// positive trace
VectorCopy ( ent - > r . currentOrigin , start ) ;
start [ 2 ] + = ( height > > 1 ) ;
trap_Trace ( & tr , start , 0 , 0 , posTraceEnd , ent - > s . number , MASK_SHOT ) ;
posWidth = MAX_SHIELD_HALFWIDTH * tr . fraction ;
// negative trace
trap_Trace ( & tr , start , 0 , 0 , negTraceEnd , ent - > s . number , MASK_SHOT ) ;
negWidth = MAX_SHIELD_HALFWIDTH * tr . fraction ;
// kef -- monkey with dimensions and place origin in center
halfWidth = ( posWidth + negWidth ) > > 1 ;
if ( xaxis )
{
ent - > r . currentOrigin [ 0 ] = ent - > r . currentOrigin [ 0 ] - negWidth + halfWidth ;
}
else
{
ent - > r . currentOrigin [ 1 ] = ent - > r . currentOrigin [ 1 ] - negWidth + halfWidth ;
}
ent - > r . currentOrigin [ 2 ] + = ( height > > 1 ) ;
// set entity's mins and maxs to new values, make it solid, and link it
if ( xaxis )
{
VectorSet ( ent - > r . mins , - halfWidth , - SHIELD_HALFTHICKNESS , - ( height > > 1 ) ) ;
VectorSet ( ent - > r . maxs , halfWidth , SHIELD_HALFTHICKNESS , height ) ;
}
else
{
VectorSet ( ent - > r . mins , - SHIELD_HALFTHICKNESS , - halfWidth , - ( height > > 1 ) ) ;
VectorSet ( ent - > r . maxs , SHIELD_HALFTHICKNESS , halfWidth , height > > 1 ) ;
}
ent - > clipmask = MASK_SHOT ;
// xaxis - 1 bit
// height - 0-254 8 bits //10
// posWidth - 0-255 8 bits //10
// negWidth - 0 - 255 8 bits
paramData = ( xaxis < < 30 ) | ( ( height & 1023 ) < < 20 ) | ( ( posWidth & 1023 ) < < 10 ) | ( negWidth & 1023 ) ; //24 16 8
ent - > s . time2 = paramData ;
if ( ent - > s . otherEntityNum2 = = TEAM_RED )
{
ent - > team = " 1 " ;
}
else if ( ent - > s . otherEntityNum2 = = TEAM_BLUE )
{
ent - > team = " 2 " ;
}
ent - > health = ceil ( SHIELD_HEALTH * g_dmgmult . value ) ;
ent - > s . time = ent - > health ; //???
ent - > pain = ShieldPain ;
ent - > die = ShieldDie ;
ent - > touch = ShieldTouch ;
ent - > r . svFlags | = SVF_SHIELD_BBOX ;
// see if we're valid
trap_Trace ( & tr , ent - > r . currentOrigin , ent - > r . mins , ent - > r . maxs , ent - > r . currentOrigin , ent - > s . number , CONTENTS_BODY ) ;
if ( tr . startsolid )
{ // Something in the way!
// make the shield non-solid very briefly
ent - > r . contents = CONTENTS_NONE ;
ent - > s . eFlags | = EF_NODRAW ;
// nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages
ent - > nextthink = level . time + 200 ;
ent - > think = ShieldGoSolid ;
ent - > takedamage = qfalse ;
trap_LinkEntity ( ent ) ;
}
else
{ // Get solid.
ent - > r . contents = CONTENTS_PLAYERCLIP | CONTENTS_SHOTCLIP ; //CONTENTS_SOLID;
ent - > nextthink = level . time + 400 ; //1000
ent - > think = ShieldThink ;
ent - > takedamage = qtrue ; //RPG-X: - RedTechie Use to be qtrue //TiM - made true again. should be okay so long as the health isn't decremented
trap_LinkEntity ( ent ) ;
ent - > s . eFlags | = EF_ITEMPLACEHOLDER ;
// Play raising sound...
G_AddEvent ( ent , EV_GENERAL_SOUND , shieldActivateSound ) ;
}
return ;
2014-10-09 18:15:26 +00:00
}
/**
* Place a forcefield
*/
static qboolean PlaceShield ( gentity_t * playerent )
{
2018-06-30 14:47:06 +00:00
static const gitem_t * shieldItem = NULL ;
gentity_t * shield = NULL ;
trace_t tr ;
vec3_t fwd , pos , dest , mins = { - 16 , - 16 , 0 } , maxs = { 16 , 16 , 16 } ;
playerState_t * ps = & playerent - > client - > ps ;
if ( shieldAttachSound = = 0 )
{
shieldAttachSound = G_SoundIndex ( " sound/weapons/detpacklatch.wav " ) ;
shieldActivateSound = G_SoundIndex ( " sound/movers/forcefield/forcefieldon.wav " ) ; //"sound/movers/forceup.wav"
shieldDamageSound = G_SoundIndex ( " sound/ambience/spark5.wav " ) ;
shieldMurderSound = G_SoundIndex ( " sound/movers/forcefield/forcefieldtouch.wav " ) ; //RPG-X: - RedTechie Added shild ZAP! sound //sound/world/electro.wav
shieldDeactivateSound = G_SoundIndex ( " sound/movers/forcefield/forcefieldoff.wav " ) ;
shieldItem = BG_FindItemForHoldable ( HI_SHIELD ) ;
}
// can we place this in front of us?
AngleVectors ( ps - > viewangles , fwd , NULL , NULL ) ;
fwd [ 2 ] = 0 ;
VectorMA ( ps - > origin , SHIELD_PLACEDIST , fwd , dest ) ;
trap_Trace ( & tr , ps - > origin , mins , maxs , dest , playerent - > s . number , MASK_SHOT ) ;
if ( tr . fraction > 0.9 )
{ //room in front
VectorCopy ( tr . endpos , pos ) ;
// drop to floor
VectorSet ( dest , pos [ 0 ] , pos [ 1 ] , pos [ 2 ] - 4096 ) ;
trap_Trace ( & tr , pos , mins , maxs , dest , playerent - > s . number , MASK_SOLID ) ;
if ( ! tr . startsolid & & ! tr . allsolid )
{
// got enough room so place the portable shield
shield = G_Spawn ( ) ;
if ( ! shield | | ! shieldItem ) return qfalse ;
// Figure out what direction the shield is facing.
if ( fabs ( fwd [ 0 ] ) > fabs ( fwd [ 1 ] ) )
{ // shield is north/south, facing east.
shield - > s . angles [ YAW ] = 0 ;
}
else
{ // shield is along the east/west axis, facing north
shield - > s . angles [ YAW ] = 90 ;
}
shield - > think = CreateShield ;
shield - > nextthink = level . time + 500 ; // power up after .5 seconds
shield - > parent = playerent ;
// Set team number.
shield - > s . otherEntityNum2 = playerent - > client - > sess . sessionTeam ;
shield - > s . eType = ET_USEABLE ;
shield - > s . modelindex = HI_SHIELD ; // this'll be used in CG_Useable() for rendering.
shield - > classname = shieldItem - > classname ;
shield - > r . contents = CONTENTS_TRIGGER ;
shield - > touch = 0 ;
// using an item causes it to respawn
shield - > use = 0 ; //Use_Item;
// allow to ride movers
shield - > s . groundEntityNum = tr . entityNum ;
G_SetOrigin ( shield , tr . endpos ) ;
shield - > s . origin2 [ 0 ] = rpg_forceFieldColor . integer ;
shield - > s . eFlags & = ~ EF_NODRAW ;
shield - > r . svFlags & = ~ SVF_NOCLIENT ;
trap_LinkEntity ( shield ) ;
// Play placing sound...
G_AddEvent ( shield , EV_GENERAL_SOUND , shieldAttachSound ) ;
return qtrue ;
}
}
return qfalse ;
2014-10-09 18:15:26 +00:00
}
//-------------------------------------------------------------- DECOY ACTIVITIES
/**
* Think function for decoys , decoys are spawnchars in RPG - X
*/
void DecoyThink ( gentity_t * ent )
{
2018-06-30 14:47:06 +00:00
ent - > s . apos = ( ent - > parent ) - > s . apos ; // Update Current Rotation
ent - > nextthink = level . time + irandom ( 2000 , 6000 ) ; // Next think between 2 & 8 seconds
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
( ent - > count ) - - ; // Count Down
if ( ent - > count < 0 ) G_FreeEntity ( ent ) ; // Time To Erase The Ent
2014-10-09 18:15:26 +00:00
}
//TiM : I was just able to spawn 600 copies of me...
//my fps died and my PC started making weird noises
//We'd better instigate a limit to our spawning here...
//Borrowed from the tripMines
/**
* Safety function to limit ammount of decoys spawned at one time and overall .
* Stops spawning if to many are spawned a a time , stop spawning is a limit was hit .
* \ author Ubergames - TiM
*/
2018-06-30 14:47:06 +00:00
void flushDecoys ( gentity_t * ent )
{
gentity_t * decoy = NULL ;
int32_t foundDecoys [ MAX_GENTITIES ] = { ENTITYNUM_NONE } ;
int32_t lowestTimeStamp ;
int32_t orgCount ;
int32_t decoyCount = 0 ;
int32_t removeMe ;
int32_t i ;
//limit to 10 placed at any one time
//see how many there are now
while ( ( decoy = G_Find ( decoy , FOFS ( classname ) , " decoy " ) ) ! = NULL )
{
if ( decoy - > parent ! = ent )
//if ( decoy->s.clientNum != ent->client->ps.clientNum )
{
continue ;
}
foundDecoys [ decoyCount + + ] = decoy - > s . clientNum ;
}
//now remove first ones we find until there are only 9 left
decoy = NULL ;
orgCount = decoyCount ;
lowestTimeStamp = level . time ;
//RPG-X: TiM - Let's limit it to say... 64 decoys per player
while ( decoyCount > 64 ) //9
{
removeMe = - 1 ;
for ( i = 0 ; i < orgCount ; i + + )
{
if ( foundDecoys [ i ] = = ENTITYNUM_NONE )
{
continue ;
}
decoy = & g_entities [ foundDecoys [ i ] ] ;
if ( decoy & & decoy - > timestamp < lowestTimeStamp )
{
removeMe = i ;
lowestTimeStamp = decoy - > timestamp ;
}
}
if ( removeMe ! = - 1 )
{
//remove it... or blow it?
if ( & g_entities [ foundDecoys [ removeMe ] ] = = NULL )
{
break ;
}
else
{
G_FreeEntity ( & g_entities [ foundDecoys [ removeMe ] ] ) ;
}
foundDecoys [ removeMe ] = ENTITYNUM_NONE ;
decoyCount - - ;
}
else
{
break ;
}
}
2014-10-09 18:15:26 +00:00
}
/**
* entities spawn non solid and through this function ,
* they ' ll become solid once nothing ' s detected in their boundaries . : )
* \ author TiM
*/
2018-06-30 14:47:06 +00:00
void Decoy_CheckForSolidity ( gentity_t * ent )
{
int32_t i , num ;
int32_t touch [ MAX_GENTITIES ] ;
qboolean canGoSolid = qtrue ;
gentity_t * hit = NULL ;
vec3_t mins , maxs ;
VectorAdd ( ent - > s . origin , ent - > r . mins , mins ) ;
VectorAdd ( ent - > s . origin , ent - > r . maxs , maxs ) ;
num = trap_EntitiesInBox ( mins , maxs , touch , MAX_GENTITIES ) ;
for ( i = 0 ; i < num ; i + + )
{
hit = & g_entities [ touch [ i ] ] ;
if ( hit & & hit - > client )
{
canGoSolid = qfalse ;
break ;
}
}
if ( canGoSolid )
{
ent - > r . contents = MASK_PLAYERSOLID ;
ent - > nextthink = 0 ;
ent - > think = 0 ;
}
else
{
ent - > nextthink = level . time + 1000 ;
ent - > r . contents = CONTENTS_NONE ;
}
2014-10-09 18:15:26 +00:00
}
/**
* Use function for decoy , removes it if activator is an player and admin
*/
2018-06-30 14:47:06 +00:00
void DecoyUse ( gentity_t * self , gentity_t * other , gentity_t * activator )
{
if ( activator = = NULL | | ! G_Client_IsAdmin ( activator ) | | activator - > client = = NULL )
return ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
G_FreeEntity ( self ) ;
2014-10-09 18:15:26 +00:00
}
/**
* Spawn a char
2014-11-10 19:56:17 +00:00
*/
2014-10-09 18:15:26 +00:00
qboolean PlaceDecoy ( gentity_t * ent )
{
2018-06-30 14:47:06 +00:00
gentity_t * decoy = NULL ;
static gitem_t * decoyItem = NULL ;
vec3_t mins = { - 16 , - 16 , - 24 } ;
char userinfo [ MAX_INFO_STRING ] ;
int32_t i ;
if ( decoyItem = = NULL )
{
decoyItem = BG_FindItemForHoldable ( HI_DECOY ) ;
}
//If we just hit our 129th decoy (...holy crap), reset the counter
if ( level . decoyIndex > = MAX_CLIENTS )
{
level . decoyIndex = 0 ;
}
//Now check if there is already a decoy with the same eventParm index. If there is, terminate it
{
gentity_t * oldDecoy = NULL ;
for ( i = 0 ; i < level . num_entities ; i + + )
{
oldDecoy = & g_entities [ i ] ;
if ( Q_stricmp ( oldDecoy - > classname , " decoy " ) = = 0 & & oldDecoy - > s . eventParm = = level . decoyIndex )
{
G_FreeEntity ( oldDecoy ) ;
break ;
}
}
}
//--------------------------- SPAWN AND PLACE DECOY ON GROUND
decoy = G_Spawn ( ) ;
G_SpawnItem ( decoy , decoyItem ) ; // Generate it as an item, temporarly
decoy - > physicsBounce = 0.0f ; //decoys are *not* bouncy
//VectorMA(ent->client->ps.origin, detDistance + mins[0], fwd, decoy->s.origin);
VectorCopy ( ent - > client - > ps . origin , decoy - > s . origin ) ;
decoy - > r . mins [ 2 ] = mins [ 2 ] ; //keep it off the floor
//VectorNegate(fwd, fwd); // ??? What does this do??
//vectoangles(fwd, decoy->s.angles);
VectorCopy ( ent - > client - > ps . viewangles , decoy - > s . angles ) ;
if ( ! FinishSpawningDecoy ( decoy , decoyItem - bg_itemlist ) )
{
return qfalse ; // Drop to ground and trap to clients
}
decoy - > s . clientNum = ent - > client - > ps . clientNum ;
decoy - > s . number = decoy - g_entities ;
//--------------------------- SPECIALIZED DECOY SETUP
decoy - > s . time = - 1 ; // tell cgame this is a decoy so it does not mess up the radar
decoy - > parent = ent ;
( decoy - > s ) . eType = ( ent - > s ) . eType ; // set to type PLAYER
( decoy - > s ) . eFlags = ( ent - > s ) . eFlags ;
( decoy - > s ) . eFlags | = EF_ITEMPLACEHOLDER ; // set the HOLOGRAM FLAG to ON
( decoy - > s ) . powerups = ( ent - > s ) . powerups ;
//TiM - attach the rotate flag if we need to
if ( ( ent - > s ) . powerups & ( 1 < < PW_FLIGHT ) | | ent - > client - > ps . gravity = = 0 )
( decoy - > s ) . eFlags | = EF_FULL_ROTATE ;
decoy - > s . eFlags & = ~ ( EF_FIRING | EF_ALT_FIRING ) ;
decoy - > s . weapon = ent - > s . weapon ; // get Player's Wepon Type
decoy - > s . apos = ent - > s . apos ; // copy angle of player to decoy
decoy - > s . pos = ent - > s . pos ;
//TiM: Set it's anim to whatever anims we're playing right now
decoy - > s . legsAnim = ent - > client - > ps . stats [ LEGSANIM ] ;
decoy - > s . torsoAnim = ent - > client - > ps . stats [ TORSOANIM ] ;
decoy - > timestamp = level . time ;
//--------------------------- WEAPON ADJUST
// The Phaser and Dreadnought (Arc Welder) weapons are rendered on the
// client side differently, and cannot be used by the decoy
decoy - > classname = " decoy " ;
//TiM-Set up data for transmission to client
decoy - > s . eventParm = level . decoyIndex ;
decoy - > r . contents = CONTENTS_SOLID ; //has to start off solid, or CGame won't realise this
VectorSet ( decoy - > r . mins , DEFAULT_MINS_0 , DEFAULT_MINS_1 , DEFAULT_MINS_2 ) ;
VectorSet ( decoy - > r . maxs , DEFAULT_MAXS_0 , DEFAULT_MAXS_1 , DEFAULT_MAXS_2 ) ;
decoy - > nextthink = level . time + FRAMETIME ;
decoy - > think = Decoy_CheckForSolidity ;
decoy - > use = DecoyUse ;
trap_GetUserinfo ( ent - > client - > ps . clientNum , userinfo , sizeof ( userinfo ) ) ;
{
char buffer [ MAX_TOKEN_CHARS ] ;
char model [ 64 ] ;
char height [ 9 ] ;
char weight [ 9 ] ;
char offset [ 6 ] ;
//TiM - ensure that we encapsulate this data better or else it sometimes
//becomes null
Q_strncpyz ( model , Info_ValueForKey ( userinfo , " model " ) , sizeof ( model ) ) ;
Q_strncpyz ( height , Info_ValueForKey ( userinfo , " height " ) , sizeof ( height ) ) ;
Q_strncpyz ( weight , Info_ValueForKey ( userinfo , " weight " ) , sizeof ( weight ) ) ;
Q_strncpyz ( offset , Info_ValueForKey ( userinfo , " modelOffset " ) , sizeof ( offset ) ) ;
Com_sprintf ( buffer , sizeof ( buffer ) , " model \\ %s \\ height \\ %s \\ weight \\ %s \\ moOf \\ %s \\ c \\ %i " ,
model ,
height ,
weight ,
offset ,
ent - > client - > sess . sessionClass ) ;
trap_SetConfigstring ( CS_DECOYS + level . decoyIndex , buffer ) ;
}
level . decoyIndex + + ;
return qtrue ; // SUCCESS
2014-10-09 18:15:26 +00:00
}
//-------------------------------------------------------------- DECOY ACTIVITIES
2014-11-10 19:56:17 +00:00
void G_Rematerialize ( gentity_t * ent )
2014-10-09 18:15:26 +00:00
{
2018-06-30 14:47:06 +00:00
playerState_t * ps = & ent - > client - > ps ;
ent - > client - > teleportTime = level . time + ( 15 * 1000 ) ;
ps - > stats [ STAT_USEABLE_PLACED ] = 15 ;
ent - > flags & = ~ FL_NOTARGET ;
ent - > takedamage = qtrue ;
ent - > r . contents = MASK_PLAYERSOLID ;
ent - > s . eFlags & = ~ EF_NODRAW ;
ps - > eFlags & = ~ EF_NODRAW ;
TeleportPlayer ( ent , ps - > origin , ps - > viewangles , TP_BORG ) ;
//take it away
ps - > stats [ STAT_HOLDABLE_ITEM ] = 0 ;
2014-10-09 18:15:26 +00:00
}
2014-11-10 19:56:17 +00:00
static void G_GiveHoldable ( gclient_t * client , holdable_t item )
2014-10-09 18:15:26 +00:00
{
2018-06-30 14:47:06 +00:00
gitem_t * holdable = BG_FindItemForHoldable ( item ) ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
client - > ps . stats [ STAT_HOLDABLE_ITEM ] = holdable - bg_itemlist ; //teleport spots should be on other side of map
RegisterItem ( holdable ) ;
2014-10-09 18:15:26 +00:00
}
/*
= = = = = = = = = = = = = = = =
ClientEvents
= = = = = = = = = = = = = = = =
*/
/**
* Events will be passed on to the clients for presentation ,
* but any server game effects are handled here
*/
2018-06-30 14:47:06 +00:00
static void ClientEvents ( gentity_t * ent , int oldEventSequence )
{
int32_t i ;
int32_t event ;
gclient_t * client = NULL ;
int32_t damage ;
playerState_t * ps = NULL ;
client = ent - > client ;
ps = & client - > ps ;
if ( oldEventSequence < ps - > eventSequence - MAX_PS_EVENTS )
{
oldEventSequence = ps - > eventSequence - MAX_PS_EVENTS ;
}
for ( i = oldEventSequence ; i < ps - > eventSequence ; i + + )
{
event = ps - > events [ i & ( MAX_PS_EVENTS - 1 ) ] ;
switch ( event )
{
case EV_FALL_MEDIUM :
case EV_FALL_FAR :
if ( ent - > s . eType ! = ET_PLAYER )
{
break ; // not in the player model
}
if ( g_dmflags . integer & DF_NO_FALLING )
{
break ;
}
if ( rpg_selfdamage . integer ! = 0 )
{
if ( event = = EV_FALL_FAR )
{
damage = 110 ; //10 -TiM : Make the falling more realistc!
}
else
{
damage = 90 ; //5
}
}
else
{
damage = 0 ;
}
ent - > pain_debounce_time = level . time + 200 ; // no normal pain sound
G_Combat_Damage ( ent , NULL , NULL , NULL , NULL , damage , DAMAGE_ARMOR_PIERCING , MOD_FALLING ) ;
break ;
case EV_FIRE_WEAPON :
FireWeapon ( ent , qfalse ) ;
break ;
case EV_ALT_FIRE :
FireWeapon ( ent , qtrue ) ;
break ;
case EV_FIRE_EMPTY_PHASER :
FireWeapon ( ent , qfalse ) ;
break ;
case EV_USE_ITEM1 : // transporter
//TiM: Since we purge the vectors each cycle. I'll save us some memory by using the vectors themselves as a check.
if ( TransDat [ ps - > clientNum ] . beamTime = = 0 & &
VectorCompare ( vec3_origin , TransDat [ ps - > clientNum ] . storedCoord [ TPT_PORTABLE ] . origin ) & &
VectorCompare ( vec3_origin , TransDat [ ps - > clientNum ] . storedCoord [ TPT_PORTABLE ] . angles ) )
{
VectorCopy ( ps - > origin , TransDat [ ps - > clientNum ] . storedCoord [ TPT_PORTABLE ] . origin ) ;
VectorCopy ( ps - > viewangles , TransDat [ ps - > clientNum ] . storedCoord [ TPT_PORTABLE ] . angles ) ;
trap_SendServerCommand ( ent - g_entities , va ( " chat \" Site to Site Transporter Location Confirmed. \n Press again to Energize. \" " ) ) ;
ps - > stats [ STAT_HOLDABLE_ITEM ] = BG_FindItemForHoldable ( HI_TRANSPORTER ) - bg_itemlist ;
ps - > stats [ STAT_USEABLE_PLACED ] = 2 ; // = 1
break ;
}
if ( TransDat [ ps - > clientNum ] . beamTime = = 0 & & level . time > ps - > powerups [ PW_QUAD ] )
{
G_InitTransport ( ps - > clientNum , TransDat [ ps - > clientNum ] . storedCoord [ TPT_PORTABLE ] . origin ,
TransDat [ ps - > clientNum ] . storedCoord [ TPT_PORTABLE ] . angles ) ;
memset ( & TransDat [ ps - > clientNum ] . storedCoord [ TPT_PORTABLE ] , 0 , sizeof ( TransDat [ ps - > clientNum ] . storedCoord [ TPT_PORTABLE ] ) ) ;
}
else
{
trap_SendServerCommand ( ent - g_entities , va ( " chat \" Unable to comply. Already within transport cycle. \" " ) ) ;
}
ps - > stats [ STAT_USEABLE_PLACED ] = 0 ;
if ( g_classData [ client - > sess . sessionClass ] . isMarine )
{
client - > teleportTime = level . time + ( 3 * 1000 ) ; // 15 * 1000
ps - > stats [ STAT_USEABLE_PLACED ] = 1 ; // = 1
}
break ;
case EV_USE_ITEM2 : // medkit
// New set of rules. You get either 100 health, or an extra 25, whichever is higher.
// Give 1/4 health.
ent - > health + = ps - > stats [ STAT_MAX_HEALTH ] * 0.25 ;
if ( ent - > health < ps - > stats [ STAT_MAX_HEALTH ] )
{ // If that doesn't bring us up to 100, make it go up to 100.
ent - > health = ps - > stats [ STAT_MAX_HEALTH ] ;
}
else if ( ent - > health > ps - > stats [ STAT_MAX_HEALTH ] * 2 )
{ // Otherwise, 25 is all you get. Just make sure we don't go above 200.
ent - > health = ps - > stats [ STAT_MAX_HEALTH ] * 2 ;
}
break ;
case EV_USE_ITEM3 : // detpack
// if we haven't placed it yet, place it
if ( 0 = = ps - > stats [ STAT_USEABLE_PLACED ] )
{
if ( PlaceDetpack ( ent ) )
{
ps - > stats [ STAT_USEABLE_PLACED ] = 1 ;
trap_SendServerCommand ( ent - g_entities , " cp \" CHARGE PLACED \" " ) ;
}
else
{ //couldn't place it
ps - > stats [ STAT_HOLDABLE_ITEM ] = ( BG_FindItemForHoldable ( HI_DETPACK ) - bg_itemlist ) ;
trap_SendServerCommand ( ent - g_entities , " cp \" NO ROOM TO PLACE CHARGE \" " ) ;
}
}
else
{
// ok, we placed it earlier. blow it up.
ps - > stats [ STAT_USEABLE_PLACED ] = 0 ;
DetonateDetpack ( ent ) ;
}
break ;
case EV_USE_ITEM4 : // portable shield
if ( ! PlaceShield ( ent ) ) // fixme if we fail, perhaps just spawn it as a pickup
{ //couldn't place it
ps - > stats [ STAT_HOLDABLE_ITEM ] = ( BG_FindItemForHoldable ( HI_SHIELD ) - bg_itemlist ) ;
trap_SendServerCommand ( ent - g_entities , " cp \" NO ROOM TO PLACE FORCE FIELD \" " ) ;
}
else
{
trap_SendServerCommand ( ent - g_entities , " cp \" FORCE FIELD PLACED \" " ) ;
}
break ;
case EV_USE_ITEM5 : // decoy
if ( ! PlaceDecoy ( ent ) )
{ //couldn't place it
ps - > stats [ STAT_HOLDABLE_ITEM ] = ( BG_FindItemForHoldable ( HI_DECOY ) - bg_itemlist ) ;
trap_SendServerCommand ( ent - g_entities , " cp \" NO ROOM TO PLACE DECOY \" " ) ;
}
else
{
trap_SendServerCommand ( ent - g_entities , " cp \" DECOY PLACED \" " ) ;
}
break ;
default :
break ;
}
}
2014-10-09 18:15:26 +00:00
}
2014-10-11 16:41:15 +00:00
void AI_main_BotTestSolid ( vec3_t origin ) ;
2014-10-09 18:15:26 +00:00
2014-11-10 19:56:17 +00:00
void G_ThrowWeapon ( gentity_t * ent , char * txt )
2014-10-09 18:15:26 +00:00
{
2018-06-30 14:47:06 +00:00
gclient_t * client = NULL ;
usercmd_t * ucmd = NULL ;
gitem_t * item = NULL ;
gentity_t * drop = NULL ;
byte i ;
playerState_t * ps = NULL ;
client = ent - > client ;
ucmd = & ent - > client - > pers . cmd ;
ps = & client - > ps ;
if ( rpg_allowWeaponDrop . integer = = 0 )
{
return ;
}
if ( numTotalDropped > = MAX_DROPPED )
{
G_LocLogger ( LL_WARN , " RPG-X Warning: maximum of dropped items of %i reached. \n " , MAX_DROPPED ) ;
return ;
}
if ( ps - > weapon = = WP_0 | | ps - > weapon = = WP_1 | | ( ucmd - > buttons & BUTTON_ATTACK ) )
{
return ;
}
numTotalDropped + + ;
item = BG_FindItemForWeapon ( weapon_t ( ps - > weapon ) ) ;
// admins don't lose weapon when thrown
if ( G_Client_IsAdmin ( ent ) = = qfalse )
{
ps - > ammo [ ps - > weapon ] - = 1 ;
if ( ps - > ammo [ ps - > weapon ] < = 0 )
{
ps - > stats [ STAT_WEAPONS ] & = ~ ( 1 < < ps - > weapon ) ;
ps - > weapon = WP_1 ;
for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i - - )
{
if ( ps - > stats [ STAT_WEAPONS ] & ( 1 < < i ) )
{
ps - > weapon = i ;
break ;
}
}
}
}
drop = DropWeapon ( ent , item , 0 , FL_DROPPED_ITEM | FL_THROWN_ITEM , txt ) ;
drop - > parent = ent ;
drop - > count = 1 ;
2014-10-09 18:15:26 +00:00
}
/*
= = = = = = = = = = = = = =
SendPendingPredictableEvents
= = = = = = = = = = = = = =
*/
2018-06-30 14:47:06 +00:00
static void SendPendingPredictableEvents ( playerState_t * ps )
{
gentity_t * t = NULL ;
int32_t event , seq ;
int32_t extEvent ;
// if there are still events pending
if ( ps - > entityEventSequence < ps - > eventSequence )
{
// create a temporary entity for this event which is sent to everyone
// except the client generated the event
seq = ps - > entityEventSequence & ( MAX_PS_EVENTS - 1 ) ;
event = ps - > events [ seq ] | ( ( ps - > entityEventSequence & 3 ) < < 8 ) ;
// set external event to zero before calling BG_PlayerStateToEntityState
extEvent = ps - > externalEvent ;
ps - > externalEvent = 0 ;
// create temporary entity for event
t = G_TempEntity ( ps - > origin , event ) ;
BG_PlayerStateToEntityState ( ps , & t - > s , qtrue ) ;
t - > s . eType = ET_EVENTS + event ;
// send to everyone except the client who generated the event
t - > r . svFlags | = SVF_NOTSINGLECLIENT ;
t - > r . singleClient = ps - > clientNum ;
// set back external event
ps - > externalEvent = extEvent ;
}
2014-10-09 18:15:26 +00:00
}
2018-06-30 14:47:06 +00:00
static void ClientCamThink ( gentity_t * client )
{
if ( client = = NULL | | client - > client = = NULL | | client - > client - > cam = = NULL )
{
if ( client - > flags & FL_CCAM )
{
client - > flags ^ = FL_CCAM ;
}
client - > client - > ps . pm_type = PM_NORMAL ;
return ;
}
G_SetOrigin ( client , client - > client - > cam - > s . origin ) ;
G_Client_SetViewAngle ( client , client - > client - > cam - > s . angles ) ;
trap_LinkEntity ( client ) ;
2014-10-09 18:15:26 +00:00
}
/*
= = = = = = = = = = = = = =
ClientThink
= = = = = = = = = = = = = =
*/
/**
* This will be called once for each client frame , which will
* usually be a couple times for each server frame on fast clients .
*
* If " g_synchronousClients 1 " is set , this will be called exactly
* once for each server frame , which makes for smooth demo recording .
*/
2018-06-30 14:47:06 +00:00
static void ClientThink_real ( gentity_t * ent )
{
gclient_t * client = NULL ;
pmove_t pm ;
int32_t oldEventSequence ;
int32_t msec ;
usercmd_t * ucmd = NULL ;
playerState_t * ps = NULL ;
client = ent - > client ;
ps = & client - > ps ;
// don't think if the client is not yet connected (and thus not yet spawned in)
if ( client - > pers . connected ! = CON_CONNECTED )
{
return ;
}
// mark the time, so the connection sprite can be removed
ucmd = & client - > pers . cmd ;
// sanity check the command time to prevent speedup cheating
if ( ucmd - > serverTime > level . time + 200 )
{
ucmd - > serverTime = level . time + 200 ;
}
if ( ucmd - > serverTime < level . time - 1000 )
{
ucmd - > serverTime = level . time - 1000 ;
}
msec = ucmd - > serverTime - ps - > commandTime ;
// following others may result in bad times, but we still want
// to check for follow toggles
if ( msec < 1 & & client - > sess . spectatorState ! = SPECTATOR_FOLLOW )
{
return ;
}
if ( msec > 200 )
{
msec = 200 ;
}
//
// check for exiting intermission
//
if ( level . intermissiontime )
{
ClientIntermissionThink ( client ) ;
return ;
}
if ( ent - > flags & FL_CCAM )
{
ClientCamThink ( ent ) ;
return ;
}
// spectators don't do much
if ( client - > sess . sessionTeam = = TEAM_SPECTATOR )
{
if ( client - > sess . spectatorState = = SPECTATOR_SCOREBOARD )
{
return ;
}
SpectatorThink ( ent , ucmd ) ;
return ;
}
// check for inactivity timer, but never drop the local client of a non-dedicated server
if ( ! ClientInactivityTimer ( client ) )
{
return ;
}
//TiM - If we're null content... see what's up
if ( ent - > r . contents = = CONTENTS_NONE )
{
if ( ! G_MoveBox ( ent ) )
{
if ( ps - > stats [ STAT_HEALTH ] > 1 )
ent - > r . contents = CONTENTS_BODY ;
else
ent - > r . contents = CONTENTS_CORPSE ;
}
}
//RPG-X: Checked to see if medics revive is on if so do as following
if ( rpg_medicsrevive . integer = = 1 )
{
if ( client - > noclip )
{
ps - > pm_type = PM_NOCLIP ;
}
else if ( ps - > stats [ STAT_HEALTH ] = = 1 )
{
ps - > pm_type = PM_DEAD ;
}
else
{
ps - > pm_type = PM_NORMAL ;
}
}
else
{
if ( client - > noclip )
{
ps - > pm_type = PM_NOCLIP ;
}
else if ( ps - > stats [ STAT_HEALTH ] < = 0 )
{
ps - > pm_type = PM_DEAD ;
}
else
{
ps - > pm_type = PM_NORMAL ;
}
}
//RPG-X: J2J & Phenix - For the gravity ent
if ( client - > SpecialGrav ! = qtrue )
{
ps - > gravity = g_gravity . value ;
}
// set speed
ps - > speed = g_speed . value ;
if ( ps - > powerups [ PW_HASTE ] )
{
ps - > speed * = 1.5 ;
}
else if ( ps - > powerups [ PW_FLIGHT ] )
{ //flying around
ps - > speed * = 1.3 ;
}
else if ( ps - > stats [ STAT_HEALTH ] < = 20 )
{
ps - > speed * = 0.55 ;
}
if ( ( ps - > powerups [ PW_EVOSUIT ] ) & & ( ps - > gravity = = 0 ) )
{ //Evosuit time.. RPG-X | Phenix | 8/8/2004
ps - > speed * = 1.3 ;
}
//RPG-X: Redtechie - n00bie stay.....good boy!
if ( g_classData [ client - > sess . sessionClass ] . isn00b )
{
ps - > speed = 0 ;
}
//TiM : SP Style Transporter. :)
//first check to see if we should be beaming
if ( level . time < TransDat [ ps - > clientNum ] . beamTime )
{
//if we're past the mid point of each materialization cycle, make it
//so bullets and other players will pass thru the transportee. :)
if ( ( level . time > TransDat [ ps - > clientNum ] . beamTime - 6000 ) & &
( level . time < TransDat [ ps - > clientNum ] . beamTime - 2000 ) )
{
if ( ps - > stats [ STAT_HEALTH ] > 1 )
{
ent - > r . contents = CONTENTS_NONE ;
}
}
else
{
if ( ps - > stats [ STAT_HEALTH ] > 1 )
{
ent - > r . contents = MASK_PLAYERSOLID ;
}
}
//If we're half-way thru the cycle, teleport the player now
if ( level . time > TransDat [ ps - > clientNum ] . beamTime - 4000 & &
! TransDat [ ps - > clientNum ] . beamed )
{
TeleportPlayer ( ent , TransDat [ ps - > clientNum ] . currentCoord . origin ,
TransDat [ ps - > clientNum ] . currentCoord . angles ,
TP_TRI_TP ) ;
TransDat [ ps - > clientNum ] . beamed = qtrue ;
}
}
else
{
//all done, let's reset :)
if ( TransDat [ ps - > clientNum ] . beamTime > 0 )
{
TransDat [ ps - > clientNum ] . beamTime = 0 ;
ps - > powerups [ PW_BEAM_OUT ] = 0 ;
ps - > powerups [ PW_QUAD ] = 0 ;
TransDat [ ps - > clientNum ] . beamed = qfalse ;
memset ( & TransDat [ ps - > clientNum ] . currentCoord , 0 ,
sizeof ( TransDat [ ps - > clientNum ] . currentCoord . origin ) ) ;
if ( g_entities [ ps - > clientNum ] . flags & FL_CLAMPED )
{
//reset everything if player was beamed by trigger_transporter
g_entities [ ps - > clientNum ] . flags ^ = FL_CLAMPED ;
}
}
}
//TiM : Freeze their movement if they're halfway through a transport cycle
if ( level . time < TransDat [ ps - > clientNum ] . beamTime & &
level . time > TransDat [ ps - > clientNum ] . beamTime - 4000 )
{
vec3_t endPoint ;
trace_t tr ;
VectorSet ( endPoint , ps - > origin [ 0 ] , ps - > origin [ 1 ] , ps - > origin [ 2 ] - 48 ) ;
//Do a trace down. If we're near ground, just re-enable gravity. Else we we get weird animations O_o
trap_Trace ( & tr , ps - > origin , NULL , NULL , endPoint , ps - > clientNum , CONTENTS_SOLID ) ;
if ( tr . fraction = = 1.0 )
{
ps - > gravity = 0 ;
ps - > velocity [ 2 ] = 0 ;
}
ps - > speed = 0 ;
ps - > velocity [ 0 ] = ps - > velocity [ 1 ] = 0.0 ;
}
// set up for pmove
oldEventSequence = ps - > eventSequence ;
memset ( & pm , 0 , sizeof ( pm ) ) ;
pm . ps = & client - > ps ;
pm . cmd = * ucmd ;
if ( pm . ps - > pm_type = = PM_DEAD )
{
pm . tracemask = MASK_PLAYERSOLID & ~ CONTENTS_BODY ;
}
else
{
pm . tracemask = MASK_PLAYERSOLID ;
}
pm . trace = trap_Trace ;
pm . pointcontents = trap_PointContents ;
pm . debugLevel = g_debugMove . integer ;
pm . noFootsteps = ( qboolean ) ( ( g_dmflags . integer & DF_NO_FOOTSTEPS ) > 0 ) ;
pm . pModDisintegration = qfalse ;
//pm.admin = IsAdmin(ent); // we use this way now the old way didn't work for adminlogin
// y call a function though???
pm . admin = ( qboolean ) ( g_classData [ client - > sess . sessionClass ] . isAdmin | | client - > LoggedAsAdmin ) ;
//pm.admin = g_classData[client->sess.sessionClass].isAdmin;
pm . medic = ( qboolean ) g_classData [ client - > sess . sessionClass ] . isMedical ;
pm . borg = ( qboolean ) g_classData [ client - > sess . sessionClass ] . isBorg ;
// perform a pmove
Pmove ( & pm ) ;
// save results of pmove
if ( ps - > eventSequence ! = oldEventSequence )
{
ent - > eventTime = level . time ;
}
BG_PlayerStateToEntityState ( ps , & ent - > s , qtrue ) ;
SendPendingPredictableEvents ( ps ) ;
// use the snapped origin for linking so it matches client predicted versions
VectorCopy ( ent - > s . pos . trBase , ent - > r . currentOrigin ) ;
VectorCopy ( pm . mins , ent - > r . mins ) ;
VectorCopy ( pm . maxs , ent - > r . maxs ) ;
ent - > waterlevel = pm . waterlevel ;
ent - > watertype = pm . watertype ;
// execute client events
ClientEvents ( ent , oldEventSequence ) ;
if ( pm . useEvent )
{ //TODO: Use
TryUse ( ent ) ;
}
// link entity now, after any personal teleporters have been used
trap_LinkEntity ( ent ) ;
G_TouchTriggers ( ent ) ;
// NOTE: now copy the exact origin over otherwise clients can be snapped into solid
VectorCopy ( ps - > origin , ent - > r . currentOrigin ) ;
//test for solid areas in the AAS file
AI_main_BotTestSolid ( ent - > r . currentOrigin ) ;
// touch other objects
ClientImpacts ( ent , & pm ) ;
// save results of triggers and client events
if ( ps - > eventSequence ! = oldEventSequence )
{
ent - > eventTime = level . time ;
}
// swap and latch button actions
client - > oldbuttons = client - > buttons ;
client - > buttons = ucmd - > buttons ;
client - > latched_buttons | = client - > buttons & ~ client - > oldbuttons ;
// check for respawning
if ( client - > ps . stats [ STAT_HEALTH ] < = 0 )
{
// wait for the attack button to be pressed
if ( level . time > client - > respawnTime )
{
// pressing attack or use is the normal respawn method
if ( ucmd - > buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) )
{
G_Client_Respawn ( ent ) ;
return ;
}
}
return ;
}
// perform once-a-second actions
ClientTimerActions ( ent , msec ) ;
if ( client - > teleportTime > 0 & & client - > teleportTime < level . time )
{
if ( g_classData [ client - > sess . sessionClass ] . isMarine )
{
G_GiveHoldable ( client , HI_TRANSPORTER ) ;
ps - > stats [ STAT_USEABLE_PLACED ] = 0 ;
client - > teleportTime = 0 ;
}
else if ( g_classData [ client - > sess . sessionClass ] . isAdmin )
{
G_GiveHoldable ( client , HI_SHIELD ) ;
ps - > stats [ STAT_USEABLE_PLACED ] = 0 ;
client - > teleportTime = 0 ;
}
}
2014-10-09 18:15:26 +00:00
}
2018-06-30 14:47:06 +00:00
void ClientThink ( int clientNum )
{
gentity_t * ent = NULL ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
ent = g_entities + clientNum ;
trap_GetUsercmd ( clientNum , & ent - > client - > pers . cmd ) ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
// mark the time we got info, so we can display the
// phone jack if they don't get any for a while
ent - > client - > lastCmdTime = level . time ;
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
if ( ! g_synchronousClients . integer )
{
ClientThink_real ( ent ) ;
}
2014-10-09 18:15:26 +00:00
}
2018-06-30 14:47:06 +00:00
void G_RunClient ( gentity_t * ent )
{
if ( g_synchronousClients . integer = = 0 )
{
return ;
}
2014-10-09 18:15:26 +00:00
2018-06-30 14:47:06 +00:00
ent - > client - > pers . cmd . serverTime = level . time ;
ClientThink_real ( ent ) ;
2014-10-09 18:15:26 +00:00
}
/*
= = = = = = = = = = = = = = = = = =
SpectatorClientEndFrame
= = = = = = = = = = = = = = = = = =
*/
2018-06-30 14:47:06 +00:00
static void SpectatorClientEndFrame ( gentity_t * ent )
{
gclient_t * cl = NULL ;
clientSession_t * sess = & ent - > client - > sess ;
playerState_t * ps = & ent - > client - > ps ;
// if we are doing a chase cam or a remote view, grab the latest info
if ( sess - > spectatorState = = SPECTATOR_FOLLOW )
{
int32_t clientNum ;
clientNum = sess - > spectatorClient ;
// team follow1 and team follow2 go to whatever clients are playing
if ( clientNum = = - 1 )
{
clientNum = level . follow1 ;
}
else if ( clientNum = = - 2 )
{
clientNum = level . follow2 ;
}
if ( clientNum > = 0 )
{
cl = & level . clients [ clientNum ] ;
if ( cl - > pers . connected = = CON_CONNECTED & & cl - > sess . sessionTeam ! = TEAM_SPECTATOR )
{
ent - > client - > ps = cl - > ps ;
ps - > pm_flags | = PMF_FOLLOW ;
return ;
}
else
{
// drop them to free spectators unless they are dedicated camera followers
if ( sess - > spectatorClient > = 0 )
{
sess - > spectatorState = SPECTATOR_FREE ;
G_Client_Begin ( ent - > client - level . clients , qfalse , qfalse , qfalse ) ;
}
}
}
}
if ( sess - > spectatorState = = SPECTATOR_SCOREBOARD )
{
ps - > pm_flags | = PMF_SCOREBOARD ;
}
else
{
ps - > pm_flags & = ~ PMF_SCOREBOARD ;
}
2014-10-09 18:15:26 +00:00
}
2018-06-30 14:47:06 +00:00
void ClientEndFrame ( gentity_t * ent )
{
int32_t i ;
playerState_t * ps = & ent - > client - > ps ;
if ( ent - > client - > sess . sessionTeam = = TEAM_SPECTATOR /*|| (ps->eFlags&EF_ELIMINATED)*/ )
{
SpectatorClientEndFrame ( ent ) ;
ent - > client - > noclip = qtrue ;
return ;
}
// turn off any expired powerups
for ( i = 0 ; i < MAX_POWERUPS ; i + + )
{
if ( ps - > powerups [ i ] < level . time )
{
ps - > powerups [ i ] = 0 ;
}
}
// save network bandwidth
2014-10-09 18:15:26 +00:00
#if 0
2018-06-30 14:47:06 +00:00
if ( ! g_synchronousClients - > integer & & ps - > pm_type = = PM_NORMAL )
{
// FIXME: this must change eventually for non-sync demo recording
VectorClear ( ps - > viewangles ) ;
}
2014-10-09 18:15:26 +00:00
# endif
2018-06-30 14:47:06 +00:00
//
// If the end of unit layout is displayed, don't give
// the player any normal movement attributes
//
if ( level . intermissiontime )
{
return ;
}
// burn from lava, etc
P_WorldEffects ( ent ) ;
// apply all the damage taken this frame
P_DamageFeedback ( ent ) ;
// add the EF_CONNECTION flag if we haven't gotten commands recently
if ( level . time - ent - > client - > lastCmdTime > 1000 )
{
ent - > s . eFlags | = EF_CONNECTION ;
}
else
{
ent - > s . eFlags & = ~ EF_CONNECTION ;
}
ps - > stats [ STAT_HEALTH ] = ent - > health ; // FIXME: get rid of ent->health...
G_SetClientSound ( ent ) ;
// set the latest infor
BG_PlayerStateToEntityState ( ps , & ent - > s , qtrue ) ;
SendPendingPredictableEvents ( ps ) ;
2014-10-09 18:15:26 +00:00
}