/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2002-2009 Q3Rally Team (Per Thormann - perle@q3rally.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 ); 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 ) { // removed angles gentity_t *tent; // 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; // Zygote // Remove angles and "spit-out" /* // 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; // Zygote // Remove angles /* // set angles SetClientViewAngle( player, angles ); */ // STONELANCE - reset car // PM_InitializeVehicle( &player->client->car, origin, angles, player->client->ps.velocity, car_frontweight_dist.value ); VectorCopy( origin, player->client->ps.origin ); 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; } } }