/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2013 Darklegion Development This file is part of Tremulous. Tremulous 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. Tremulous 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 Tremulous; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #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_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 ) { // 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; // 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; // toggle the teleport bit so the client knows to not lerp player->client->ps.eFlags ^= EF_TELEPORT_BIT; G_UnlaggedClear( player ); // cut all relevant zap beams G_ClearPlayerZapEffects( player ); // set angles G_SetClientViewAngle( player, angles ); // 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.spectatorState == SPECTATOR_NOT ) { // kill anything at the destination G_KillBox( player ); 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_portal_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.misc = 0; } else ent->s.misc = 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.0f * 256; } /* ====================================================================== NEAT EFFECTS AND STUFF FOR TREMULOUS ====================================================================== */ void SP_toggle_particle_system( gentity_t *self ) { //toggle EF_NODRAW self->s.eFlags ^= EF_NODRAW; self->nextthink = 0; } /* =============== SP_use_particle_system Use function for particle_system =============== */ void SP_use_particle_system( gentity_t *self, gentity_t *other, gentity_t *activator ) { SP_toggle_particle_system( self ); if( self->wait > 0.0f ) { self->think = SP_toggle_particle_system; self->nextthink = level.time + (int)( self->wait * 1000 ); } } /* =============== SP_spawn_particle_system Spawn function for particle system =============== */ void SP_misc_particle_system( gentity_t *self ) { char *s; G_SetOrigin( self, self->s.origin ); G_SpawnString( "psName", "", &s ); G_SpawnFloat( "wait", "0", &self->wait ); //add the particle system to the client precache list self->s.modelindex = G_ParticleSystemIndex( s ); if( self->spawnflags & 1 ) self->s.eFlags |= EF_NODRAW; self->use = SP_use_particle_system; self->s.eType = ET_PARTICLE_SYSTEM; trap_LinkEntity( self ); } /* =============== SP_use_anim_model Use function for anim model =============== */ void SP_use_anim_model( gentity_t *self, gentity_t *other, gentity_t *activator ) { if( self->spawnflags & 1 ) { //if spawnflag 1 is set //toggle EF_NODRAW if( self->s.eFlags & EF_NODRAW ) self->s.eFlags &= ~EF_NODRAW; else self->s.eFlags |= EF_NODRAW; } else { //if the animation loops then toggle the animation //toggle EF_MOVER_STOP if( self->s.eFlags & EF_MOVER_STOP ) self->s.eFlags &= ~EF_MOVER_STOP; else self->s.eFlags |= EF_MOVER_STOP; } } /* =============== SP_misc_anim_model Spawn function for anim model =============== */ void SP_misc_anim_model( gentity_t *self ) { self->s.misc = (int)self->animation[ 0 ]; self->s.weapon = (int)self->animation[ 1 ]; self->s.torsoAnim = (int)self->animation[ 2 ]; self->s.legsAnim = (int)self->animation[ 3 ]; self->s.angles2[ 0 ] = self->pos2[ 0 ]; //add the model to the client precache list self->s.modelindex = G_ModelIndex( self->model ); self->use = SP_use_anim_model; self->s.eType = ET_ANIMMAPOBJ; // spawn with animation stopped if( self->spawnflags & 2 ) self->s.eFlags |= EF_MOVER_STOP; trap_LinkEntity( self ); } /* =============== SP_use_light_flare Use function for light flare =============== */ void SP_use_light_flare( gentity_t *self, gentity_t *other, gentity_t *activator ) { self->s.eFlags ^= EF_NODRAW; } /* =============== findEmptySpot Finds an empty spot radius units from origin ============== */ static void findEmptySpot( vec3_t origin, float radius, vec3_t spot ) { int i, j, k; vec3_t delta, test, total; trace_t tr; VectorClear( total ); //54(!) traces to test for empty spots for( i = -1; i <= 1; i++ ) { for( j = -1; j <= 1; j++ ) { for( k = -1; k <= 1; k++ ) { VectorSet( delta, ( i * radius ), ( j * radius ), ( k * radius ) ); VectorAdd( origin, delta, test ); trap_Trace( &tr, test, NULL, NULL, test, ENTITYNUM_NONE, MASK_SOLID ); if( !tr.allsolid ) { trap_Trace( &tr, test, NULL, NULL, origin, ENTITYNUM_NONE, MASK_SOLID ); VectorScale( delta, tr.fraction, delta ); VectorAdd( total, delta, total ); } } } } VectorNormalize( total ); VectorScale( total, radius, total ); VectorAdd( origin, total, spot ); } /* =============== SP_misc_light_flare Spawn function for light flare =============== */ void SP_misc_light_flare( gentity_t *self ) { self->s.eType = ET_LIGHTFLARE; self->s.modelindex = G_ShaderIndex( self->targetShaderName ); VectorCopy( self->pos2, self->s.origin2 ); //try to find a spot near to the flare which is empty. This //is used to facilitate visibility testing findEmptySpot( self->s.origin, 8.0f, self->s.angles2 ); self->use = SP_use_light_flare; G_SpawnFloat( "speed", "200", &self->speed ); self->s.time = self->speed; G_SpawnInt( "mindist", "0", &self->s.generic1 ); if( self->spawnflags & 1 ) self->s.eFlags |= EF_NODRAW; trap_LinkEntity( self ); }