og-openarena-gamecode/code/game/g_misc.c
sago007 0bee41a517 Pull request #35. Replaces all the complicated gametype checks with helper functions
Squashed commit of the following:

commit c05ffecf38
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 22:53:54 2018 -0300

    This is also not needed anymore.

commit 14abe7715a
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 22:29:26 2018 -0300

    Last checks found. Hopefully that's enough.

commit cad2ab0c5a
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 22:14:33 2018 -0300

    We don't need g_ffa_gt anymore.

commit 2577547db4
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 22:04:09 2018 -0300

    More checks located.

commit d9c96477c6
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 21:29:41 2018 -0300

    Had to do this just to get it to compile.

commit 2089039745
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 21:18:25 2018 -0300

    Fixing several syntax errors.

commit 0edba34607
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 21:07:23 2018 -0300

    Oops... my mistakes. -.-

commit 05e9ef79d4
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 21:00:00 2018 -0300

    New checks for team flags, team obelisks, control points and rounds.

commit 06e325ab00
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Thu Nov 29 17:10:48 2018 -0300

    Some syntax errors fixed.

commit e394d4fc54
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Thu Nov 29 17:00:01 2018 -0300

    Finished the splitting.

commit 5e267d161f
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Thu Nov 29 16:34:01 2018 -0300

    Splitting the function.

commit 7c643f14aa
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Thu Nov 29 12:36:14 2018 -0300

    Syntax error.

commit aba598fde4
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Thu Nov 29 12:26:58 2018 -0300

    All the isTeam checks were moved to a centralized function.

commit b9a61dbe45
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Wed Nov 28 21:26:34 2018 -0300

    Revert "Toggleable missionpack checks. Disables team models and relevant checks interfering with testing. Some comments were also modified in order to work with SciTE."

    This reverts commit fe4c340e35.

commit fe4c340e35
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Wed Nov 28 21:21:31 2018 -0300

    Toggleable missionpack checks. Disables team models and relevant checks interfering with testing. Some comments were also modified in order to work with SciTE.
2018-12-01 10:43:03 +01:00

