// Copyright (C) 1999-2000 Id Software, Inc. // // g_misc.c #include "g_local.h" /*QUAKED func_group (0 0 0) ? -----DESCRIPTION----- Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities. -----SPAWNFLAGS----- none -----KEYS----- q3map2: "_lightmapscale" - set a diffrent lightmapscale for this func group only */ /*QUAKED info_camp (0 0.5 0) (-4 -4 -4) (4 4 4) -----DESCRIPTION----- Used as a positional target for calculations in the compiler/utilities (spotlights, etc), but removed during gameplay. -----SAPWNFLAGS----- none -----KEYS----- "targetname" - have whatever is required point at this. */ // Lol, this is contradictory, should free but instead sets origin... Description sais removed so maybe merge with info_null. 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) -----DESCRIPTION----- Used as a positional target for calculations in the compiler/utilities (spotlights, etc), but removed during gameplay. -----SAPWNFLAGS----- none -----KEYS----- "targetname" - have whatever is required point at this. */ void SP_info_null( gentity_t *self ) { G_FreeEntity( self ); } /*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) -----DESCRIPTION----- Used as a positional target for in-game calculation, like jumppad targets. target_position does the same thing -----SPAWNFLAGS----- none -----KEYS----- "targetname" - have whatever is required point at this. */ //these share the spawnfunction with info_notnull /*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) -----DESCRIPTION----- Merely a fancy name for info_notnull. was originally used for teleporters but became redundant. -----SPAWNFLAGS----- none -----KEYS----- "targetname" - have whatever is required point at this. */ /*QUAKED target_position (0 0.5 0) (-4 -4 -4) (4 4 4) -----DESCRIPTION----- Merely a fancy name for info_notnull. -----SPAWNFLAGS----- none -----KEYS----- "targetname" - have whatever is required point at this. */ void SP_info_notnull( gentity_t *self ){ if(!Q_stricmp(self->classname, "ref_tag") && !rpg_allowspmaps.integer) G_FreeEntity(self); if(strcmp(self->classname, "info_notnull")) { self->classname = G_NewString("info_notnull"); } G_SetOrigin( self, self->s.origin ); } /*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) LINEAR NO_INCIDENCE X X NO_GRID NORMALIZED_COLOR FORCE_DISTANCE_ATTENUATION -----DESCRIPTION----- Light source for the compiler. Will be removed ingame. Lights pointed at a target (info_null) will be spotlights. -----SPAWNFLAGS----- 1: LINEAR - checkbox gives linear falloff instead of inverse square 2: NO_INCIDENCE - checkbox makes lighting smoother 4: X - Unknown. Usage not recomended. 8: X - Unknown. Usage not recomended. q3map2: 16: NO_GRID - light does not affect the grid 32: NORMALIZED_COLOR - light color gets normalized by the compiler 64: FORCE_DISTANCE_ATTENUATION - distance attenuation is enforced -----KEYS----- "light" - overrides the default 300 intensity. "radius" - overrides the default 64 unit radius of a spotlight at the target point. "_color" - light color q3map2: "_style" - light style number "fade" - Fade factor of light attenuation of linear lights. (Linear lights vanish at light/(fade * 8000). "_anglescale" - scales angle attenuation "scale" - intensity multiplier "_samples" - number of samples to use to get soft shadows from a light "_deviance" - position deviance of the samples of a regular light "_filterradius" - filter radius for this light "_sun" - if 1, this light is an infinite sun light "_flareshader" - shader for a flare surface generated by this light "_flare" - when set, this light is a flare without a specified shader */ void SP_light( gentity_t *self ) { G_FreeEntity( self ); } /* ================================================================================= TELEPORTERS ================================================================================= */ void TransportPlayer( gentity_t *player, vec3_t origin, vec3_t angles, int speed ) { gentity_t *tent = NULL; playerState_t *ps = &player->client->ps; clientSession_t *sess = &player->client->sess; // use temp events at source and destination to prevent the effect // from getting dropped by a second player event if ( sess->sessionTeam != TEAM_SPECTATOR /*&& !(ps->eFlags&EF_ELIMINATED)*/ ) { vec3_t org; VectorCopy( ps->origin, org ); org[2] += (ps->viewheight >> 1); tent = G_TempEntity( 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, ps->origin ); ps->origin[2] += 1; // spit the player out AngleVectors( angles, ps->velocity, NULL, NULL ); VectorScale( ps->velocity, speed, ps->velocity ); ps->pm_time = 160; // hold time ps->pm_flags |= PMF_TIME_KNOCKBACK; // toggle the teleport bit so the client knows to not lerp ps->eFlags ^= EF_TELEPORT_BIT; // set angles G_Client_SetViewAngle( player, angles ); // kill anything at the destination if ( sess->sessionTeam != TEAM_SPECTATOR /*&& !(ps->eFlags&EF_ELIMINATED)*/) { G_KillBox (player); } // save results of pmove BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); // use the precise origin for linking VectorCopy( ps->origin, player->r.currentOrigin ); if ( sess->sessionTeam != TEAM_SPECTATOR /*&& !(ps->eFlags&EF_ELIMINATED)*/) { trap_LinkEntity (player); } } void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles, tpType_t tpType ) { gentity_t *tent; playerState_t *ps = &player->client->ps; clientSession_t *sess = &player->client->sess; // unlink to make sure it can't possibly interfere with G_KillBox trap_UnlinkEntity (player); VectorCopy ( origin, ps->origin ); // use temp events at source and destination to prevent the effect // from getting dropped by a second player event if ( sess->sessionTeam != TEAM_SPECTATOR ) { if ( tpType == TP_BORG ) { // ...we are borg...prepare to be... tent = G_TempEntity( origin, EV_BORG_TELEPORT ); tent->s.clientNum = player->s.clientNum; } //RPG-X: J2J Added to get fed trans effect without any traveling after beam in //TiM: Since the SP teleporter has been coded to only work with Jay's modification, //we'll add the second half of the client-side effects (ie materialization) here else if( tpType == TP_TRI_TP ) { // probably isn't necessary, but just in case, end the beam out powerup ps->powerups[PW_BEAM_OUT] = 0; //and add the beam in one ps->powerups[PW_QUAD] = level.time + 4000; tent = G_TempEntity( ps->origin, EV_PLAYER_TRANSPORT_IN ); tent->s.clientNum = player->s.clientNum; } } // spit the player out //TiM - If in a turbolift and moving, get their velocity, perform the rotation //calc on it, and then reset it to their velocity. if ( tpType == TP_TURBO ) { vec3_t dir; vec3_t velAngles; float length; VectorCopy( ps->velocity, dir ); length = VectorLength( dir ); VectorNormalize( dir ); vectoangles( dir, velAngles ); velAngles[YAW] = AngleNormalize360( velAngles[YAW]+ (angles[YAW] - ps->viewangles[YAW] ) ); AngleVectors( velAngles, dir, NULL, NULL ); VectorScale( dir, length, ps->velocity); } else { //TiM: Set the velocity to 0. So if they were moving b4 the transport, they'll be stopped when they come out. //It's a little something called the Heisenberg compensators. ;) //bug-fix. if the velocity is killed in a spectator door teleporter, the player //gets wedged in the door. >_< if ( sess->sessionTeam != TEAM_SPECTATOR ) VectorScale( ps->velocity, 0, ps->velocity ); else { AngleVectors( angles, ps->velocity, NULL, NULL ); VectorScale( ps->velocity, 400, ps->velocity ); ps->pm_time = 160; // hold time ps->pm_flags |= PMF_TIME_KNOCKBACK; } {//see if we can move the player up one vec3_t newOrg; trace_t tr; VectorCopy ( origin, newOrg ); newOrg[2] += 1; trap_Trace( &tr, ps->origin, player->r.mins, player->r.maxs, newOrg, ps->clientNum, player->clipmask ); if ( !tr.allsolid && !tr.startsolid && tr.fraction == 1.0 ) { ps->origin[2] += 1; } } } // toggle the teleport bit so the client knows to not lerp ps->eFlags ^= EF_TELEPORT_BIT; // set angles G_Client_SetViewAngle( player, angles ); // kill anything at the destination if ( sess->sessionTeam != TEAM_SPECTATOR ) { if ( G_MoveBox (player) ) player->r.contents = CONTENTS_NONE; } // save results of pmove BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); // use the precise origin for linking VectorCopy( ps->origin, player->r.currentOrigin ); if ( sess->sessionTeam != TEAM_SPECTATOR /*&& !(ps->eFlags&EF_ELIMINATED)*/) { trap_LinkEntity (player); } } //=========================================================== /*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16) CAST_SHADOWS CLIP_MODEL FORCE_META -----DESCRIPTION----- Will just spawn and display it's model. Can be hooked up with a brushmodel-entity to move relative to. -----SPAWNFLAGS----- q3map2: 1: CAST_SHADOWS - Model will cast shadows. 2: CLIP_MODEL - Model will be clipped so noone can pass trough. 4: FORCE_META - will enforce a meta-compile for .bsp-build even if the compiler wasn't told to do so. -----KEYS----- "model" - arbitrary .md3 file to display "target" - brushmodel-entity to attach to q3map2: "_castShadows" OR "_cs" sets whether the entity casts shadows "_receiveShadows" OR "_rs" sets whether the entity receives shadows "modelscale" scaling factor for the model to include "modelscale_vec" non-uniform scaling vector for the model to include "model2" path name of second model to load "_frame" frame of model to load "_frame2" frame of second model to load */ 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 } //=========================================================== static void setCamera ( gentity_t *ent, int ownernum ) { vec3_t dir; gentity_t *target = NULL; gentity_t *owner = NULL; ent->r.ownerNum = ownernum; owner = &g_entities[ownernum]; //frame holds the rotate speed ent->s.frame = 0; //TiM: 0 // 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 ); } void cycleCamera( gentity_t *self ) { gentity_t *orgOwner = NULL; gentity_t *owner = NULL; if ( self->r.ownerNum >= 0 && self->r.ownerNum < ENTITYNUM_WORLD ) { orgOwner = &g_entities[self->r.ownerNum]; } owner = G_Find( orgOwner, FOFS(targetname), self->target ); if ( owner == NULL ) { //Uh oh! Not targeted at any ents! Or reached end of list? Which is it? //for now assume reached end of list and are cycling owner = G_Find( owner, FOFS(targetname), self->target ); } setCamera( self, owner->s.number ); if ( self->think == cycleCamera ) { if ( owner->wait > 0 ) self->nextthink = level.time + owner->wait; else if ( self->wait > 0 ) self->nextthink = level.time + self->wait; else self->nextthink = -1; //no auto cycle } } void misc_portal_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { if(!Q_stricmp(self->swapname, activator->target) && self->wait > 0) { //failsafe in case something slipped up, I'm too tired to be sure ^^ if(self->nextthink) self->nextthink = -1; else self->nextthink = level.time + self->wait; }else{ cycleCamera( self ); } } void locateCamera( gentity_t *ent ) { gentity_t *owner = NULL; owner = G_Find( NULL, FOFS(targetname), ent->target ); if(!owner){ DEVELOPER(G_Printf(S_COLOR_YELLOW "[Entity-Error] Couldn't find target for misc_partal_surface, removing surface so you'll note.\n" );); G_FreeEntity( ent ); return; } //let's see if we need cyceling of some sort. Basic requirement: Do we have another camera connected? if( G_Find( owner, FOFS(targetname), ent->target)){ //we do, so do we need to set up for manual cycle or pause? if( ent->targetname || (ent->swapname && ent->wait > 0)) ent->use = misc_portal_use; //there's one of either. Which one will be determined in usefunction. //to set up the autocycle we need wait set on either surface or camera. if(ent->wait > 0 || owner->wait > 0){ if(ent->wait == -1 && owner->wait > 0){ //we need to make sure every camera has an individual wait while((owner = G_Find( owner, FOFS(targetname), ent->target)) != NULL){ if(owner->wait == -1){ DEVELOPER(G_Printf(S_COLOR_YELLOW "[Entity-Error] One of the tragetet misc_portal_cameras does not have an individual wait. Adapting wait of the first camera found as a default.\n" );); owner = G_Find( NULL, FOFS(targetname), ent->target); ent->wait = owner->wait; //a camera failed so make sure to have the wait of the first camera ported over to the surface as failsafe break; } } } //make sure we got the right camera at this point owner = G_Find( NULL, FOFS(targetname), ent->target); ent->think = cycleCamera; if ( owner->wait > 0 ) ent->nextthink = level.time + owner->wait; else if ( ent->wait > 0 ) ent->nextthink = level.time + ent->wait; else ent->nextthink = -1; //no auto cycle } } setCamera( ent, owner->s.number ); } /*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8) -----DESCRIPTION----- 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! -----SPAWNFLAGS----- none -----KEYS----- "target" - misc_portal_camera's to target "targetname" - When used, cycles to the next misc_portal_camera it's targeted "wait" - makes it auto-cycle between all cameras it's pointed at at intevervals of specified number of seconds. Default = -1 = don't autocycle cameras will be cycled through in the order they were created on the map. if this and the first camera are -1 there will be no autocycle. if this is -1 but the first camera is positive the wait will be adapted as a faulsafe measure should one of the later cameras lack an individual wait. "swapname" - will pause/unpause the autocycle. The next cycle will happen aufer "wait" seconds, so wait is required for this. requires SELF/NO_ACRIVATOR -----USAGE----- Autocycle or manual Cycle usually only makes sence for a survaliance-station or security. For a viewscreen or a communications channel make the brush having the portal-texture a func_usable and treat is as described on that entity for VFX-Entities. */ 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->wait > 0) ent->wait *= 1000; else ent->wait = -1; if ( !ent->target ) { VectorCopy( ent->s.origin, ent->s.origin2 ); //mirror } else { ent->think = locateCamera; ent->nextthink = level.time + 500; //give cameras time to spawn if(ent->targetname && ent->swapname && ent->wait == -1){ DEVELOPER(G_Printf(S_COLOR_YELLOW "[Entity-Error] Both swapname and wait need to be present on a misc_model_surface to potentionally turn it's autocycle off. NULLing swapname.\n" );); ent->swapname = G_NewString("NULL"); //Failsafe: We'll have the usefunction, however we can not use it for pausing as we did not have wait on ent. Set swapname to NULL so we can use wait as a potential failsafe should one of the cameras lack an individual wait. } } } /*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) SLOWROTATE FASTROTATE -----DESCRIPTION----- The target for a misc_portal_surface. You can set either angles or target another entity (NOT an info_null or similar) to determine the direction of view. -----SPAWNFLAGS----- 1: SLOWROTATE - slowly rotates around it's axis of view 2: FASTROTATE - quickly rotates around it's axis of view -----KEYS----- "targetname" - have misc_portal_surface target this "roll" - an angle modifier to orient the camera around the target vector. Default is 0. "wait" - delay for autocycle misc_portal_surface. Will overwrite theirs. Default is -1 = use surface-value. */ 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; if(ent->wait > 0) ent->wait *= 1000; else ent->wait = -1; } /* ====================================================================== 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_8: fire_grenade( ent, ent->s.origin, dir ); break; case WP_10: fire_rocket( ent, ent->s.origin, dir ); break; case WP_4: fire_plasma( ent, ent->s.origin, dir ); break; case WP_9: fire_quantum( ent, ent->s.origin, dir ); break; case WP_6: fire_comprifle( ent, ent->s.origin, dir ); break; } } 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_t)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) -----DESCRIPTION----- When used fires a rocket at either the target or the current direction. -----SPAWNFLAGS----- none -----KEYS----- "target" - direction to fire to "random" - the number of degrees of deviance from the taget. (1.0 default) */ void SP_shooter_rocket( gentity_t *ent ) { InitShooter( ent, WP_10 ); } /*QUAKED shooter_plasma (1 0 0) (-16 -16 -16) (16 16 16) -----DESCRIPTION----- When used fires a plasma-burst at either the target or the current direction. -----SPAWNFLAGS----- none -----KEYS----- "target" - direction to fire to "random" - the number of degrees of deviance from the taget. (1.0 default) */ void SP_shooter_plasma( gentity_t *ent ) { InitShooter( ent, WP_6 ); //TiM : WP_4 } /*QUAKED shooter_grenade (1 0 0) (-16 -16 -16) (16 16 16) -----DESCRIPTION----- When used fires a grenade at either the target or the current direction. -----SPAWNFLAGS----- none -----KEYS----- "target" - direction to fire to "random" - the number of degrees of deviance from the taget. (1.0 default) */ void SP_shooter_grenade( gentity_t *ent ) { InitShooter( ent, WP_8); } /*QUAKED shooter_torpedo (1 0 0) (-16 -16 -16) (16 16 16) -----DESCRIPTION----- When used fires a torpedo at either the target or the current direction. -----SPAWNFLAGS----- none -----KEYS----- "target" - direction to fire to "random" - the number of degrees of deviance from the taget. (1.0 default) */ void SP_shooter_torpedo( gentity_t *ent ) { InitShooter( ent, WP_9 ); }