g_mover.c (40950B)
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 24 #include "g_local.h" 25 26 27 28 /* 29 =============================================================================== 30 31 PUSHMOVE 32 33 =============================================================================== 34 */ 35 36 void MatchTeam( gentity_t *teamLeader, int moverState, int time ); 37 38 typedef struct { 39 gentity_t *ent; 40 vec3_t origin; 41 vec3_t angles; 42 float deltayaw; 43 } pushed_t; 44 pushed_t pushed[MAX_GENTITIES], *pushed_p; 45 46 47 /* 48 ============ 49 G_TestEntityPosition 50 51 ============ 52 */ 53 gentity_t *G_TestEntityPosition( gentity_t *ent ) { 54 trace_t tr; 55 int mask; 56 57 if ( ent->clipmask ) { 58 mask = ent->clipmask; 59 } else { 60 mask = MASK_SOLID; 61 } 62 if ( ent->client ) { 63 trap_Trace( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, ent->client->ps.origin, ent->s.number, mask ); 64 } else { 65 trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, mask ); 66 } 67 68 if (tr.startsolid) 69 return &g_entities[ tr.entityNum ]; 70 71 return NULL; 72 } 73 74 /* 75 ================ 76 G_CreateRotationMatrix 77 ================ 78 */ 79 void G_CreateRotationMatrix(vec3_t angles, vec3_t matrix[3]) { 80 AngleVectors(angles, matrix[0], matrix[1], matrix[2]); 81 VectorInverse(matrix[1]); 82 } 83 84 /* 85 ================ 86 G_TransposeMatrix 87 ================ 88 */ 89 void G_TransposeMatrix(vec3_t matrix[3], vec3_t transpose[3]) { 90 int i, j; 91 for (i = 0; i < 3; i++) { 92 for (j = 0; j < 3; j++) { 93 transpose[i][j] = matrix[j][i]; 94 } 95 } 96 } 97 98 /* 99 ================ 100 G_RotatePoint 101 ================ 102 */ 103 void G_RotatePoint(vec3_t point, vec3_t matrix[3]) { 104 vec3_t tvec; 105 106 VectorCopy(point, tvec); 107 point[0] = DotProduct(matrix[0], tvec); 108 point[1] = DotProduct(matrix[1], tvec); 109 point[2] = DotProduct(matrix[2], tvec); 110 } 111 112 /* 113 ================== 114 G_TryPushingEntity 115 116 Returns qfalse if the move is blocked 117 ================== 118 */ 119 qboolean G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) { 120 vec3_t matrix[3], transpose[3]; 121 vec3_t org, org2, move2; 122 gentity_t *block; 123 124 // EF_MOVER_STOP will just stop when contacting another entity 125 // instead of pushing it, but entities can still ride on top of it 126 if ( ( pusher->s.eFlags & EF_MOVER_STOP ) && 127 check->s.groundEntityNum != pusher->s.number ) { 128 return qfalse; 129 } 130 131 // save off the old position 132 if (pushed_p > &pushed[MAX_GENTITIES]) { 133 G_Error( "pushed_p > &pushed[MAX_GENTITIES]" ); 134 } 135 pushed_p->ent = check; 136 VectorCopy (check->s.pos.trBase, pushed_p->origin); 137 VectorCopy (check->s.apos.trBase, pushed_p->angles); 138 if ( check->client ) { 139 pushed_p->deltayaw = check->client->ps.delta_angles[YAW]; 140 VectorCopy (check->client->ps.origin, pushed_p->origin); 141 } 142 pushed_p++; 143 144 // try moving the contacted entity 145 // figure movement due to the pusher's amove 146 G_CreateRotationMatrix( amove, transpose ); 147 G_TransposeMatrix( transpose, matrix ); 148 if ( check->client ) { 149 VectorSubtract (check->client->ps.origin, pusher->r.currentOrigin, org); 150 } 151 else { 152 VectorSubtract (check->s.pos.trBase, pusher->r.currentOrigin, org); 153 } 154 VectorCopy( org, org2 ); 155 G_RotatePoint( org2, matrix ); 156 VectorSubtract (org2, org, move2); 157 // add movement 158 VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase); 159 VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase); 160 if ( check->client ) { 161 VectorAdd (check->client->ps.origin, move, check->client->ps.origin); 162 VectorAdd (check->client->ps.origin, move2, check->client->ps.origin); 163 // make sure the client's view rotates when on a rotating mover 164 check->client->ps.delta_angles[YAW] += ANGLE2SHORT(amove[YAW]); 165 } 166 167 // may have pushed them off an edge 168 if ( check->s.groundEntityNum != pusher->s.number ) { 169 check->s.groundEntityNum = -1; 170 } 171 172 block = G_TestEntityPosition( check ); 173 if (!block) { 174 // pushed ok 175 if ( check->client ) { 176 VectorCopy( check->client->ps.origin, check->r.currentOrigin ); 177 } else { 178 VectorCopy( check->s.pos.trBase, check->r.currentOrigin ); 179 } 180 trap_LinkEntity (check); 181 return qtrue; 182 } 183 184 // if it is ok to leave in the old position, do it 185 // this is only relevent for riding entities, not pushed 186 // Sliding trapdoors can cause this. 187 VectorCopy( (pushed_p-1)->origin, check->s.pos.trBase); 188 if ( check->client ) { 189 VectorCopy( (pushed_p-1)->origin, check->client->ps.origin); 190 } 191 VectorCopy( (pushed_p-1)->angles, check->s.apos.trBase ); 192 block = G_TestEntityPosition (check); 193 if ( !block ) { 194 check->s.groundEntityNum = -1; 195 pushed_p--; 196 return qtrue; 197 } 198 199 // blocked 200 return qfalse; 201 } 202 203 /* 204 ================== 205 G_CheckProxMinePosition 206 ================== 207 */ 208 qboolean G_CheckProxMinePosition( gentity_t *check ) { 209 vec3_t start, end; 210 trace_t tr; 211 212 VectorMA(check->s.pos.trBase, 0.125, check->movedir, start); 213 VectorMA(check->s.pos.trBase, 2, check->movedir, end); 214 trap_Trace( &tr, start, NULL, NULL, end, check->s.number, MASK_SOLID ); 215 216 if (tr.startsolid || tr.fraction < 1) 217 return qfalse; 218 219 return qtrue; 220 } 221 222 /* 223 ================== 224 G_TryPushingProxMine 225 ================== 226 */ 227 qboolean G_TryPushingProxMine( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) { 228 vec3_t forward, right, up; 229 vec3_t org, org2, move2; 230 int ret; 231 232 // we need this for pushing things later 233 VectorSubtract (vec3_origin, amove, org); 234 AngleVectors (org, forward, right, up); 235 236 // try moving the contacted entity 237 VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase); 238 239 // figure movement due to the pusher's amove 240 VectorSubtract (check->s.pos.trBase, pusher->r.currentOrigin, org); 241 org2[0] = DotProduct (org, forward); 242 org2[1] = -DotProduct (org, right); 243 org2[2] = DotProduct (org, up); 244 VectorSubtract (org2, org, move2); 245 VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase); 246 247 ret = G_CheckProxMinePosition( check ); 248 if (ret) { 249 VectorCopy( check->s.pos.trBase, check->r.currentOrigin ); 250 trap_LinkEntity (check); 251 } 252 return ret; 253 } 254 255 void G_ExplodeMissile( gentity_t *ent ); 256 257 /* 258 ============ 259 G_MoverPush 260 261 Objects need to be moved back on a failed push, 262 otherwise riders would continue to slide. 263 If qfalse is returned, *obstacle will be the blocking entity 264 ============ 265 */ 266 qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **obstacle ) { 267 int i, e; 268 gentity_t *check; 269 vec3_t mins, maxs; 270 pushed_t *p; 271 int entityList[MAX_GENTITIES]; 272 int listedEntities; 273 vec3_t totalMins, totalMaxs; 274 275 *obstacle = NULL; 276 277 278 // mins/maxs are the bounds at the destination 279 // totalMins / totalMaxs are the bounds for the entire move 280 if ( pusher->r.currentAngles[0] || pusher->r.currentAngles[1] || pusher->r.currentAngles[2] 281 || amove[0] || amove[1] || amove[2] ) { 282 float radius; 283 284 radius = RadiusFromBounds( pusher->r.mins, pusher->r.maxs ); 285 for ( i = 0 ; i < 3 ; i++ ) { 286 mins[i] = pusher->r.currentOrigin[i] + move[i] - radius; 287 maxs[i] = pusher->r.currentOrigin[i] + move[i] + radius; 288 totalMins[i] = mins[i] - move[i]; 289 totalMaxs[i] = maxs[i] - move[i]; 290 } 291 } else { 292 for (i=0 ; i<3 ; i++) { 293 mins[i] = pusher->r.absmin[i] + move[i]; 294 maxs[i] = pusher->r.absmax[i] + move[i]; 295 } 296 297 VectorCopy( pusher->r.absmin, totalMins ); 298 VectorCopy( pusher->r.absmax, totalMaxs ); 299 for (i=0 ; i<3 ; i++) { 300 if ( move[i] > 0 ) { 301 totalMaxs[i] += move[i]; 302 } else { 303 totalMins[i] += move[i]; 304 } 305 } 306 } 307 308 // unlink the pusher so we don't get it in the entityList 309 trap_UnlinkEntity( pusher ); 310 311 listedEntities = trap_EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES ); 312 313 // move the pusher to it's final position 314 VectorAdd( pusher->r.currentOrigin, move, pusher->r.currentOrigin ); 315 VectorAdd( pusher->r.currentAngles, amove, pusher->r.currentAngles ); 316 trap_LinkEntity( pusher ); 317 318 // see if any solid entities are inside the final position 319 for ( e = 0 ; e < listedEntities ; e++ ) { 320 check = &g_entities[ entityList[ e ] ]; 321 322 #ifdef MISSIONPACK 323 if ( check->s.eType == ET_MISSILE ) { 324 // if it is a prox mine 325 if ( !strcmp(check->classname, "prox mine") ) { 326 // if this prox mine is attached to this mover try to move it with the pusher 327 if ( check->enemy == pusher ) { 328 if (!G_TryPushingProxMine( check, pusher, move, amove )) { 329 //explode 330 check->s.loopSound = 0; 331 G_AddEvent( check, EV_PROXIMITY_MINE_TRIGGER, 0 ); 332 G_ExplodeMissile(check); 333 if (check->activator) { 334 G_FreeEntity(check->activator); 335 check->activator = NULL; 336 } 337 //G_Printf("prox mine explodes\n"); 338 } 339 } 340 else { 341 //check if the prox mine is crushed by the mover 342 if (!G_CheckProxMinePosition( check )) { 343 //explode 344 check->s.loopSound = 0; 345 G_AddEvent( check, EV_PROXIMITY_MINE_TRIGGER, 0 ); 346 G_ExplodeMissile(check); 347 if (check->activator) { 348 G_FreeEntity(check->activator); 349 check->activator = NULL; 350 } 351 //G_Printf("prox mine explodes\n"); 352 } 353 } 354 continue; 355 } 356 } 357 #endif 358 // only push items and players 359 if ( check->s.eType != ET_ITEM && check->s.eType != ET_PLAYER && !check->physicsObject ) { 360 continue; 361 } 362 363 // if the entity is standing on the pusher, it will definitely be moved 364 if ( check->s.groundEntityNum != pusher->s.number ) { 365 // see if the ent needs to be tested 366 if ( check->r.absmin[0] >= maxs[0] 367 || check->r.absmin[1] >= maxs[1] 368 || check->r.absmin[2] >= maxs[2] 369 || check->r.absmax[0] <= mins[0] 370 || check->r.absmax[1] <= mins[1] 371 || check->r.absmax[2] <= mins[2] ) { 372 continue; 373 } 374 // see if the ent's bbox is inside the pusher's final position 375 // this does allow a fast moving object to pass through a thin entity... 376 if (!G_TestEntityPosition (check)) { 377 continue; 378 } 379 } 380 381 // the entity needs to be pushed 382 if ( G_TryPushingEntity( check, pusher, move, amove ) ) { 383 continue; 384 } 385 386 // the move was blocked an entity 387 388 // bobbing entities are instant-kill and never get blocked 389 if ( pusher->s.pos.trType == TR_SINE || pusher->s.apos.trType == TR_SINE ) { 390 G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH ); 391 continue; 392 } 393 394 395 // save off the obstacle so we can call the block function (crush, etc) 396 *obstacle = check; 397 398 // move back any entities we already moved 399 // go backwards, so if the same entity was pushed 400 // twice, it goes back to the original position 401 for ( p=pushed_p-1 ; p>=pushed ; p-- ) { 402 VectorCopy (p->origin, p->ent->s.pos.trBase); 403 VectorCopy (p->angles, p->ent->s.apos.trBase); 404 if ( p->ent->client ) { 405 p->ent->client->ps.delta_angles[YAW] = p->deltayaw; 406 VectorCopy (p->origin, p->ent->client->ps.origin); 407 } 408 trap_LinkEntity (p->ent); 409 } 410 return qfalse; 411 } 412 413 return qtrue; 414 } 415 416 417 /* 418 ================= 419 G_MoverTeam 420 ================= 421 */ 422 void G_MoverTeam( gentity_t *ent ) { 423 vec3_t move, amove; 424 gentity_t *part, *obstacle; 425 vec3_t origin, angles; 426 427 obstacle = NULL; 428 429 // make sure all team slaves can move before commiting 430 // any moves or calling any think functions 431 // if the move is blocked, all moved objects will be backed out 432 pushed_p = pushed; 433 for (part = ent ; part ; part=part->teamchain) { 434 // get current position 435 BG_EvaluateTrajectory( &part->s.pos, level.time, origin ); 436 BG_EvaluateTrajectory( &part->s.apos, level.time, angles ); 437 VectorSubtract( origin, part->r.currentOrigin, move ); 438 VectorSubtract( angles, part->r.currentAngles, amove ); 439 if ( !G_MoverPush( part, move, amove, &obstacle ) ) { 440 break; // move was blocked 441 } 442 } 443 444 if (part) { 445 // go back to the previous position 446 for ( part = ent ; part ; part = part->teamchain ) { 447 part->s.pos.trTime += level.time - level.previousTime; 448 part->s.apos.trTime += level.time - level.previousTime; 449 BG_EvaluateTrajectory( &part->s.pos, level.time, part->r.currentOrigin ); 450 BG_EvaluateTrajectory( &part->s.apos, level.time, part->r.currentAngles ); 451 trap_LinkEntity( part ); 452 } 453 454 // if the pusher has a "blocked" function, call it 455 if (ent->blocked) { 456 ent->blocked( ent, obstacle ); 457 } 458 return; 459 } 460 461 // the move succeeded 462 for ( part = ent ; part ; part = part->teamchain ) { 463 // call the reached function if time is at or past end point 464 if ( part->s.pos.trType == TR_LINEAR_STOP ) { 465 if ( level.time >= part->s.pos.trTime + part->s.pos.trDuration ) { 466 if ( part->reached ) { 467 part->reached( part ); 468 } 469 } 470 } 471 } 472 } 473 474 /* 475 ================ 476 G_RunMover 477 478 ================ 479 */ 480 void G_RunMover( gentity_t *ent ) { 481 // if not a team captain, don't do anything, because 482 // the captain will handle everything 483 if ( ent->flags & FL_TEAMSLAVE ) { 484 return; 485 } 486 487 // if stationary at one of the positions, don't move anything 488 if ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) { 489 G_MoverTeam( ent ); 490 } 491 492 // check think function 493 G_RunThink( ent ); 494 } 495 496 /* 497 ============================================================================ 498 499 GENERAL MOVERS 500 501 Doors, plats, and buttons are all binary (two position) movers 502 Pos1 is "at rest", pos2 is "activated" 503 ============================================================================ 504 */ 505 506 /* 507 =============== 508 SetMoverState 509 =============== 510 */ 511 void SetMoverState( gentity_t *ent, moverState_t moverState, int time ) { 512 vec3_t delta; 513 float f; 514 515 ent->moverState = moverState; 516 517 ent->s.pos.trTime = time; 518 switch( moverState ) { 519 case MOVER_POS1: 520 VectorCopy( ent->pos1, ent->s.pos.trBase ); 521 ent->s.pos.trType = TR_STATIONARY; 522 break; 523 case MOVER_POS2: 524 VectorCopy( ent->pos2, ent->s.pos.trBase ); 525 ent->s.pos.trType = TR_STATIONARY; 526 break; 527 case MOVER_1TO2: 528 VectorCopy( ent->pos1, ent->s.pos.trBase ); 529 VectorSubtract( ent->pos2, ent->pos1, delta ); 530 f = 1000.0 / ent->s.pos.trDuration; 531 VectorScale( delta, f, ent->s.pos.trDelta ); 532 ent->s.pos.trType = TR_LINEAR_STOP; 533 break; 534 case MOVER_2TO1: 535 VectorCopy( ent->pos2, ent->s.pos.trBase ); 536 VectorSubtract( ent->pos1, ent->pos2, delta ); 537 f = 1000.0 / ent->s.pos.trDuration; 538 VectorScale( delta, f, ent->s.pos.trDelta ); 539 ent->s.pos.trType = TR_LINEAR_STOP; 540 break; 541 } 542 BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin ); 543 trap_LinkEntity( ent ); 544 } 545 546 /* 547 ================ 548 MatchTeam 549 550 All entities in a mover team will move from pos1 to pos2 551 in the same amount of time 552 ================ 553 */ 554 void MatchTeam( gentity_t *teamLeader, int moverState, int time ) { 555 gentity_t *slave; 556 557 for ( slave = teamLeader ; slave ; slave = slave->teamchain ) { 558 SetMoverState( slave, moverState, time ); 559 } 560 } 561 562 563 564 /* 565 ================ 566 ReturnToPos1 567 ================ 568 */ 569 void ReturnToPos1( gentity_t *ent ) { 570 MatchTeam( ent, MOVER_2TO1, level.time ); 571 572 // looping sound 573 ent->s.loopSound = ent->soundLoop; 574 575 // starting sound 576 if ( ent->sound2to1 ) { 577 G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); 578 } 579 } 580 581 582 /* 583 ================ 584 Reached_BinaryMover 585 ================ 586 */ 587 void Reached_BinaryMover( gentity_t *ent ) { 588 589 // stop the looping sound 590 ent->s.loopSound = ent->soundLoop; 591 592 if ( ent->moverState == MOVER_1TO2 ) { 593 // reached pos2 594 SetMoverState( ent, MOVER_POS2, level.time ); 595 596 // play sound 597 if ( ent->soundPos2 ) { 598 G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 ); 599 } 600 601 // return to pos1 after a delay 602 ent->think = ReturnToPos1; 603 ent->nextthink = level.time + ent->wait; 604 605 // fire targets 606 if ( !ent->activator ) { 607 ent->activator = ent; 608 } 609 G_UseTargets( ent, ent->activator ); 610 } else if ( ent->moverState == MOVER_2TO1 ) { 611 // reached pos1 612 SetMoverState( ent, MOVER_POS1, level.time ); 613 614 // play sound 615 if ( ent->soundPos1 ) { 616 G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 ); 617 } 618 619 // close areaportals 620 if ( ent->teammaster == ent || !ent->teammaster ) { 621 trap_AdjustAreaPortalState( ent, qfalse ); 622 } 623 } else { 624 G_Error( "Reached_BinaryMover: bad moverState" ); 625 } 626 } 627 628 629 /* 630 ================ 631 Use_BinaryMover 632 ================ 633 */ 634 void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) { 635 int total; 636 int partial; 637 638 // only the master should be used 639 if ( ent->flags & FL_TEAMSLAVE ) { 640 Use_BinaryMover( ent->teammaster, other, activator ); 641 return; 642 } 643 644 ent->activator = activator; 645 646 if ( ent->moverState == MOVER_POS1 ) { 647 // start moving 50 msec later, becase if this was player 648 // triggered, level.time hasn't been advanced yet 649 MatchTeam( ent, MOVER_1TO2, level.time + 50 ); 650 651 // starting sound 652 if ( ent->sound1to2 ) { 653 G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); 654 } 655 656 // looping sound 657 ent->s.loopSound = ent->soundLoop; 658 659 // open areaportal 660 if ( ent->teammaster == ent || !ent->teammaster ) { 661 trap_AdjustAreaPortalState( ent, qtrue ); 662 } 663 return; 664 } 665 666 // if all the way up, just delay before coming down 667 if ( ent->moverState == MOVER_POS2 ) { 668 ent->nextthink = level.time + ent->wait; 669 return; 670 } 671 672 // only partway down before reversing 673 if ( ent->moverState == MOVER_2TO1 ) { 674 total = ent->s.pos.trDuration; 675 partial = level.time - ent->s.pos.trTime; 676 if ( partial > total ) { 677 partial = total; 678 } 679 680 MatchTeam( ent, MOVER_1TO2, level.time - ( total - partial ) ); 681 682 if ( ent->sound1to2 ) { 683 G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); 684 } 685 return; 686 } 687 688 // only partway up before reversing 689 if ( ent->moverState == MOVER_1TO2 ) { 690 total = ent->s.pos.trDuration; 691 partial = level.time - ent->s.pos.trTime; 692 if ( partial > total ) { 693 partial = total; 694 } 695 696 MatchTeam( ent, MOVER_2TO1, level.time - ( total - partial ) ); 697 698 if ( ent->sound2to1 ) { 699 G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); 700 } 701 return; 702 } 703 } 704 705 706 707 /* 708 ================ 709 InitMover 710 711 "pos1", "pos2", and "speed" should be set before calling, 712 so the movement delta can be calculated 713 ================ 714 */ 715 void InitMover( gentity_t *ent ) { 716 vec3_t move; 717 float distance; 718 float light; 719 vec3_t color; 720 qboolean lightSet, colorSet; 721 char *sound; 722 723 // if the "model2" key is set, use a seperate model 724 // for drawing, but clip against the brushes 725 if ( ent->model2 ) { 726 ent->s.modelindex2 = G_ModelIndex( ent->model2 ); 727 } 728 729 // if the "loopsound" key is set, use a constant looping sound when moving 730 if ( G_SpawnString( "noise", "100", &sound ) ) { 731 ent->s.loopSound = G_SoundIndex( sound ); 732 } 733 734 // if the "color" or "light" keys are set, setup constantLight 735 lightSet = G_SpawnFloat( "light", "100", &light ); 736 colorSet = G_SpawnVector( "color", "1 1 1", color ); 737 if ( lightSet || colorSet ) { 738 int r, g, b, i; 739 740 r = color[0] * 255; 741 if ( r > 255 ) { 742 r = 255; 743 } 744 g = color[1] * 255; 745 if ( g > 255 ) { 746 g = 255; 747 } 748 b = color[2] * 255; 749 if ( b > 255 ) { 750 b = 255; 751 } 752 i = light / 4; 753 if ( i > 255 ) { 754 i = 255; 755 } 756 ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); 757 } 758 759 760 ent->use = Use_BinaryMover; 761 ent->reached = Reached_BinaryMover; 762 763 ent->moverState = MOVER_POS1; 764 ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; 765 ent->s.eType = ET_MOVER; 766 VectorCopy (ent->pos1, ent->r.currentOrigin); 767 trap_LinkEntity (ent); 768 769 ent->s.pos.trType = TR_STATIONARY; 770 VectorCopy( ent->pos1, ent->s.pos.trBase ); 771 772 // calculate time to reach second position from speed 773 VectorSubtract( ent->pos2, ent->pos1, move ); 774 distance = VectorLength( move ); 775 if ( ! ent->speed ) { 776 ent->speed = 100; 777 } 778 VectorScale( move, ent->speed, ent->s.pos.trDelta ); 779 ent->s.pos.trDuration = distance * 1000 / ent->speed; 780 if ( ent->s.pos.trDuration <= 0 ) { 781 ent->s.pos.trDuration = 1; 782 } 783 } 784 785 786 /* 787 =============================================================================== 788 789 DOOR 790 791 A use can be triggered either by a touch function, by being shot, or by being 792 targeted by another entity. 793 794 =============================================================================== 795 */ 796 797 /* 798 ================ 799 Blocked_Door 800 ================ 801 */ 802 void Blocked_Door( gentity_t *ent, gentity_t *other ) { 803 // remove anything other than a client 804 if ( !other->client ) { 805 // except CTF flags!!!! 806 if( other->s.eType == ET_ITEM && other->item->giType == IT_TEAM ) { 807 Team_DroppedFlagThink( other ); 808 return; 809 } 810 G_TempEntity( other->s.origin, EV_ITEM_POP ); 811 G_FreeEntity( other ); 812 return; 813 } 814 815 if ( ent->damage ) { 816 G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH ); 817 } 818 if ( ent->spawnflags & 4 ) { 819 return; // crushers don't reverse 820 } 821 822 // reverse direction 823 Use_BinaryMover( ent, ent, other ); 824 } 825 826 /* 827 ================ 828 Touch_DoorTriggerSpectator 829 ================ 830 */ 831 static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_t *trace ) { 832 int i, axis; 833 vec3_t origin, dir, angles; 834 835 axis = ent->count; 836 VectorClear(dir); 837 if (fabs(other->s.origin[axis] - ent->r.absmax[axis]) < 838 fabs(other->s.origin[axis] - ent->r.absmin[axis])) { 839 origin[axis] = ent->r.absmin[axis] - 10; 840 dir[axis] = -1; 841 } 842 else { 843 origin[axis] = ent->r.absmax[axis] + 10; 844 dir[axis] = 1; 845 } 846 for (i = 0; i < 3; i++) { 847 if (i == axis) continue; 848 origin[i] = (ent->r.absmin[i] + ent->r.absmax[i]) * 0.5; 849 } 850 vectoangles(dir, angles); 851 TeleportPlayer(other, origin, angles ); 852 } 853 854 /* 855 ================ 856 Touch_DoorTrigger 857 ================ 858 */ 859 void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ) { 860 if ( other->client && other->client->sess.sessionTeam == TEAM_SPECTATOR ) { 861 // if the door is not open and not opening 862 if ( ent->parent->moverState != MOVER_1TO2 && 863 ent->parent->moverState != MOVER_POS2) { 864 Touch_DoorTriggerSpectator( ent, other, trace ); 865 } 866 } 867 else if ( ent->parent->moverState != MOVER_1TO2 ) { 868 Use_BinaryMover( ent->parent, ent, other ); 869 } 870 } 871 872 873 /* 874 ====================== 875 Think_SpawnNewDoorTrigger 876 877 All of the parts of a door have been spawned, so create 878 a trigger that encloses all of them 879 ====================== 880 */ 881 void Think_SpawnNewDoorTrigger( gentity_t *ent ) { 882 gentity_t *other; 883 vec3_t mins, maxs; 884 int i, best; 885 886 // set all of the slaves as shootable 887 for ( other = ent ; other ; other = other->teamchain ) { 888 other->takedamage = qtrue; 889 } 890 891 // find the bounds of everything on the team 892 VectorCopy (ent->r.absmin, mins); 893 VectorCopy (ent->r.absmax, maxs); 894 895 for (other = ent->teamchain ; other ; other=other->teamchain) { 896 AddPointToBounds (other->r.absmin, mins, maxs); 897 AddPointToBounds (other->r.absmax, mins, maxs); 898 } 899 900 // find the thinnest axis, which will be the one we expand 901 best = 0; 902 for ( i = 1 ; i < 3 ; i++ ) { 903 if ( maxs[i] - mins[i] < maxs[best] - mins[best] ) { 904 best = i; 905 } 906 } 907 maxs[best] += 120; 908 mins[best] -= 120; 909 910 // create a trigger with this size 911 other = G_Spawn (); 912 other->classname = "door_trigger"; 913 VectorCopy (mins, other->r.mins); 914 VectorCopy (maxs, other->r.maxs); 915 other->parent = ent; 916 other->r.contents = CONTENTS_TRIGGER; 917 other->touch = Touch_DoorTrigger; 918 // remember the thinnest axis 919 other->count = best; 920 trap_LinkEntity (other); 921 922 MatchTeam( ent, ent->moverState, level.time ); 923 } 924 925 void Think_MatchTeam( gentity_t *ent ) { 926 MatchTeam( ent, ent->moverState, level.time ); 927 } 928 929 930 /*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER 931 TOGGLE wait in both the start and end states for a trigger event. 932 START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). 933 NOMONSTER monsters will not trigger this door 934 935 "model2" .md3 model to also draw 936 "angle" determines the opening direction 937 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. 938 "speed" movement speed (100 default) 939 "wait" wait before returning (3 default, -1 = never return) 940 "lip" lip remaining at end of move (8 default) 941 "dmg" damage to inflict when blocked (2 default) 942 "color" constantLight color 943 "light" constantLight radius 944 "health" if set, the door must be shot open 945 */ 946 void SP_func_door (gentity_t *ent) { 947 vec3_t abs_movedir; 948 float distance; 949 vec3_t size; 950 float lip; 951 952 ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/doors/dr1_strt.wav"); 953 ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/doors/dr1_end.wav"); 954 955 ent->blocked = Blocked_Door; 956 957 // default speed of 400 958 if (!ent->speed) 959 ent->speed = 400; 960 961 // default wait of 2 seconds 962 if (!ent->wait) 963 ent->wait = 2; 964 ent->wait *= 1000; 965 966 // default lip of 8 units 967 G_SpawnFloat( "lip", "8", &lip ); 968 969 // default damage of 2 points 970 G_SpawnInt( "dmg", "2", &ent->damage ); 971 972 // first position at start 973 VectorCopy( ent->s.origin, ent->pos1 ); 974 975 // calculate second position 976 trap_SetBrushModel( ent, ent->model ); 977 G_SetMovedir (ent->s.angles, ent->movedir); 978 abs_movedir[0] = fabs(ent->movedir[0]); 979 abs_movedir[1] = fabs(ent->movedir[1]); 980 abs_movedir[2] = fabs(ent->movedir[2]); 981 VectorSubtract( ent->r.maxs, ent->r.mins, size ); 982 distance = DotProduct( abs_movedir, size ) - lip; 983 VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 ); 984 985 // if "start_open", reverse position 1 and 2 986 if ( ent->spawnflags & 1 ) { 987 vec3_t temp; 988 989 VectorCopy( ent->pos2, temp ); 990 VectorCopy( ent->s.origin, ent->pos2 ); 991 VectorCopy( temp, ent->pos1 ); 992 } 993 994 InitMover( ent ); 995 996 ent->nextthink = level.time + FRAMETIME; 997 998 if ( ! (ent->flags & FL_TEAMSLAVE ) ) { 999 int health; 1000 1001 G_SpawnInt( "health", "0", &health ); 1002 if ( health ) { 1003 ent->takedamage = qtrue; 1004 } 1005 if ( ent->targetname || health ) { 1006 // non touch/shoot doors 1007 ent->think = Think_MatchTeam; 1008 } else { 1009 ent->think = Think_SpawnNewDoorTrigger; 1010 } 1011 } 1012 1013 1014 } 1015 1016 /* 1017 =============================================================================== 1018 1019 PLAT 1020 1021 =============================================================================== 1022 */ 1023 1024 /* 1025 ============== 1026 Touch_Plat 1027 1028 Don't allow decent if a living player is on it 1029 =============== 1030 */ 1031 void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) { 1032 if ( !other->client || other->client->ps.stats[STAT_HEALTH] <= 0 ) { 1033 return; 1034 } 1035 1036 // delay return-to-pos1 by one second 1037 if ( ent->moverState == MOVER_POS2 ) { 1038 ent->nextthink = level.time + 1000; 1039 } 1040 } 1041 1042 /* 1043 ============== 1044 Touch_PlatCenterTrigger 1045 1046 If the plat is at the bottom position, start it going up 1047 =============== 1048 */ 1049 void Touch_PlatCenterTrigger(gentity_t *ent, gentity_t *other, trace_t *trace ) { 1050 if ( !other->client ) { 1051 return; 1052 } 1053 1054 if ( ent->parent->moverState == MOVER_POS1 ) { 1055 Use_BinaryMover( ent->parent, ent, other ); 1056 } 1057 } 1058 1059 1060 /* 1061 ================ 1062 SpawnPlatTrigger 1063 1064 Spawn a trigger in the middle of the plat's low position 1065 Elevator cars require that the trigger extend through the entire low position, 1066 not just sit on top of it. 1067 ================ 1068 */ 1069 void SpawnPlatTrigger( gentity_t *ent ) { 1070 gentity_t *trigger; 1071 vec3_t tmin, tmax; 1072 1073 // the middle trigger will be a thin trigger just 1074 // above the starting position 1075 trigger = G_Spawn(); 1076 trigger->classname = "plat_trigger"; 1077 trigger->touch = Touch_PlatCenterTrigger; 1078 trigger->r.contents = CONTENTS_TRIGGER; 1079 trigger->parent = ent; 1080 1081 tmin[0] = ent->pos1[0] + ent->r.mins[0] + 33; 1082 tmin[1] = ent->pos1[1] + ent->r.mins[1] + 33; 1083 tmin[2] = ent->pos1[2] + ent->r.mins[2]; 1084 1085 tmax[0] = ent->pos1[0] + ent->r.maxs[0] - 33; 1086 tmax[1] = ent->pos1[1] + ent->r.maxs[1] - 33; 1087 tmax[2] = ent->pos1[2] + ent->r.maxs[2] + 8; 1088 1089 if ( tmax[0] <= tmin[0] ) { 1090 tmin[0] = ent->pos1[0] + (ent->r.mins[0] + ent->r.maxs[0]) *0.5; 1091 tmax[0] = tmin[0] + 1; 1092 } 1093 if ( tmax[1] <= tmin[1] ) { 1094 tmin[1] = ent->pos1[1] + (ent->r.mins[1] + ent->r.maxs[1]) *0.5; 1095 tmax[1] = tmin[1] + 1; 1096 } 1097 1098 VectorCopy (tmin, trigger->r.mins); 1099 VectorCopy (tmax, trigger->r.maxs); 1100 1101 trap_LinkEntity (trigger); 1102 } 1103 1104 1105 /*QUAKED func_plat (0 .5 .8) ? 1106 Plats are always drawn in the extended position so they will light correctly. 1107 1108 "lip" default 8, protrusion above rest position 1109 "height" total height of movement, defaults to model height 1110 "speed" overrides default 200. 1111 "dmg" overrides default 2 1112 "model2" .md3 model to also draw 1113 "color" constantLight color 1114 "light" constantLight radius 1115 */ 1116 void SP_func_plat (gentity_t *ent) { 1117 float lip, height; 1118 1119 ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/plats/pt1_strt.wav"); 1120 ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/plats/pt1_end.wav"); 1121 1122 VectorClear (ent->s.angles); 1123 1124 G_SpawnFloat( "speed", "200", &ent->speed ); 1125 G_SpawnInt( "dmg", "2", &ent->damage ); 1126 G_SpawnFloat( "wait", "1", &ent->wait ); 1127 G_SpawnFloat( "lip", "8", &lip ); 1128 1129 ent->wait = 1000; 1130 1131 // create second position 1132 trap_SetBrushModel( ent, ent->model ); 1133 1134 if ( !G_SpawnFloat( "height", "0", &height ) ) { 1135 height = (ent->r.maxs[2] - ent->r.mins[2]) - lip; 1136 } 1137 1138 // pos1 is the rest (bottom) position, pos2 is the top 1139 VectorCopy( ent->s.origin, ent->pos2 ); 1140 VectorCopy( ent->pos2, ent->pos1 ); 1141 ent->pos1[2] -= height; 1142 1143 InitMover( ent ); 1144 1145 // touch function keeps the plat from returning while 1146 // a live player is standing on it 1147 ent->touch = Touch_Plat; 1148 1149 ent->blocked = Blocked_Door; 1150 1151 ent->parent = ent; // so it can be treated as a door 1152 1153 // spawn the trigger if one hasn't been custom made 1154 if ( !ent->targetname ) { 1155 SpawnPlatTrigger(ent); 1156 } 1157 } 1158 1159 1160 /* 1161 =============================================================================== 1162 1163 BUTTON 1164 1165 =============================================================================== 1166 */ 1167 1168 /* 1169 ============== 1170 Touch_Button 1171 1172 =============== 1173 */ 1174 void Touch_Button(gentity_t *ent, gentity_t *other, trace_t *trace ) { 1175 if ( !other->client ) { 1176 return; 1177 } 1178 1179 if ( ent->moverState == MOVER_POS1 ) { 1180 Use_BinaryMover( ent, other, other ); 1181 } 1182 } 1183 1184 1185 /*QUAKED func_button (0 .5 .8) ? 1186 When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. 1187 1188 "model2" .md3 model to also draw 1189 "angle" determines the opening direction 1190 "target" all entities with a matching targetname will be used 1191 "speed" override the default 40 speed 1192 "wait" override the default 1 second wait (-1 = never return) 1193 "lip" override the default 4 pixel lip remaining at end of move 1194 "health" if set, the button must be killed instead of touched 1195 "color" constantLight color 1196 "light" constantLight radius 1197 */ 1198 void SP_func_button( gentity_t *ent ) { 1199 vec3_t abs_movedir; 1200 float distance; 1201 vec3_t size; 1202 float lip; 1203 1204 ent->sound1to2 = G_SoundIndex("sound/movers/switches/butn2.wav"); 1205 1206 if ( !ent->speed ) { 1207 ent->speed = 40; 1208 } 1209 1210 if ( !ent->wait ) { 1211 ent->wait = 1; 1212 } 1213 ent->wait *= 1000; 1214 1215 // first position 1216 VectorCopy( ent->s.origin, ent->pos1 ); 1217 1218 // calculate second position 1219 trap_SetBrushModel( ent, ent->model ); 1220 1221 G_SpawnFloat( "lip", "4", &lip ); 1222 1223 G_SetMovedir( ent->s.angles, ent->movedir ); 1224 abs_movedir[0] = fabs(ent->movedir[0]); 1225 abs_movedir[1] = fabs(ent->movedir[1]); 1226 abs_movedir[2] = fabs(ent->movedir[2]); 1227 VectorSubtract( ent->r.maxs, ent->r.mins, size ); 1228 distance = abs_movedir[0] * size[0] + abs_movedir[1] * size[1] + abs_movedir[2] * size[2] - lip; 1229 VectorMA (ent->pos1, distance, ent->movedir, ent->pos2); 1230 1231 if (ent->health) { 1232 // shootable button 1233 ent->takedamage = qtrue; 1234 } else { 1235 // touchable button 1236 ent->touch = Touch_Button; 1237 } 1238 1239 InitMover( ent ); 1240 } 1241 1242 1243 1244 /* 1245 =============================================================================== 1246 1247 TRAIN 1248 1249 =============================================================================== 1250 */ 1251 1252 1253 #define TRAIN_START_ON 1 1254 #define TRAIN_TOGGLE 2 1255 #define TRAIN_BLOCK_STOPS 4 1256 1257 /* 1258 =============== 1259 Think_BeginMoving 1260 1261 The wait time at a corner has completed, so start moving again 1262 =============== 1263 */ 1264 void Think_BeginMoving( gentity_t *ent ) { 1265 ent->s.pos.trTime = level.time; 1266 ent->s.pos.trType = TR_LINEAR_STOP; 1267 } 1268 1269 /* 1270 =============== 1271 Reached_Train 1272 =============== 1273 */ 1274 void Reached_Train( gentity_t *ent ) { 1275 gentity_t *next; 1276 float speed; 1277 vec3_t move; 1278 float length; 1279 1280 // copy the apropriate values 1281 next = ent->nextTrain; 1282 if ( !next || !next->nextTrain ) { 1283 return; // just stop 1284 } 1285 1286 // fire all other targets 1287 G_UseTargets( next, NULL ); 1288 1289 // set the new trajectory 1290 ent->nextTrain = next->nextTrain; 1291 VectorCopy( next->s.origin, ent->pos1 ); 1292 VectorCopy( next->nextTrain->s.origin, ent->pos2 ); 1293 1294 // if the path_corner has a speed, use that 1295 if ( next->speed ) { 1296 speed = next->speed; 1297 } else { 1298 // otherwise use the train's speed 1299 speed = ent->speed; 1300 } 1301 if ( speed < 1 ) { 1302 speed = 1; 1303 } 1304 1305 // calculate duration 1306 VectorSubtract( ent->pos2, ent->pos1, move ); 1307 length = VectorLength( move ); 1308 1309 ent->s.pos.trDuration = length * 1000 / speed; 1310 1311 // looping sound 1312 ent->s.loopSound = next->soundLoop; 1313 1314 // start it going 1315 SetMoverState( ent, MOVER_1TO2, level.time ); 1316 1317 // if there is a "wait" value on the target, don't start moving yet 1318 if ( next->wait ) { 1319 ent->nextthink = level.time + next->wait * 1000; 1320 ent->think = Think_BeginMoving; 1321 ent->s.pos.trType = TR_STATIONARY; 1322 } 1323 } 1324 1325 1326 /* 1327 =============== 1328 Think_SetupTrainTargets 1329 1330 Link all the corners together 1331 =============== 1332 */ 1333 void Think_SetupTrainTargets( gentity_t *ent ) { 1334 gentity_t *path, *next, *start; 1335 1336 ent->nextTrain = G_Find( NULL, FOFS(targetname), ent->target ); 1337 if ( !ent->nextTrain ) { 1338 G_Printf( "func_train at %s with an unfound target\n", 1339 vtos(ent->r.absmin) ); 1340 return; 1341 } 1342 1343 start = NULL; 1344 for ( path = ent->nextTrain ; path != start ; path = next ) { 1345 if ( !start ) { 1346 start = path; 1347 } 1348 1349 if ( !path->target ) { 1350 G_Printf( "Train corner at %s without a target\n", 1351 vtos(path->s.origin) ); 1352 return; 1353 } 1354 1355 // find a path_corner among the targets 1356 // there may also be other targets that get fired when the corner 1357 // is reached 1358 next = NULL; 1359 do { 1360 next = G_Find( next, FOFS(targetname), path->target ); 1361 if ( !next ) { 1362 G_Printf( "Train corner at %s without a target path_corner\n", 1363 vtos(path->s.origin) ); 1364 return; 1365 } 1366 } while ( strcmp( next->classname, "path_corner" ) ); 1367 1368 path->nextTrain = next; 1369 } 1370 1371 // start the train moving from the first corner 1372 Reached_Train( ent ); 1373 } 1374 1375 1376 1377 /*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) 1378 Train path corners. 1379 Target: next path corner and other targets to fire 1380 "speed" speed to move to the next corner 1381 "wait" seconds to wait before behining move to next corner 1382 */ 1383 void SP_path_corner( gentity_t *self ) { 1384 if ( !self->targetname ) { 1385 G_Printf ("path_corner with no targetname at %s\n", vtos(self->s.origin)); 1386 G_FreeEntity( self ); 1387 return; 1388 } 1389 // path corners don't need to be linked in 1390 } 1391 1392 1393 1394 /*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS 1395 A train is a mover that moves between path_corner target points. 1396 Trains MUST HAVE AN ORIGIN BRUSH. 1397 The train spawns at the first target it is pointing at. 1398 "model2" .md3 model to also draw 1399 "speed" default 100 1400 "dmg" default 2 1401 "noise" looping sound to play when the train is in motion 1402 "target" next path corner 1403 "color" constantLight color 1404 "light" constantLight radius 1405 */ 1406 void SP_func_train (gentity_t *self) { 1407 VectorClear (self->s.angles); 1408 1409 if (self->spawnflags & TRAIN_BLOCK_STOPS) { 1410 self->damage = 0; 1411 } else { 1412 if (!self->damage) { 1413 self->damage = 2; 1414 } 1415 } 1416 1417 if ( !self->speed ) { 1418 self->speed = 100; 1419 } 1420 1421 if ( !self->target ) { 1422 G_Printf ("func_train without a target at %s\n", vtos(self->r.absmin)); 1423 G_FreeEntity( self ); 1424 return; 1425 } 1426 1427 trap_SetBrushModel( self, self->model ); 1428 InitMover( self ); 1429 1430 self->reached = Reached_Train; 1431 1432 // start trains on the second frame, to make sure their targets have had 1433 // a chance to spawn 1434 self->nextthink = level.time + FRAMETIME; 1435 self->think = Think_SetupTrainTargets; 1436 } 1437 1438 /* 1439 =============================================================================== 1440 1441 STATIC 1442 1443 =============================================================================== 1444 */ 1445 1446 1447 /*QUAKED func_static (0 .5 .8) ? 1448 A bmodel that just sits there, doing nothing. Can be used for conditional walls and models. 1449 "model2" .md3 model to also draw 1450 "color" constantLight color 1451 "light" constantLight radius 1452 */ 1453 void SP_func_static( gentity_t *ent ) { 1454 trap_SetBrushModel( ent, ent->model ); 1455 InitMover( ent ); 1456 VectorCopy( ent->s.origin, ent->s.pos.trBase ); 1457 VectorCopy( ent->s.origin, ent->r.currentOrigin ); 1458 } 1459 1460 1461 /* 1462 =============================================================================== 1463 1464 ROTATING 1465 1466 =============================================================================== 1467 */ 1468 1469 1470 /*QUAKED func_rotating (0 .5 .8) ? START_ON - X_AXIS Y_AXIS 1471 You need to have an origin brush as part of this entity. The center of that brush will be 1472 the point around which it is rotated. It will rotate around the Z axis by default. You can 1473 check either the X_AXIS or Y_AXIS box to change that. 1474 1475 "model2" .md3 model to also draw 1476 "speed" determines how fast it moves; default value is 100. 1477 "dmg" damage to inflict when blocked (2 default) 1478 "color" constantLight color 1479 "light" constantLight radius 1480 */ 1481 void SP_func_rotating (gentity_t *ent) { 1482 if ( !ent->speed ) { 1483 ent->speed = 100; 1484 } 1485 1486 // set the axis of rotation 1487 ent->s.apos.trType = TR_LINEAR; 1488 if ( ent->spawnflags & 4 ) { 1489 ent->s.apos.trDelta[2] = ent->speed; 1490 } else if ( ent->spawnflags & 8 ) { 1491 ent->s.apos.trDelta[0] = ent->speed; 1492 } else { 1493 ent->s.apos.trDelta[1] = ent->speed; 1494 } 1495 1496 if (!ent->damage) { 1497 ent->damage = 2; 1498 } 1499 1500 trap_SetBrushModel( ent, ent->model ); 1501 InitMover( ent ); 1502 1503 VectorCopy( ent->s.origin, ent->s.pos.trBase ); 1504 VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); 1505 VectorCopy( ent->s.apos.trBase, ent->r.currentAngles ); 1506 1507 trap_LinkEntity( ent ); 1508 } 1509 1510 1511 /* 1512 =============================================================================== 1513 1514 BOBBING 1515 1516 =============================================================================== 1517 */ 1518 1519 1520 /*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS 1521 Normally bobs on the Z axis 1522 "model2" .md3 model to also draw 1523 "height" amplitude of bob (32 default) 1524 "speed" seconds to complete a bob cycle (4 default) 1525 "phase" the 0.0 to 1.0 offset in the cycle to start at 1526 "dmg" damage to inflict when blocked (2 default) 1527 "color" constantLight color 1528 "light" constantLight radius 1529 */ 1530 void SP_func_bobbing (gentity_t *ent) { 1531 float height; 1532 float phase; 1533 1534 G_SpawnFloat( "speed", "4", &ent->speed ); 1535 G_SpawnFloat( "height", "32", &height ); 1536 G_SpawnInt( "dmg", "2", &ent->damage ); 1537 G_SpawnFloat( "phase", "0", &phase ); 1538 1539 trap_SetBrushModel( ent, ent->model ); 1540 InitMover( ent ); 1541 1542 VectorCopy( ent->s.origin, ent->s.pos.trBase ); 1543 VectorCopy( ent->s.origin, ent->r.currentOrigin ); 1544 1545 ent->s.pos.trDuration = ent->speed * 1000; 1546 ent->s.pos.trTime = ent->s.pos.trDuration * phase; 1547 ent->s.pos.trType = TR_SINE; 1548 1549 // set the axis of bobbing 1550 if ( ent->spawnflags & 1 ) { 1551 ent->s.pos.trDelta[0] = height; 1552 } else if ( ent->spawnflags & 2 ) { 1553 ent->s.pos.trDelta[1] = height; 1554 } else { 1555 ent->s.pos.trDelta[2] = height; 1556 } 1557 } 1558 1559 /* 1560 =============================================================================== 1561 1562 PENDULUM 1563 1564 =============================================================================== 1565 */ 1566 1567 1568 /*QUAKED func_pendulum (0 .5 .8) ? 1569 You need to have an origin brush as part of this entity. 1570 Pendulums always swing north / south on unrotated models. Add an angles field to the model to allow rotation in other directions. 1571 Pendulum frequency is a physical constant based on the length of the beam and gravity. 1572 "model2" .md3 model to also draw 1573 "speed" the number of degrees each way the pendulum swings, (30 default) 1574 "phase" the 0.0 to 1.0 offset in the cycle to start at 1575 "dmg" damage to inflict when blocked (2 default) 1576 "color" constantLight color 1577 "light" constantLight radius 1578 */ 1579 void SP_func_pendulum(gentity_t *ent) { 1580 float freq; 1581 float length; 1582 float phase; 1583 float speed; 1584 1585 G_SpawnFloat( "speed", "30", &speed ); 1586 G_SpawnInt( "dmg", "2", &ent->damage ); 1587 G_SpawnFloat( "phase", "0", &phase ); 1588 1589 trap_SetBrushModel( ent, ent->model ); 1590 1591 // find pendulum length 1592 length = fabs( ent->r.mins[2] ); 1593 if ( length < 8 ) { 1594 length = 8; 1595 } 1596 1597 freq = 1 / ( M_PI * 2 ) * sqrt( g_gravity.value / ( 3 * length ) ); 1598 1599 ent->s.pos.trDuration = ( 1000 / freq ); 1600 1601 InitMover( ent ); 1602 1603 VectorCopy( ent->s.origin, ent->s.pos.trBase ); 1604 VectorCopy( ent->s.origin, ent->r.currentOrigin ); 1605 1606 VectorCopy( ent->s.angles, ent->s.apos.trBase ); 1607 1608 ent->s.apos.trDuration = 1000 / freq; 1609 ent->s.apos.trTime = ent->s.apos.trDuration * phase; 1610 ent->s.apos.trType = TR_SINE; 1611 ent->s.apos.trDelta[2] = speed; 1612 }