579 lines
15 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
//
// g_misc.c
#include "g_local.h"
/*QUAKED func_group (0 0 0) ?
Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities.
*/
/*QUAKED info_camp (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
*/
void SP_info_camp( gentity_t *self ) {
G_SetOrigin( self, self->s.origin );
}
/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
*/
void SP_info_null( gentity_t *self ) {
G_FreeEntity( self );
}
/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for in-game calculation, like jumppad targets.
target_position does the same thing
*/
void SP_info_notnull( gentity_t *self ){
G_SetOrigin( self, self->s.origin );
}
/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear
Non-displayed light.
"light" overrides the default 300 intensity.
Linear checbox gives linear falloff instead of inverse square
Lights pointed at a target will be spotlights.
"radius" overrides the default 64 unit radius of a spotlight at the target point.
*/
void SP_light( gentity_t *self ) {
G_FreeEntity( self );
}
/*
=================================================================================
TELEPORTERS
=================================================================================
*/
void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) {
gentity_t *tent;
qboolean noAngles;
noAngles = (angles[0] > 999999.0);
// use temp events at source and destination to prevent the effect
// from getting dropped by a second player event
if ( player->client->sess.sessionTeam != TEAM_SPECTATOR && player->client->ps.pm_type != PM_SPECTATOR) {
tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
tent->s.clientNum = player->s.clientNum;
tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN );
tent->s.clientNum = player->s.clientNum;
}
// unlink to make sure it can't possibly interfere with G_KillBox
trap_UnlinkEntity (player);
VectorCopy ( origin, player->client->ps.origin );
player->client->ps.origin[2] += 1;
if (!noAngles) {
// spit the player out
AngleVectors( angles, player->client->ps.velocity, NULL, NULL );
VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity );
player->client->ps.pm_time = 160; // hold time
player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
// set angles
SetClientViewAngle(player, angles);
}
// toggle the teleport bit so the client knows to not lerp
player->client->ps.eFlags ^= EF_TELEPORT_BIT;
//unlagged - backward reconciliation #3
// we don't want players being backward-reconciled back through teleporters
G_ResetHistory( player );
//unlagged - backward reconciliation #3
// kill anything at the destination
if ( player->client->sess.sessionTeam != TEAM_SPECTATOR && player->client->ps.pm_type != PM_SPECTATOR ) {
G_KillBox (player);
}
// save results of pmove
BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue );
// use the precise origin for linking
VectorCopy( player->client->ps.origin, player->r.currentOrigin );
if ( player->client->sess.sessionTeam != TEAM_SPECTATOR && player->client->ps.pm_type != PM_SPECTATOR ) {
trap_LinkEntity (player);
}
}
/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
Point teleporters at these.
Now that we don't have teleport destination pads, this is just
an info_notnull
*/
void SP_misc_teleporter_dest( gentity_t *ent ) {
}
//===========================================================
/*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16)
"model" arbitrary .md3 file to display
*/
void SP_misc_model( gentity_t *ent ) {
#if 0
ent->s.modelindex = G_ModelIndex( ent->model );
VectorSet (ent->mins, -16, -16, -16);
VectorSet (ent->maxs, 16, 16, 16);
trap_LinkEntity (ent);
G_SetOrigin( ent, ent->s.origin );
VectorCopy( ent->s.angles, ent->s.apos.trBase );
#else
G_FreeEntity( ent );
#endif
}
//===========================================================
void locateCamera( gentity_t *ent ) {
vec3_t dir;
gentity_t *target;
gentity_t *owner;
owner = G_PickTarget( ent->target );
if ( !owner ) {
G_Printf( "Couldn't find target for misc_partal_surface\n" );
G_FreeEntity( ent );
return;
}
ent->r.ownerNum = owner->s.number;
// frame holds the rotate speed
if ( owner->spawnflags & 1 ) {
ent->s.frame = 25;
} else if ( owner->spawnflags & 2 ) {
ent->s.frame = 75;
}
// swing camera ?
if ( owner->spawnflags & 4 ) {
// set to 0 for no rotation at all
ent->s.powerups = 0;
}
else {
ent->s.powerups = 1;
}
// clientNum holds the rotate offset
ent->s.clientNum = owner->s.clientNum;
VectorCopy( owner->s.origin, ent->s.origin2 );
// see if the portal_camera has a target
target = G_PickTarget( owner->target );
if ( target ) {
VectorSubtract( target->s.origin, owner->s.origin, dir );
VectorNormalize( dir );
} else {
G_SetMovedir( owner->s.angles, dir );
}
ent->s.eventParm = DirToByte( dir );
}
/*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
This must be within 64 world units of the surface!
*/
void SP_misc_portal_surface(gentity_t *ent) {
VectorClear( ent->r.mins );
VectorClear( ent->r.maxs );
trap_LinkEntity (ent);
ent->r.svFlags = SVF_PORTAL;
ent->s.eType = ET_PORTAL;
if ( !ent->target ) {
VectorCopy( ent->s.origin, ent->s.origin2 );
} else {
ent->think = locateCamera;
ent->nextthink = level.time + 100;
}
}
/*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate noswing
The target for a misc_portal_director. You can set either angles or target another entity to determine the direction of view.
"roll" an angle modifier to orient the camera around the target vector;
*/
void SP_misc_portal_camera(gentity_t *ent) {
float roll;
VectorClear( ent->r.mins );
VectorClear( ent->r.maxs );
trap_LinkEntity (ent);
G_SpawnFloat( "roll", "0", &roll );
ent->s.clientNum = roll/360.0 * 256;
}
/*
======================================================================
SHOOTERS
======================================================================
*/
void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
vec3_t dir;
float deg;
vec3_t up, right;
// see if we have a target
if ( ent->enemy ) {
VectorSubtract( ent->enemy->r.currentOrigin, ent->s.origin, dir );
VectorNormalize( dir );
} else {
VectorCopy( ent->movedir, dir );
}
// randomize a bit
PerpendicularVector( up, dir );
CrossProduct( up, dir, right );
deg = crandom() * ent->random;
VectorMA( dir, deg, up, dir );
deg = crandom() * ent->random;
VectorMA( dir, deg, right, dir );
VectorNormalize( dir );
switch ( ent->s.weapon ) {
case WP_GRENADE_LAUNCHER:
fire_grenade( ent, ent->s.origin, dir );
break;
case WP_ROCKET_LAUNCHER:
fire_rocket( ent, ent->s.origin, dir );
break;
case WP_PLASMAGUN:
fire_plasma( ent, ent->s.origin, dir );
break;
}
G_AddEvent( ent, EV_FIRE_WEAPON, 0 );
}
static void InitShooter_Finish( gentity_t *ent ) {
ent->enemy = G_PickTarget( ent->target );
ent->think = 0;
ent->nextthink = 0;
}
void InitShooter( gentity_t *ent, int weapon ) {
ent->use = Use_Shooter;
ent->s.weapon = weapon;
RegisterItem( BG_FindItemForWeapon( weapon ) );
G_SetMovedir( ent->s.angles, ent->movedir );
if ( !ent->random ) {
ent->random = 1.0;
}
ent->random = sin( M_PI * ent->random / 180 );
// target might be a moving object, so we can't set movedir for it
if ( ent->target ) {
ent->think = InitShooter_Finish;
ent->nextthink = level.time + 500;
}
trap_LinkEntity( ent );
}
/*QUAKED shooter_rocket (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" the number of degrees of deviance from the taget. (1.0 default)
*/
void SP_shooter_rocket( gentity_t *ent ) {
InitShooter( ent, WP_ROCKET_LAUNCHER );
}
/*QUAKED shooter_plasma (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" is the number of degrees of deviance from the taget. (1.0 default)
*/
void SP_shooter_plasma( gentity_t *ent ) {
InitShooter( ent, WP_PLASMAGUN);
}
/*QUAKED shooter_grenade (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" is the number of degrees of deviance from the taget. (1.0 default)
*/
void SP_shooter_grenade( gentity_t *ent ) {
InitShooter( ent, WP_GRENADE_LAUNCHER);
}
static void PortalDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod) {
G_FreeEntity( self );
//FIXME do something more interesting
}
void DropPortalDestination( gentity_t *player ) {
gentity_t *ent;
vec3_t snapped;
// create the portal destination
ent = G_Spawn();
ent->s.modelindex = G_ModelIndex( "models/powerups/teleporter/tele_exit.md3" );
VectorCopy( player->s.pos.trBase, snapped );
SnapVector( snapped );
G_SetOrigin( ent, snapped );
VectorCopy( player->r.mins, ent->r.mins );
VectorCopy( player->r.maxs, ent->r.maxs );
ent->classname = "hi_portal destination";
ent->s.pos.trType = TR_STATIONARY;
ent->r.contents = CONTENTS_CORPSE;
ent->takedamage = qtrue;
ent->health = 200;
ent->die = PortalDie;
VectorCopy( player->s.apos.trBase, ent->s.angles );
ent->think = G_FreeEntity;
ent->nextthink = level.time + 2 * 60 * 1000;
trap_LinkEntity( ent );
player->client->portalID = ++level.portalSequence;
ent->count = player->client->portalID;
// give the item back so they can drop the source now
player->client->ps.stats[STAT_HOLDABLE_ITEM] = BG_FindItem( "Portal" ) - bg_itemlist;
}
static void PortalTouch( gentity_t *self, gentity_t *other, trace_t *trace) {
gentity_t *destination;
// see if we will even let other try to use it
if( other->health <= 0 ) {
return;
}
if( !other->client ) {
return;
}
// if( other->client->ps.persistant[PERS_TEAM] != self->spawnflags ) {
// return;
// }
if ( other->client->ps.powerups[PW_NEUTRALFLAG] ) { // only happens in One Flag CTF
Drop_Item( other, BG_FindItemForPowerup( PW_NEUTRALFLAG ), 0 );
other->client->ps.powerups[PW_NEUTRALFLAG] = 0;
}
else if ( other->client->ps.powerups[PW_REDFLAG] ) { // only happens in standard CTF
Drop_Item( other, BG_FindItemForPowerup( PW_REDFLAG ), 0 );
other->client->ps.powerups[PW_REDFLAG] = 0;
}
else if ( other->client->ps.powerups[PW_BLUEFLAG] ) { // only happens in standard CTF
Drop_Item( other, BG_FindItemForPowerup( PW_BLUEFLAG ), 0 );
other->client->ps.powerups[PW_BLUEFLAG] = 0;
}
// find the destination
destination = NULL;
while( (destination = G_Find(destination, FOFS(classname), "hi_portal destination")) != NULL ) {
if( destination->count == self->count ) {
break;
}
}
// if there is not one, die!
if( !destination ) {
if( self->pos1[0] || self->pos1[1] || self->pos1[2] ) {
TeleportPlayer( other, self->pos1, self->s.angles );
}
G_Damage( other, other, other, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG );
return;
}
TeleportPlayer( other, destination->s.pos.trBase, destination->s.angles );
}
static void PortalEnable( gentity_t *self ) {
self->touch = PortalTouch;
self->think = G_FreeEntity;
self->nextthink = level.time + 2 * 60 * 1000;
}
void DropPortalSource( gentity_t *player ) {
gentity_t *ent;
gentity_t *destination;
vec3_t snapped;
// create the portal source
ent = G_Spawn();
ent->s.modelindex = G_ModelIndex( "models/powerups/teleporter/tele_enter.md3" );
VectorCopy( player->s.pos.trBase, snapped );
SnapVector( snapped );
G_SetOrigin( ent, snapped );
VectorCopy( player->r.mins, ent->r.mins );
VectorCopy( player->r.maxs, ent->r.maxs );
ent->classname = "hi_portal source";
ent->s.pos.trType = TR_STATIONARY;
ent->r.contents = CONTENTS_CORPSE | CONTENTS_TRIGGER;
ent->takedamage = qtrue;
ent->health = 200;
ent->die = PortalDie;
trap_LinkEntity( ent );
ent->count = player->client->portalID;
player->client->portalID = 0;
// ent->spawnflags = player->client->ps.persistant[PERS_TEAM];
ent->nextthink = level.time + 1000;
ent->think = PortalEnable;
// find the destination
destination = NULL;
while( (destination = G_Find(destination, FOFS(classname), "hi_portal destination")) != NULL ) {
if( destination->count == ent->count ) {
VectorCopy( destination->s.pos.trBase, ent->pos1 );
break;
}
}
}
static int countItems(const char* itemname, qboolean exclude_no_bots) {
gentity_t *spot;
int count;
count = 0;
spot = NULL;
while ((spot = G_Find (spot, FOFS(classname), itemname)) != NULL) {
if(exclude_no_bots && spot->flags & (FL_NO_BOTS|FL_NO_HUMANS) )
continue; //Do not count no_humans or no_bots items
count++;
}
G_Printf("Number of %s: %i\n",itemname,count);
return count;
}
static int countFfaSpawnpoints(void) {
return countItems("info_player_deathmatch",qtrue);
}
static int countDdSpawnpoints(void) {
int mincount,tmp;
mincount = 100;
tmp = countItems("info_player_dd_red",qtrue);
if(!tmp){
tmp = countFfaSpawnpoints(); //tmp==0 -> Fallback to FFA spawnpoints!
}
if(tmp<mincount)
mincount=tmp;
tmp = countItems("info_player_dd_blue",qtrue);
if(!tmp){
tmp = countFfaSpawnpoints(); //tmp==0 -> Fallback to FFA spawnpoints!
}
if(tmp<mincount)
mincount=tmp;
return mincount;
}
static int countCtfSpawnpoints(void) {
int mincount,tmp;
mincount=100;
tmp = countItems("team_CTF_redplayer",qtrue);
if(!tmp){
tmp = countFfaSpawnpoints(); //tmp==0 -> Fallback to FFA spawnpoints!
}
if(tmp<mincount) {
mincount=tmp;
}
tmp = countItems("team_CTF_blueplayer",qtrue);
if(!tmp){
tmp = countFfaSpawnpoints(); //tmp==0 -> Fallback to FFA spawnpoints!
}
if(tmp<mincount) {
mincount=tmp;
}
tmp = countItems("team_CTF_redspawn",qtrue);
if(!tmp){
tmp = countFfaSpawnpoints(); //tmp==0 -> Fallback to FFA spawnpoints!
}
if(tmp<mincount)
mincount=tmp;
tmp = countItems("team_CTF_bluespawn",qtrue);
if(!tmp){
tmp = countFfaSpawnpoints(); //tmp==0 -> Fallback to FFA spawnpoints!
}
if(tmp<mincount) {
mincount=tmp;
}
return mincount;
}
int MinSpawnpointCount(void) {
if(!G_IsATeamGametype(g_gametype.integer)) {
return countFfaSpawnpoints();
}
if(g_gametype.integer == GT_DOUBLE_D ) {
return countDdSpawnpoints();
}
return countCtfSpawnpoints();
}