q3rally/engine/code/game/g_misc.c
q3rally ae3c64ee50 commit for 0.0.3.0 release (code freeze)
tweaked telefrag gun
updated roadmap
updated discord logo in main menu
version upcount
2021-05-13 16:02:43 +00:00

769 lines
21 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2002-2021 Q3Rally Team (Per Thormann - q3rally@gmail.com)
This file is part of q3rally source code.
q3rally 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.
q3rally 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 q3rally; 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 ) {
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;
// STONELANCE - reset car
// PM_InitializeVehicle( &player->client->car, origin, angles, player->client->ps.velocity, car_frontweight_dist.value );
VectorCopy( origin, player->client->ps.origin );
if (!noAngles) {
VectorCopy( angles, player->client->ps.viewangles );
}
player->client->car.initializeOnNextMove = qtrue;
// END
// kill anything at the destination
if ( player->client->sess.sessionTeam != TEAM_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 ) {
trap_LinkEntity (player);
}
}
//==================================================
// TelefragPlayer
//==================================================
void TelefragPlayer( 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 ) {
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;
// STONELANCE - reset car
// PM_InitializeVehicle( &player->client->car, origin, angles, player->client->ps.velocity, car_frontweight_dist.value );
VectorCopy( origin, player->client->ps.origin );
if (!noAngles) {
VectorCopy( angles, player->client->ps.viewangles );
}
player->client->car.initializeOnNextMove = qtrue;
// END
// kill anything at the destination
if ( player->client->sess.sessionTeam != TEAM_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 ) {
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);
}
#ifdef MISSIONPACK
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;
}
}
}
#endif
/*QUAKED func_breakglass (1 0 0) (-16 -16 -16) (16 16 16)
We have breakable glass in our mod , if you hit it till health 0 it will explode and spawn models
*/
void SP_func_breakglass( gentity_t *ent ) {
int health;
// It's a model brush , show it
trap_SetBrushModel( ent, ent->model );
// the default health will be set to 25
G_SpawnInt( "health", "0", &health );
if( health <= 0 )
health = 25;
// set health on the function
ent->health = health;
// our function can receive damage
ent->takedamage = qtrue;
// that's our new ET(EntityType)
ent->s.eType = ET_BREAKGLASS;
// additional brush model
if ( ent->model2 ) {
ent->s.modelindex2 = G_ModelIndex( ent->model2 );
}
// link the entity ( otherwise nothing will work :) )
trap_LinkEntity (ent);
}
/*
=================
G_BreakGlass
=================
*/
void G_BreakGlass(gentity_t *ent, vec3_t point, int mod) {
gentity_t *tent;
vec3_t size;
vec3_t center;
qboolean splashdmg;
// Lanz: Cleaned up compiler errors.
// Get the center of the glass
VectorSubtract(ent->r.maxs, ent->r.mins, size);
VectorScale(size, 0.5, size);
VectorAdd(ent->r.mins, size, center);
// if the health of our glass brush is at 0 we'll break it
if( ent->health <= 0 ) {
G_FreeEntity( ent );
switch( mod ) {
case MOD_GAUNTLET:
splashdmg = qfalse;
break;
default:
splashdmg = qtrue;
break;
}
// Now we call our EVs(Events) so that cgame can work on it
switch( splashdmg ){
case qtrue:
tent = G_TempEntity( center, EV_BREAK_GLASS );
tent->s.eventParm = 0;
break;
case qfalse:
tent = G_TempEntity( point, EV_BREAK_GLASS );
tent->s.eventParm = 0;
break;
}
}
}
/*QUAKED func_breakwood (1 0 0) (-16 -16 -16) (16 16 16)
we have breakable wood
*/
void SP_func_breakwood( gentity_t *ent ) {
int health;
// It's a model brush , show it
trap_SetBrushModel( ent, ent->model );
// the default health will be set to 25
G_SpawnInt( "health", "0", &health );
if( health <= 0 )
health = 50;
// set health on the function
ent->health = health;
// our function can receive damage
ent->takedamage = qtrue;
// that's our new ET(EntityType)
ent->s.eType = ET_BREAKWOOD;
// additional brush model
if ( ent->model2 ) {
ent->s.modelindex2 = G_ModelIndex( ent->model2 );
}
// link the entity ( otherwise nothing will work :) )
trap_LinkEntity (ent);
}
/*
=================
G_BREAKWOOD
=================
*/
void G_BREAKWOOD(gentity_t *ent, vec3_t point, int mod) {
gentity_t *tent;
vec3_t size;
vec3_t center;
qboolean splashdmg;
// Lanz: cleaned up compiler errors.
// Get the center of the BOX
VectorSubtract(ent->r.maxs, ent->r.mins, size);
VectorScale(size, 0.5, size);
VectorAdd(ent->r.mins, size, center);
// if the health of our wood brush is at 0 we'll move it
if( ent->health <= 0 ) {
G_FreeEntity( ent );
switch( mod ) {
case MOD_GAUNTLET:
splashdmg = qfalse;
break;
default:
splashdmg = qtrue;
break;
}
// Now we call our EVs(Events) so that cgame can work on it
switch( splashdmg ){
case qtrue:
tent = G_TempEntity( center, EV_BREAKWOOD );
tent->s.eventParm = 0;
break;
case qfalse:
tent = G_TempEntity( point, EV_BREAKWOOD );
tent->s.eventParm = 0;
break;
}
}
}
/*QUAKED func_breakmetal (1 0 0) (-16 -16 -16) (16 16 16)
We have breakable metal in our mod , if you hit it till health 0 it will explode and spawn models
*/
void SP_func_breakmetal( gentity_t *ent ) {
int health;
// It's a model brush , show it
trap_SetBrushModel( ent, ent->model );
// the default health will be set to 25
G_SpawnInt( "health", "0", &health );
if( health <= 0 )
health = 50;
// set health on the function
ent->health = health;
// our function can receive damage
ent->takedamage = qtrue;
// that's our new ET(EntityType)
ent->s.eType = ET_BREAKMETAL;
// additional brush model
if ( ent->model2 ) {
ent->s.modelindex2 = G_ModelIndex( ent->model2 );
}
// link the entity ( otherwise nothing will work :) )
trap_LinkEntity (ent);
}
/*
=================
G_BreakMetal
=================
*/
void G_BREAKMETAL(gentity_t *ent, vec3_t point, int mod) {
gentity_t *tent;
vec3_t size;
vec3_t center;
qboolean splashdmg;
// Lanz: Cleaned up compiler errors.
// Get the center of the metal
VectorSubtract(ent->r.maxs, ent->r.mins, size);
VectorScale(size, 0.5, size);
VectorAdd(ent->r.mins, size, center);
// if the health of our metal brush is at 0 we'll break it
if( ent->health <= 0 ) {
G_FreeEntity( ent );
switch( mod ) {
case MOD_GAUNTLET:
splashdmg = qfalse;
break;
default:
splashdmg = qtrue;
break;
}
// Now we call our EVs(Events) so that cgame can work on it
switch( splashdmg ){
case qtrue:
tent = G_TempEntity( center, EV_BREAKMETAL );
tent->s.eventParm = 0;
break;
case qfalse:
tent = G_TempEntity( point, EV_BREAKMETAL );
tent->s.eventParm = 0;
break;
}
}
}