g_misc.c (13207B)
1 /* 2 =========================================================================== 3 Copyright (C) 1999-2005 Id Software, Inc. 4 5 This file is part of Quake III Arena source code. 6 7 Quake III Arena source code is free software; you can redistribute it 8 and/or modify it under the terms of the GNU General Public License as 9 published by the Free Software Foundation; either version 2 of the License, 10 or (at your option) any later version. 11 12 Quake III Arena source code is distributed in the hope that it will be 13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with Foobar; if not, write to the Free Software 19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 =========================================================================== 21 */ 22 // 23 // g_misc.c 24 25 #include "g_local.h" 26 27 28 /*QUAKED func_group (0 0 0) ? 29 Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities. 30 */ 31 32 33 /*QUAKED info_camp (0 0.5 0) (-4 -4 -4) (4 4 4) 34 Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay. 35 */ 36 void SP_info_camp( gentity_t *self ) { 37 G_SetOrigin( self, self->s.origin ); 38 } 39 40 41 /*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) 42 Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay. 43 */ 44 void SP_info_null( gentity_t *self ) { 45 G_FreeEntity( self ); 46 } 47 48 49 /*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) 50 Used as a positional target for in-game calculation, like jumppad targets. 51 target_position does the same thing 52 */ 53 void SP_info_notnull( gentity_t *self ){ 54 G_SetOrigin( self, self->s.origin ); 55 } 56 57 58 /*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear 59 Non-displayed light. 60 "light" overrides the default 300 intensity. 61 Linear checbox gives linear falloff instead of inverse square 62 Lights pointed at a target will be spotlights. 63 "radius" overrides the default 64 unit radius of a spotlight at the target point. 64 */ 65 void SP_light( gentity_t *self ) { 66 G_FreeEntity( self ); 67 } 68 69 70 71 /* 72 ================================================================================= 73 74 TELEPORTERS 75 76 ================================================================================= 77 */ 78 79 void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) { 80 gentity_t *tent; 81 82 // use temp events at source and destination to prevent the effect 83 // from getting dropped by a second player event 84 if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { 85 tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); 86 tent->s.clientNum = player->s.clientNum; 87 88 tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN ); 89 tent->s.clientNum = player->s.clientNum; 90 } 91 92 // unlink to make sure it can't possibly interfere with G_KillBox 93 trap_UnlinkEntity (player); 94 95 VectorCopy ( origin, player->client->ps.origin ); 96 player->client->ps.origin[2] += 1; 97 98 // spit the player out 99 AngleVectors( angles, player->client->ps.velocity, NULL, NULL ); 100 VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity ); 101 player->client->ps.pm_time = 160; // hold time 102 player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; 103 104 // toggle the teleport bit so the client knows to not lerp 105 player->client->ps.eFlags ^= EF_TELEPORT_BIT; 106 107 // set angles 108 SetClientViewAngle( player, angles ); 109 110 // kill anything at the destination 111 if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { 112 G_KillBox (player); 113 } 114 115 // save results of pmove 116 BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); 117 118 // use the precise origin for linking 119 VectorCopy( player->client->ps.origin, player->r.currentOrigin ); 120 121 if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { 122 trap_LinkEntity (player); 123 } 124 } 125 126 127 /*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) 128 Point teleporters at these. 129 Now that we don't have teleport destination pads, this is just 130 an info_notnull 131 */ 132 void SP_misc_teleporter_dest( gentity_t *ent ) { 133 } 134 135 136 //=========================================================== 137 138 /*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16) 139 "model" arbitrary .md3 file to display 140 */ 141 void SP_misc_model( gentity_t *ent ) { 142 143 #if 0 144 ent->s.modelindex = G_ModelIndex( ent->model ); 145 VectorSet (ent->mins, -16, -16, -16); 146 VectorSet (ent->maxs, 16, 16, 16); 147 trap_LinkEntity (ent); 148 149 G_SetOrigin( ent, ent->s.origin ); 150 VectorCopy( ent->s.angles, ent->s.apos.trBase ); 151 #else 152 G_FreeEntity( ent ); 153 #endif 154 } 155 156 //=========================================================== 157 158 void locateCamera( gentity_t *ent ) { 159 vec3_t dir; 160 gentity_t *target; 161 gentity_t *owner; 162 163 owner = G_PickTarget( ent->target ); 164 if ( !owner ) { 165 G_Printf( "Couldn't find target for misc_partal_surface\n" ); 166 G_FreeEntity( ent ); 167 return; 168 } 169 ent->r.ownerNum = owner->s.number; 170 171 // frame holds the rotate speed 172 if ( owner->spawnflags & 1 ) { 173 ent->s.frame = 25; 174 } else if ( owner->spawnflags & 2 ) { 175 ent->s.frame = 75; 176 } 177 178 // swing camera ? 179 if ( owner->spawnflags & 4 ) { 180 // set to 0 for no rotation at all 181 ent->s.powerups = 0; 182 } 183 else { 184 ent->s.powerups = 1; 185 } 186 187 // clientNum holds the rotate offset 188 ent->s.clientNum = owner->s.clientNum; 189 190 VectorCopy( owner->s.origin, ent->s.origin2 ); 191 192 // see if the portal_camera has a target 193 target = G_PickTarget( owner->target ); 194 if ( target ) { 195 VectorSubtract( target->s.origin, owner->s.origin, dir ); 196 VectorNormalize( dir ); 197 } else { 198 G_SetMovedir( owner->s.angles, dir ); 199 } 200 201 ent->s.eventParm = DirToByte( dir ); 202 } 203 204 /*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8) 205 The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted. 206 This must be within 64 world units of the surface! 207 */ 208 void SP_misc_portal_surface(gentity_t *ent) { 209 VectorClear( ent->r.mins ); 210 VectorClear( ent->r.maxs ); 211 trap_LinkEntity (ent); 212 213 ent->r.svFlags = SVF_PORTAL; 214 ent->s.eType = ET_PORTAL; 215 216 if ( !ent->target ) { 217 VectorCopy( ent->s.origin, ent->s.origin2 ); 218 } else { 219 ent->think = locateCamera; 220 ent->nextthink = level.time + 100; 221 } 222 } 223 224 /*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate noswing 225 The target for a misc_portal_director. You can set either angles or target another entity to determine the direction of view. 226 "roll" an angle modifier to orient the camera around the target vector; 227 */ 228 void SP_misc_portal_camera(gentity_t *ent) { 229 float roll; 230 231 VectorClear( ent->r.mins ); 232 VectorClear( ent->r.maxs ); 233 trap_LinkEntity (ent); 234 235 G_SpawnFloat( "roll", "0", &roll ); 236 237 ent->s.clientNum = roll/360.0 * 256; 238 } 239 240 /* 241 ====================================================================== 242 243 SHOOTERS 244 245 ====================================================================== 246 */ 247 248 void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator ) { 249 vec3_t dir; 250 float deg; 251 vec3_t up, right; 252 253 // see if we have a target 254 if ( ent->enemy ) { 255 VectorSubtract( ent->enemy->r.currentOrigin, ent->s.origin, dir ); 256 VectorNormalize( dir ); 257 } else { 258 VectorCopy( ent->movedir, dir ); 259 } 260 261 // randomize a bit 262 PerpendicularVector( up, dir ); 263 CrossProduct( up, dir, right ); 264 265 deg = crandom() * ent->random; 266 VectorMA( dir, deg, up, dir ); 267 268 deg = crandom() * ent->random; 269 VectorMA( dir, deg, right, dir ); 270 271 VectorNormalize( dir ); 272 273 switch ( ent->s.weapon ) { 274 case WP_GRENADE_LAUNCHER: 275 fire_grenade( ent, ent->s.origin, dir ); 276 break; 277 case WP_ROCKET_LAUNCHER: 278 fire_rocket( ent, ent->s.origin, dir ); 279 break; 280 case WP_PLASMAGUN: 281 fire_plasma( ent, ent->s.origin, dir ); 282 break; 283 } 284 285 G_AddEvent( ent, EV_FIRE_WEAPON, 0 ); 286 } 287 288 289 static void InitShooter_Finish( gentity_t *ent ) { 290 ent->enemy = G_PickTarget( ent->target ); 291 ent->think = 0; 292 ent->nextthink = 0; 293 } 294 295 void InitShooter( gentity_t *ent, int weapon ) { 296 ent->use = Use_Shooter; 297 ent->s.weapon = weapon; 298 299 RegisterItem( BG_FindItemForWeapon( weapon ) ); 300 301 G_SetMovedir( ent->s.angles, ent->movedir ); 302 303 if ( !ent->random ) { 304 ent->random = 1.0; 305 } 306 ent->random = sin( M_PI * ent->random / 180 ); 307 // target might be a moving object, so we can't set movedir for it 308 if ( ent->target ) { 309 ent->think = InitShooter_Finish; 310 ent->nextthink = level.time + 500; 311 } 312 trap_LinkEntity( ent ); 313 } 314 315 /*QUAKED shooter_rocket (1 0 0) (-16 -16 -16) (16 16 16) 316 Fires at either the target or the current direction. 317 "random" the number of degrees of deviance from the taget. (1.0 default) 318 */ 319 void SP_shooter_rocket( gentity_t *ent ) { 320 InitShooter( ent, WP_ROCKET_LAUNCHER ); 321 } 322 323 /*QUAKED shooter_plasma (1 0 0) (-16 -16 -16) (16 16 16) 324 Fires at either the target or the current direction. 325 "random" is the number of degrees of deviance from the taget. (1.0 default) 326 */ 327 void SP_shooter_plasma( gentity_t *ent ) { 328 InitShooter( ent, WP_PLASMAGUN); 329 } 330 331 /*QUAKED shooter_grenade (1 0 0) (-16 -16 -16) (16 16 16) 332 Fires at either the target or the current direction. 333 "random" is the number of degrees of deviance from the taget. (1.0 default) 334 */ 335 void SP_shooter_grenade( gentity_t *ent ) { 336 InitShooter( ent, WP_GRENADE_LAUNCHER); 337 } 338 339 340 #ifdef MISSIONPACK 341 static void PortalDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod) { 342 G_FreeEntity( self ); 343 //FIXME do something more interesting 344 } 345 346 347 void DropPortalDestination( gentity_t *player ) { 348 gentity_t *ent; 349 vec3_t snapped; 350 351 // create the portal destination 352 ent = G_Spawn(); 353 ent->s.modelindex = G_ModelIndex( "models/powerups/teleporter/tele_exit.md3" ); 354 355 VectorCopy( player->s.pos.trBase, snapped ); 356 SnapVector( snapped ); 357 G_SetOrigin( ent, snapped ); 358 VectorCopy( player->r.mins, ent->r.mins ); 359 VectorCopy( player->r.maxs, ent->r.maxs ); 360 361 ent->classname = "hi_portal destination"; 362 ent->s.pos.trType = TR_STATIONARY; 363 364 ent->r.contents = CONTENTS_CORPSE; 365 ent->takedamage = qtrue; 366 ent->health = 200; 367 ent->die = PortalDie; 368 369 VectorCopy( player->s.apos.trBase, ent->s.angles ); 370 371 ent->think = G_FreeEntity; 372 ent->nextthink = level.time + 2 * 60 * 1000; 373 374 trap_LinkEntity( ent ); 375 376 player->client->portalID = ++level.portalSequence; 377 ent->count = player->client->portalID; 378 379 // give the item back so they can drop the source now 380 player->client->ps.stats[STAT_HOLDABLE_ITEM] = BG_FindItem( "Portal" ) - bg_itemlist; 381 } 382 383 384 static void PortalTouch( gentity_t *self, gentity_t *other, trace_t *trace) { 385 gentity_t *destination; 386 387 // see if we will even let other try to use it 388 if( other->health <= 0 ) { 389 return; 390 } 391 if( !other->client ) { 392 return; 393 } 394 // if( other->client->ps.persistant[PERS_TEAM] != self->spawnflags ) { 395 // return; 396 // } 397 398 if ( other->client->ps.powerups[PW_NEUTRALFLAG] ) { // only happens in One Flag CTF 399 Drop_Item( other, BG_FindItemForPowerup( PW_NEUTRALFLAG ), 0 ); 400 other->client->ps.powerups[PW_NEUTRALFLAG] = 0; 401 } 402 else if ( other->client->ps.powerups[PW_REDFLAG] ) { // only happens in standard CTF 403 Drop_Item( other, BG_FindItemForPowerup( PW_REDFLAG ), 0 ); 404 other->client->ps.powerups[PW_REDFLAG] = 0; 405 } 406 else if ( other->client->ps.powerups[PW_BLUEFLAG] ) { // only happens in standard CTF 407 Drop_Item( other, BG_FindItemForPowerup( PW_BLUEFLAG ), 0 ); 408 other->client->ps.powerups[PW_BLUEFLAG] = 0; 409 } 410 411 // find the destination 412 destination = NULL; 413 while( (destination = G_Find(destination, FOFS(classname), "hi_portal destination")) != NULL ) { 414 if( destination->count == self->count ) { 415 break; 416 } 417 } 418 419 // if there is not one, die! 420 if( !destination ) { 421 if( self->pos1[0] || self->pos1[1] || self->pos1[2] ) { 422 TeleportPlayer( other, self->pos1, self->s.angles ); 423 } 424 G_Damage( other, other, other, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG ); 425 return; 426 } 427 428 TeleportPlayer( other, destination->s.pos.trBase, destination->s.angles ); 429 } 430 431 432 static void PortalEnable( gentity_t *self ) { 433 self->touch = PortalTouch; 434 self->think = G_FreeEntity; 435 self->nextthink = level.time + 2 * 60 * 1000; 436 } 437 438 439 void DropPortalSource( gentity_t *player ) { 440 gentity_t *ent; 441 gentity_t *destination; 442 vec3_t snapped; 443 444 // create the portal source 445 ent = G_Spawn(); 446 ent->s.modelindex = G_ModelIndex( "models/powerups/teleporter/tele_enter.md3" ); 447 448 VectorCopy( player->s.pos.trBase, snapped ); 449 SnapVector( snapped ); 450 G_SetOrigin( ent, snapped ); 451 VectorCopy( player->r.mins, ent->r.mins ); 452 VectorCopy( player->r.maxs, ent->r.maxs ); 453 454 ent->classname = "hi_portal source"; 455 ent->s.pos.trType = TR_STATIONARY; 456 457 ent->r.contents = CONTENTS_CORPSE | CONTENTS_TRIGGER; 458 ent->takedamage = qtrue; 459 ent->health = 200; 460 ent->die = PortalDie; 461 462 trap_LinkEntity( ent ); 463 464 ent->count = player->client->portalID; 465 player->client->portalID = 0; 466 467 // ent->spawnflags = player->client->ps.persistant[PERS_TEAM]; 468 469 ent->nextthink = level.time + 1000; 470 ent->think = PortalEnable; 471 472 // find the destination 473 destination = NULL; 474 while( (destination = G_Find(destination, FOFS(classname), "hi_portal destination")) != NULL ) { 475 if( destination->count == ent->count ) { 476 VectorCopy( destination->s.pos.trBase, ent->pos1 ); 477 break; 478 } 479 } 480 481 } 482 #endif