g_func.c (51174B)
1 /* 2 Copyright (C) 1997-2001 Id Software, Inc. 3 4 This program is free software; you can redistribute it and/or 5 modify it under the terms of the GNU General Public License 6 as published by the Free Software Foundation; either version 2 7 of the License, or (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 13 See the GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software 17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 19 */ 20 #include "g_local.h" 21 22 /* 23 ========================================================= 24 25 PLATS 26 27 movement options: 28 29 linear 30 smooth start, hard stop 31 smooth start, smooth stop 32 33 start 34 end 35 acceleration 36 speed 37 deceleration 38 begin sound 39 end sound 40 target fired when reaching end 41 wait at end 42 43 object characteristics that use move segments 44 --------------------------------------------- 45 movetype_push, or movetype_stop 46 action when touched 47 action when blocked 48 action when used 49 disabled? 50 auto trigger spawning 51 52 53 ========================================================= 54 */ 55 56 #define PLAT_LOW_TRIGGER 1 57 58 #define STATE_TOP 0 59 #define STATE_BOTTOM 1 60 #define STATE_UP 2 61 #define STATE_DOWN 3 62 63 #define DOOR_START_OPEN 1 64 #define DOOR_REVERSE 2 65 #define DOOR_CRUSHER 4 66 #define DOOR_NOMONSTER 8 67 #define DOOR_TOGGLE 32 68 #define DOOR_X_AXIS 64 69 #define DOOR_Y_AXIS 128 70 71 72 // 73 // Support routines for movement (changes in origin using velocity) 74 // 75 76 void Move_Done (edict_t *ent) 77 { 78 VectorClear (ent->velocity); 79 ent->moveinfo.endfunc (ent); 80 } 81 82 void Move_Final (edict_t *ent) 83 { 84 if (ent->moveinfo.remaining_distance == 0) 85 { 86 Move_Done (ent); 87 return; 88 } 89 90 VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity); 91 92 ent->think = Move_Done; 93 ent->nextthink = level.time + FRAMETIME; 94 } 95 96 void Move_Begin (edict_t *ent) 97 { 98 float frames; 99 100 if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance) 101 { 102 Move_Final (ent); 103 return; 104 } 105 VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity); 106 frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME); 107 ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME; 108 ent->nextthink = level.time + (frames * FRAMETIME); 109 ent->think = Move_Final; 110 } 111 112 void Think_AccelMove (edict_t *ent); 113 114 void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*)) 115 { 116 VectorClear (ent->velocity); 117 VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir); 118 ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir); 119 ent->moveinfo.endfunc = func; 120 121 if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel) 122 { 123 if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) 124 { 125 Move_Begin (ent); 126 } 127 else 128 { 129 ent->nextthink = level.time + FRAMETIME; 130 ent->think = Move_Begin; 131 } 132 } 133 else 134 { 135 // accelerative 136 ent->moveinfo.current_speed = 0; 137 ent->think = Think_AccelMove; 138 ent->nextthink = level.time + FRAMETIME; 139 } 140 } 141 142 143 // 144 // Support routines for angular movement (changes in angle using avelocity) 145 // 146 147 void AngleMove_Done (edict_t *ent) 148 { 149 VectorClear (ent->avelocity); 150 ent->moveinfo.endfunc (ent); 151 } 152 153 void AngleMove_Final (edict_t *ent) 154 { 155 vec3_t move; 156 157 if (ent->moveinfo.state == STATE_UP) 158 VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move); 159 else 160 VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move); 161 162 if (VectorCompare (move, vec3_origin)) 163 { 164 AngleMove_Done (ent); 165 return; 166 } 167 168 VectorScale (move, 1.0/FRAMETIME, ent->avelocity); 169 170 ent->think = AngleMove_Done; 171 ent->nextthink = level.time + FRAMETIME; 172 } 173 174 void AngleMove_Begin (edict_t *ent) 175 { 176 vec3_t destdelta; 177 float len; 178 float traveltime; 179 float frames; 180 181 // set destdelta to the vector needed to move 182 if (ent->moveinfo.state == STATE_UP) 183 VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta); 184 else 185 VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta); 186 187 // calculate length of vector 188 len = VectorLength (destdelta); 189 190 // divide by speed to get time to reach dest 191 traveltime = len / ent->moveinfo.speed; 192 193 if (traveltime < FRAMETIME) 194 { 195 AngleMove_Final (ent); 196 return; 197 } 198 199 frames = floor(traveltime / FRAMETIME); 200 201 // scale the destdelta vector by the time spent traveling to get velocity 202 VectorScale (destdelta, 1.0 / traveltime, ent->avelocity); 203 204 // set nextthink to trigger a think when dest is reached 205 ent->nextthink = level.time + frames * FRAMETIME; 206 ent->think = AngleMove_Final; 207 } 208 209 void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*)) 210 { 211 VectorClear (ent->avelocity); 212 ent->moveinfo.endfunc = func; 213 if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) 214 { 215 AngleMove_Begin (ent); 216 } 217 else 218 { 219 ent->nextthink = level.time + FRAMETIME; 220 ent->think = AngleMove_Begin; 221 } 222 } 223 224 225 /* 226 ============== 227 Think_AccelMove 228 229 The team has completed a frame of movement, so 230 change the speed for the next frame 231 ============== 232 */ 233 #define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) 234 235 void plat_CalcAcceleratedMove(moveinfo_t *moveinfo) 236 { 237 float accel_dist; 238 float decel_dist; 239 240 moveinfo->move_speed = moveinfo->speed; 241 242 if (moveinfo->remaining_distance < moveinfo->accel) 243 { 244 moveinfo->current_speed = moveinfo->remaining_distance; 245 return; 246 } 247 248 accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel); 249 decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel); 250 251 if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0) 252 { 253 float f; 254 255 f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel); 256 moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f); 257 decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel); 258 } 259 260 moveinfo->decel_distance = decel_dist; 261 }; 262 263 void plat_Accelerate (moveinfo_t *moveinfo) 264 { 265 // are we decelerating? 266 if (moveinfo->remaining_distance <= moveinfo->decel_distance) 267 { 268 if (moveinfo->remaining_distance < moveinfo->decel_distance) 269 { 270 if (moveinfo->next_speed) 271 { 272 moveinfo->current_speed = moveinfo->next_speed; 273 moveinfo->next_speed = 0; 274 return; 275 } 276 if (moveinfo->current_speed > moveinfo->decel) 277 moveinfo->current_speed -= moveinfo->decel; 278 } 279 return; 280 } 281 282 // are we at full speed and need to start decelerating during this move? 283 if (moveinfo->current_speed == moveinfo->move_speed) 284 if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance) 285 { 286 float p1_distance; 287 float p2_distance; 288 float distance; 289 290 p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; 291 p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed)); 292 distance = p1_distance + p2_distance; 293 moveinfo->current_speed = moveinfo->move_speed; 294 moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); 295 return; 296 } 297 298 // are we accelerating? 299 if (moveinfo->current_speed < moveinfo->speed) 300 { 301 float old_speed; 302 float p1_distance; 303 float p1_speed; 304 float p2_distance; 305 float distance; 306 307 old_speed = moveinfo->current_speed; 308 309 // figure simple acceleration up to move_speed 310 moveinfo->current_speed += moveinfo->accel; 311 if (moveinfo->current_speed > moveinfo->speed) 312 moveinfo->current_speed = moveinfo->speed; 313 314 // are we accelerating throughout this entire move? 315 if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance) 316 return; 317 318 // during this move we will accelrate from current_speed to move_speed 319 // and cross over the decel_distance; figure the average speed for the 320 // entire move 321 p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; 322 p1_speed = (old_speed + moveinfo->move_speed) / 2.0; 323 p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed)); 324 distance = p1_distance + p2_distance; 325 moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance)); 326 moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); 327 return; 328 } 329 330 // we are at constant velocity (move_speed) 331 return; 332 }; 333 334 void Think_AccelMove (edict_t *ent) 335 { 336 ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed; 337 338 if (ent->moveinfo.current_speed == 0) // starting or blocked 339 plat_CalcAcceleratedMove(&ent->moveinfo); 340 341 plat_Accelerate (&ent->moveinfo); 342 343 // will the entire move complete on next frame? 344 if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed) 345 { 346 Move_Final (ent); 347 return; 348 } 349 350 VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity); 351 ent->nextthink = level.time + FRAMETIME; 352 ent->think = Think_AccelMove; 353 } 354 355 356 void plat_go_down (edict_t *ent); 357 358 void plat_hit_top (edict_t *ent) 359 { 360 if (!(ent->flags & FL_TEAMSLAVE)) 361 { 362 if (ent->moveinfo.sound_end) 363 gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); 364 ent->s.sound = 0; 365 } 366 ent->moveinfo.state = STATE_TOP; 367 368 ent->think = plat_go_down; 369 ent->nextthink = level.time + 3; 370 } 371 372 void plat_hit_bottom (edict_t *ent) 373 { 374 if (!(ent->flags & FL_TEAMSLAVE)) 375 { 376 if (ent->moveinfo.sound_end) 377 gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); 378 ent->s.sound = 0; 379 } 380 ent->moveinfo.state = STATE_BOTTOM; 381 } 382 383 void plat_go_down (edict_t *ent) 384 { 385 if (!(ent->flags & FL_TEAMSLAVE)) 386 { 387 if (ent->moveinfo.sound_start) 388 gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); 389 ent->s.sound = ent->moveinfo.sound_middle; 390 } 391 ent->moveinfo.state = STATE_DOWN; 392 Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom); 393 } 394 395 void plat_go_up (edict_t *ent) 396 { 397 if (!(ent->flags & FL_TEAMSLAVE)) 398 { 399 if (ent->moveinfo.sound_start) 400 gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); 401 ent->s.sound = ent->moveinfo.sound_middle; 402 } 403 ent->moveinfo.state = STATE_UP; 404 Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top); 405 } 406 407 void plat_blocked (edict_t *self, edict_t *other) 408 { 409 if (!(other->svflags & SVF_MONSTER) && (!other->client) ) 410 { 411 // give it a chance to go away on it's own terms (like gibs) 412 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); 413 // if it's still there, nuke it 414 if (other) 415 BecomeExplosion1 (other); 416 return; 417 } 418 419 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); 420 421 if (self->moveinfo.state == STATE_UP) 422 plat_go_down (self); 423 else if (self->moveinfo.state == STATE_DOWN) 424 plat_go_up (self); 425 } 426 427 428 void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator) 429 { 430 if (ent->think) 431 return; // already down 432 plat_go_down (ent); 433 } 434 435 436 void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) 437 { 438 if (!other->client) 439 return; 440 441 if (other->health <= 0) 442 return; 443 444 ent = ent->enemy; // now point at the plat, not the trigger 445 if (ent->moveinfo.state == STATE_BOTTOM) 446 plat_go_up (ent); 447 else if (ent->moveinfo.state == STATE_TOP) 448 ent->nextthink = level.time + 1; // the player is still on the plat, so delay going down 449 } 450 451 void plat_spawn_inside_trigger (edict_t *ent) 452 { 453 edict_t *trigger; 454 vec3_t tmin, tmax; 455 456 // 457 // middle trigger 458 // 459 trigger = G_Spawn(); 460 trigger->touch = Touch_Plat_Center; 461 trigger->movetype = MOVETYPE_NONE; 462 trigger->solid = SOLID_TRIGGER; 463 trigger->enemy = ent; 464 465 tmin[0] = ent->mins[0] + 25; 466 tmin[1] = ent->mins[1] + 25; 467 tmin[2] = ent->mins[2]; 468 469 tmax[0] = ent->maxs[0] - 25; 470 tmax[1] = ent->maxs[1] - 25; 471 tmax[2] = ent->maxs[2] + 8; 472 473 tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip); 474 475 if (ent->spawnflags & PLAT_LOW_TRIGGER) 476 tmax[2] = tmin[2] + 8; 477 478 if (tmax[0] - tmin[0] <= 0) 479 { 480 tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5; 481 tmax[0] = tmin[0] + 1; 482 } 483 if (tmax[1] - tmin[1] <= 0) 484 { 485 tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5; 486 tmax[1] = tmin[1] + 1; 487 } 488 489 VectorCopy (tmin, trigger->mins); 490 VectorCopy (tmax, trigger->maxs); 491 492 gi.linkentity (trigger); 493 } 494 495 496 /*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER 497 speed default 150 498 499 Plats are always drawn in the extended position, so they will light correctly. 500 501 If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat. 502 503 "speed" overrides default 200. 504 "accel" overrides default 500 505 "lip" overrides default 8 pixel lip 506 507 If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height. 508 509 Set "sounds" to one of the following: 510 1) base fast 511 2) chain slow 512 */ 513 void SP_func_plat (edict_t *ent) 514 { 515 VectorClear (ent->s.angles); 516 ent->solid = SOLID_BSP; 517 ent->movetype = MOVETYPE_PUSH; 518 519 gi.setmodel (ent, ent->model); 520 521 ent->blocked = plat_blocked; 522 523 if (!ent->speed) 524 ent->speed = 20; 525 else 526 ent->speed *= 0.1; 527 528 if (!ent->accel) 529 ent->accel = 5; 530 else 531 ent->accel *= 0.1; 532 533 if (!ent->decel) 534 ent->decel = 5; 535 else 536 ent->decel *= 0.1; 537 538 if (!ent->dmg) 539 ent->dmg = 2; 540 541 if (!st.lip) 542 st.lip = 8; 543 544 // pos1 is the top position, pos2 is the bottom 545 VectorCopy (ent->s.origin, ent->pos1); 546 VectorCopy (ent->s.origin, ent->pos2); 547 if (st.height) 548 ent->pos2[2] -= st.height; 549 else 550 ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; 551 552 ent->use = Use_Plat; 553 554 plat_spawn_inside_trigger (ent); // the "start moving" trigger 555 556 if (ent->targetname) 557 { 558 ent->moveinfo.state = STATE_UP; 559 } 560 else 561 { 562 VectorCopy (ent->pos2, ent->s.origin); 563 gi.linkentity (ent); 564 ent->moveinfo.state = STATE_BOTTOM; 565 } 566 567 ent->moveinfo.speed = ent->speed; 568 ent->moveinfo.accel = ent->accel; 569 ent->moveinfo.decel = ent->decel; 570 ent->moveinfo.wait = ent->wait; 571 VectorCopy (ent->pos1, ent->moveinfo.start_origin); 572 VectorCopy (ent->s.angles, ent->moveinfo.start_angles); 573 VectorCopy (ent->pos2, ent->moveinfo.end_origin); 574 VectorCopy (ent->s.angles, ent->moveinfo.end_angles); 575 576 ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav"); 577 ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav"); 578 ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav"); 579 } 580 581 //==================================================================== 582 583 /*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST 584 You need to have an origin brush as part of this entity. The center of that brush will be 585 the point around which it is rotated. It will rotate around the Z axis by default. You can 586 check either the X_AXIS or Y_AXIS box to change that. 587 588 "speed" determines how fast it moves; default value is 100. 589 "dmg" damage to inflict when blocked (2 default) 590 591 REVERSE will cause the it to rotate in the opposite direction. 592 STOP mean it will stop moving instead of pushing entities 593 */ 594 595 void rotating_blocked (edict_t *self, edict_t *other) 596 { 597 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); 598 } 599 600 void rotating_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) 601 { 602 if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2]) 603 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); 604 } 605 606 void rotating_use (edict_t *self, edict_t *other, edict_t *activator) 607 { 608 if (!VectorCompare (self->avelocity, vec3_origin)) 609 { 610 self->s.sound = 0; 611 VectorClear (self->avelocity); 612 self->touch = NULL; 613 } 614 else 615 { 616 self->s.sound = self->moveinfo.sound_middle; 617 VectorScale (self->movedir, self->speed, self->avelocity); 618 if (self->spawnflags & 16) 619 self->touch = rotating_touch; 620 } 621 } 622 623 void SP_func_rotating (edict_t *ent) 624 { 625 ent->solid = SOLID_BSP; 626 if (ent->spawnflags & 32) 627 ent->movetype = MOVETYPE_STOP; 628 else 629 ent->movetype = MOVETYPE_PUSH; 630 631 // set the axis of rotation 632 VectorClear(ent->movedir); 633 if (ent->spawnflags & 4) 634 ent->movedir[2] = 1.0; 635 else if (ent->spawnflags & 8) 636 ent->movedir[0] = 1.0; 637 else // Z_AXIS 638 ent->movedir[1] = 1.0; 639 640 // check for reverse rotation 641 if (ent->spawnflags & 2) 642 VectorNegate (ent->movedir, ent->movedir); 643 644 if (!ent->speed) 645 ent->speed = 100; 646 if (!ent->dmg) 647 ent->dmg = 2; 648 649 // ent->moveinfo.sound_middle = "doors/hydro1.wav"; 650 651 ent->use = rotating_use; 652 if (ent->dmg) 653 ent->blocked = rotating_blocked; 654 655 if (ent->spawnflags & 1) 656 ent->use (ent, NULL, NULL); 657 658 if (ent->spawnflags & 64) 659 ent->s.effects |= EF_ANIM_ALL; 660 if (ent->spawnflags & 128) 661 ent->s.effects |= EF_ANIM_ALLFAST; 662 663 gi.setmodel (ent, ent->model); 664 gi.linkentity (ent); 665 } 666 667 /* 668 ====================================================================== 669 670 BUTTONS 671 672 ====================================================================== 673 */ 674 675 /*QUAKED func_button (0 .5 .8) ? 676 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. 677 678 "angle" determines the opening direction 679 "target" all entities with a matching targetname will be used 680 "speed" override the default 40 speed 681 "wait" override the default 1 second wait (-1 = never return) 682 "lip" override the default 4 pixel lip remaining at end of move 683 "health" if set, the button must be killed instead of touched 684 "sounds" 685 1) silent 686 2) steam metal 687 3) wooden clunk 688 4) metallic click 689 5) in-out 690 */ 691 692 void button_done (edict_t *self) 693 { 694 self->moveinfo.state = STATE_BOTTOM; 695 self->s.effects &= ~EF_ANIM23; 696 self->s.effects |= EF_ANIM01; 697 } 698 699 void button_return (edict_t *self) 700 { 701 self->moveinfo.state = STATE_DOWN; 702 703 Move_Calc (self, self->moveinfo.start_origin, button_done); 704 705 self->s.frame = 0; 706 707 if (self->health) 708 self->takedamage = DAMAGE_YES; 709 } 710 711 void button_wait (edict_t *self) 712 { 713 self->moveinfo.state = STATE_TOP; 714 self->s.effects &= ~EF_ANIM01; 715 self->s.effects |= EF_ANIM23; 716 717 G_UseTargets (self, self->activator); 718 self->s.frame = 1; 719 if (self->moveinfo.wait >= 0) 720 { 721 self->nextthink = level.time + self->moveinfo.wait; 722 self->think = button_return; 723 } 724 } 725 726 void button_fire (edict_t *self) 727 { 728 if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) 729 return; 730 731 self->moveinfo.state = STATE_UP; 732 if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE)) 733 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); 734 Move_Calc (self, self->moveinfo.end_origin, button_wait); 735 } 736 737 void button_use (edict_t *self, edict_t *other, edict_t *activator) 738 { 739 self->activator = activator; 740 button_fire (self); 741 } 742 743 void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) 744 { 745 if (!other->client) 746 return; 747 748 if (other->health <= 0) 749 return; 750 751 self->activator = other; 752 button_fire (self); 753 } 754 755 void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) 756 { 757 self->activator = attacker; 758 self->health = self->max_health; 759 self->takedamage = DAMAGE_NO; 760 button_fire (self); 761 } 762 763 void SP_func_button (edict_t *ent) 764 { 765 vec3_t abs_movedir; 766 float dist; 767 768 G_SetMovedir (ent->s.angles, ent->movedir); 769 ent->movetype = MOVETYPE_STOP; 770 ent->solid = SOLID_BSP; 771 gi.setmodel (ent, ent->model); 772 773 if (ent->sounds != 1) 774 ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav"); 775 776 if (!ent->speed) 777 ent->speed = 40; 778 if (!ent->accel) 779 ent->accel = ent->speed; 780 if (!ent->decel) 781 ent->decel = ent->speed; 782 783 if (!ent->wait) 784 ent->wait = 3; 785 if (!st.lip) 786 st.lip = 4; 787 788 VectorCopy (ent->s.origin, ent->pos1); 789 abs_movedir[0] = fabs(ent->movedir[0]); 790 abs_movedir[1] = fabs(ent->movedir[1]); 791 abs_movedir[2] = fabs(ent->movedir[2]); 792 dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; 793 VectorMA (ent->pos1, dist, ent->movedir, ent->pos2); 794 795 ent->use = button_use; 796 ent->s.effects |= EF_ANIM01; 797 798 if (ent->health) 799 { 800 ent->max_health = ent->health; 801 ent->die = button_killed; 802 ent->takedamage = DAMAGE_YES; 803 } 804 else if (! ent->targetname) 805 ent->touch = button_touch; 806 807 ent->moveinfo.state = STATE_BOTTOM; 808 809 ent->moveinfo.speed = ent->speed; 810 ent->moveinfo.accel = ent->accel; 811 ent->moveinfo.decel = ent->decel; 812 ent->moveinfo.wait = ent->wait; 813 VectorCopy (ent->pos1, ent->moveinfo.start_origin); 814 VectorCopy (ent->s.angles, ent->moveinfo.start_angles); 815 VectorCopy (ent->pos2, ent->moveinfo.end_origin); 816 VectorCopy (ent->s.angles, ent->moveinfo.end_angles); 817 818 gi.linkentity (ent); 819 } 820 821 /* 822 ====================================================================== 823 824 DOORS 825 826 spawn a trigger surrounding the entire team unless it is 827 already targeted by another 828 829 ====================================================================== 830 */ 831 832 /*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST 833 TOGGLE wait in both the start and end states for a trigger event. 834 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). 835 NOMONSTER monsters will not trigger this door 836 837 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet 838 "angle" determines the opening direction 839 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. 840 "health" if set, door must be shot open 841 "speed" movement speed (100 default) 842 "wait" wait before returning (3 default, -1 = never return) 843 "lip" lip remaining at end of move (8 default) 844 "dmg" damage to inflict when blocked (2 default) 845 "sounds" 846 1) silent 847 2) light 848 3) medium 849 4) heavy 850 */ 851 852 void door_use_areaportals (edict_t *self, qboolean open) 853 { 854 edict_t *t = NULL; 855 856 if (!self->target) 857 return; 858 859 while ((t = G_Find (t, FOFS(targetname), self->target))) 860 { 861 if (Q_stricmp(t->classname, "func_areaportal") == 0) 862 { 863 gi.SetAreaPortalState (t->style, open); 864 } 865 } 866 } 867 868 void door_go_down (edict_t *self); 869 870 void door_hit_top (edict_t *self) 871 { 872 if (!(self->flags & FL_TEAMSLAVE)) 873 { 874 if (self->moveinfo.sound_end) 875 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); 876 self->s.sound = 0; 877 } 878 self->moveinfo.state = STATE_TOP; 879 if (self->spawnflags & DOOR_TOGGLE) 880 return; 881 if (self->moveinfo.wait >= 0) 882 { 883 self->think = door_go_down; 884 self->nextthink = level.time + self->moveinfo.wait; 885 } 886 } 887 888 void door_hit_bottom (edict_t *self) 889 { 890 if (!(self->flags & FL_TEAMSLAVE)) 891 { 892 if (self->moveinfo.sound_end) 893 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); 894 self->s.sound = 0; 895 } 896 self->moveinfo.state = STATE_BOTTOM; 897 door_use_areaportals (self, false); 898 } 899 900 void door_go_down (edict_t *self) 901 { 902 if (!(self->flags & FL_TEAMSLAVE)) 903 { 904 if (self->moveinfo.sound_start) 905 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); 906 self->s.sound = self->moveinfo.sound_middle; 907 } 908 if (self->max_health) 909 { 910 self->takedamage = DAMAGE_YES; 911 self->health = self->max_health; 912 } 913 914 self->moveinfo.state = STATE_DOWN; 915 if (strcmp(self->classname, "func_door") == 0) 916 Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom); 917 else if (strcmp(self->classname, "func_door_rotating") == 0) 918 AngleMove_Calc (self, door_hit_bottom); 919 } 920 921 void door_go_up (edict_t *self, edict_t *activator) 922 { 923 if (self->moveinfo.state == STATE_UP) 924 return; // already going up 925 926 if (self->moveinfo.state == STATE_TOP) 927 { // reset top wait time 928 if (self->moveinfo.wait >= 0) 929 self->nextthink = level.time + self->moveinfo.wait; 930 return; 931 } 932 933 if (!(self->flags & FL_TEAMSLAVE)) 934 { 935 if (self->moveinfo.sound_start) 936 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); 937 self->s.sound = self->moveinfo.sound_middle; 938 } 939 self->moveinfo.state = STATE_UP; 940 if (strcmp(self->classname, "func_door") == 0) 941 Move_Calc (self, self->moveinfo.end_origin, door_hit_top); 942 else if (strcmp(self->classname, "func_door_rotating") == 0) 943 AngleMove_Calc (self, door_hit_top); 944 945 G_UseTargets (self, activator); 946 door_use_areaportals (self, true); 947 } 948 949 void door_use (edict_t *self, edict_t *other, edict_t *activator) 950 { 951 edict_t *ent; 952 953 if (self->flags & FL_TEAMSLAVE) 954 return; 955 956 if (self->spawnflags & DOOR_TOGGLE) 957 { 958 if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) 959 { 960 // trigger all paired doors 961 for (ent = self ; ent ; ent = ent->teamchain) 962 { 963 ent->message = NULL; 964 ent->touch = NULL; 965 door_go_down (ent); 966 } 967 return; 968 } 969 } 970 971 // trigger all paired doors 972 for (ent = self ; ent ; ent = ent->teamchain) 973 { 974 ent->message = NULL; 975 ent->touch = NULL; 976 door_go_up (ent, activator); 977 } 978 }; 979 980 void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) 981 { 982 if (other->health <= 0) 983 return; 984 985 if (!(other->svflags & SVF_MONSTER) && (!other->client)) 986 return; 987 988 if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER)) 989 return; 990 991 if (level.time < self->touch_debounce_time) 992 return; 993 self->touch_debounce_time = level.time + 1.0; 994 995 door_use (self->owner, other, other); 996 } 997 998 void Think_CalcMoveSpeed (edict_t *self) 999 { 1000 edict_t *ent; 1001 float min; 1002 float time; 1003 float newspeed; 1004 float ratio; 1005 float dist; 1006 1007 if (self->flags & FL_TEAMSLAVE) 1008 return; // only the team master does this 1009 1010 // find the smallest distance any member of the team will be moving 1011 min = fabs(self->moveinfo.distance); 1012 for (ent = self->teamchain; ent; ent = ent->teamchain) 1013 { 1014 dist = fabs(ent->moveinfo.distance); 1015 if (dist < min) 1016 min = dist; 1017 } 1018 1019 time = min / self->moveinfo.speed; 1020 1021 // adjust speeds so they will all complete at the same time 1022 for (ent = self; ent; ent = ent->teamchain) 1023 { 1024 newspeed = fabs(ent->moveinfo.distance) / time; 1025 ratio = newspeed / ent->moveinfo.speed; 1026 if (ent->moveinfo.accel == ent->moveinfo.speed) 1027 ent->moveinfo.accel = newspeed; 1028 else 1029 ent->moveinfo.accel *= ratio; 1030 if (ent->moveinfo.decel == ent->moveinfo.speed) 1031 ent->moveinfo.decel = newspeed; 1032 else 1033 ent->moveinfo.decel *= ratio; 1034 ent->moveinfo.speed = newspeed; 1035 } 1036 } 1037 1038 void Think_SpawnDoorTrigger (edict_t *ent) 1039 { 1040 edict_t *other; 1041 vec3_t mins, maxs; 1042 1043 if (ent->flags & FL_TEAMSLAVE) 1044 return; // only the team leader spawns a trigger 1045 1046 VectorCopy (ent->absmin, mins); 1047 VectorCopy (ent->absmax, maxs); 1048 1049 for (other = ent->teamchain ; other ; other=other->teamchain) 1050 { 1051 AddPointToBounds (other->absmin, mins, maxs); 1052 AddPointToBounds (other->absmax, mins, maxs); 1053 } 1054 1055 // expand 1056 mins[0] -= 60; 1057 mins[1] -= 60; 1058 maxs[0] += 60; 1059 maxs[1] += 60; 1060 1061 other = G_Spawn (); 1062 VectorCopy (mins, other->mins); 1063 VectorCopy (maxs, other->maxs); 1064 other->owner = ent; 1065 other->solid = SOLID_TRIGGER; 1066 other->movetype = MOVETYPE_NONE; 1067 other->touch = Touch_DoorTrigger; 1068 gi.linkentity (other); 1069 1070 if (ent->spawnflags & DOOR_START_OPEN) 1071 door_use_areaportals (ent, true); 1072 1073 Think_CalcMoveSpeed (ent); 1074 } 1075 1076 void door_blocked (edict_t *self, edict_t *other) 1077 { 1078 edict_t *ent; 1079 1080 if (!(other->svflags & SVF_MONSTER) && (!other->client) ) 1081 { 1082 // give it a chance to go away on it's own terms (like gibs) 1083 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); 1084 // if it's still there, nuke it 1085 if (other) 1086 BecomeExplosion1 (other); 1087 return; 1088 } 1089 1090 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); 1091 1092 if (self->spawnflags & DOOR_CRUSHER) 1093 return; 1094 1095 1096 // if a door has a negative wait, it would never come back if blocked, 1097 // so let it just squash the object to death real fast 1098 if (self->moveinfo.wait >= 0) 1099 { 1100 if (self->moveinfo.state == STATE_DOWN) 1101 { 1102 for (ent = self->teammaster ; ent ; ent = ent->teamchain) 1103 door_go_up (ent, ent->activator); 1104 } 1105 else 1106 { 1107 for (ent = self->teammaster ; ent ; ent = ent->teamchain) 1108 door_go_down (ent); 1109 } 1110 } 1111 } 1112 1113 void door_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) 1114 { 1115 edict_t *ent; 1116 1117 for (ent = self->teammaster ; ent ; ent = ent->teamchain) 1118 { 1119 ent->health = ent->max_health; 1120 ent->takedamage = DAMAGE_NO; 1121 } 1122 door_use (self->teammaster, attacker, attacker); 1123 } 1124 1125 void door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) 1126 { 1127 if (!other->client) 1128 return; 1129 1130 if (level.time < self->touch_debounce_time) 1131 return; 1132 self->touch_debounce_time = level.time + 5.0; 1133 1134 gi.centerprintf (other, "%s", self->message); 1135 gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); 1136 } 1137 1138 void SP_func_door (edict_t *ent) 1139 { 1140 vec3_t abs_movedir; 1141 1142 if (ent->sounds != 1) 1143 { 1144 ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); 1145 ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); 1146 ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); 1147 } 1148 1149 G_SetMovedir (ent->s.angles, ent->movedir); 1150 ent->movetype = MOVETYPE_PUSH; 1151 ent->solid = SOLID_BSP; 1152 gi.setmodel (ent, ent->model); 1153 1154 ent->blocked = door_blocked; 1155 ent->use = door_use; 1156 1157 if (!ent->speed) 1158 ent->speed = 100; 1159 if (deathmatch->value) 1160 ent->speed *= 2; 1161 1162 if (!ent->accel) 1163 ent->accel = ent->speed; 1164 if (!ent->decel) 1165 ent->decel = ent->speed; 1166 1167 if (!ent->wait) 1168 ent->wait = 3; 1169 if (!st.lip) 1170 st.lip = 8; 1171 if (!ent->dmg) 1172 ent->dmg = 2; 1173 1174 // calculate second position 1175 VectorCopy (ent->s.origin, ent->pos1); 1176 abs_movedir[0] = fabs(ent->movedir[0]); 1177 abs_movedir[1] = fabs(ent->movedir[1]); 1178 abs_movedir[2] = fabs(ent->movedir[2]); 1179 ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; 1180 VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2); 1181 1182 // if it starts open, switch the positions 1183 if (ent->spawnflags & DOOR_START_OPEN) 1184 { 1185 VectorCopy (ent->pos2, ent->s.origin); 1186 VectorCopy (ent->pos1, ent->pos2); 1187 VectorCopy (ent->s.origin, ent->pos1); 1188 } 1189 1190 ent->moveinfo.state = STATE_BOTTOM; 1191 1192 if (ent->health) 1193 { 1194 ent->takedamage = DAMAGE_YES; 1195 ent->die = door_killed; 1196 ent->max_health = ent->health; 1197 } 1198 else if (ent->targetname && ent->message) 1199 { 1200 gi.soundindex ("misc/talk.wav"); 1201 ent->touch = door_touch; 1202 } 1203 1204 ent->moveinfo.speed = ent->speed; 1205 ent->moveinfo.accel = ent->accel; 1206 ent->moveinfo.decel = ent->decel; 1207 ent->moveinfo.wait = ent->wait; 1208 VectorCopy (ent->pos1, ent->moveinfo.start_origin); 1209 VectorCopy (ent->s.angles, ent->moveinfo.start_angles); 1210 VectorCopy (ent->pos2, ent->moveinfo.end_origin); 1211 VectorCopy (ent->s.angles, ent->moveinfo.end_angles); 1212 1213 if (ent->spawnflags & 16) 1214 ent->s.effects |= EF_ANIM_ALL; 1215 if (ent->spawnflags & 64) 1216 ent->s.effects |= EF_ANIM_ALLFAST; 1217 1218 // to simplify logic elsewhere, make non-teamed doors into a team of one 1219 if (!ent->team) 1220 ent->teammaster = ent; 1221 1222 gi.linkentity (ent); 1223 1224 ent->nextthink = level.time + FRAMETIME; 1225 if (ent->health || ent->targetname) 1226 ent->think = Think_CalcMoveSpeed; 1227 else 1228 ent->think = Think_SpawnDoorTrigger; 1229 } 1230 1231 1232 /*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS 1233 TOGGLE causes the door to wait in both the start and end states for a trigger event. 1234 1235 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). 1236 NOMONSTER monsters will not trigger this door 1237 1238 You need to have an origin brush as part of this entity. The center of that brush will be 1239 the point around which it is rotated. It will rotate around the Z axis by default. You can 1240 check either the X_AXIS or Y_AXIS box to change that. 1241 1242 "distance" is how many degrees the door will be rotated. 1243 "speed" determines how fast the door moves; default value is 100. 1244 1245 REVERSE will cause the door to rotate in the opposite direction. 1246 1247 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet 1248 "angle" determines the opening direction 1249 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. 1250 "health" if set, door must be shot open 1251 "speed" movement speed (100 default) 1252 "wait" wait before returning (3 default, -1 = never return) 1253 "dmg" damage to inflict when blocked (2 default) 1254 "sounds" 1255 1) silent 1256 2) light 1257 3) medium 1258 4) heavy 1259 */ 1260 1261 void SP_func_door_rotating (edict_t *ent) 1262 { 1263 VectorClear (ent->s.angles); 1264 1265 // set the axis of rotation 1266 VectorClear(ent->movedir); 1267 if (ent->spawnflags & DOOR_X_AXIS) 1268 ent->movedir[2] = 1.0; 1269 else if (ent->spawnflags & DOOR_Y_AXIS) 1270 ent->movedir[0] = 1.0; 1271 else // Z_AXIS 1272 ent->movedir[1] = 1.0; 1273 1274 // check for reverse rotation 1275 if (ent->spawnflags & DOOR_REVERSE) 1276 VectorNegate (ent->movedir, ent->movedir); 1277 1278 if (!st.distance) 1279 { 1280 gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin)); 1281 st.distance = 90; 1282 } 1283 1284 VectorCopy (ent->s.angles, ent->pos1); 1285 VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2); 1286 ent->moveinfo.distance = st.distance; 1287 1288 ent->movetype = MOVETYPE_PUSH; 1289 ent->solid = SOLID_BSP; 1290 gi.setmodel (ent, ent->model); 1291 1292 ent->blocked = door_blocked; 1293 ent->use = door_use; 1294 1295 if (!ent->speed) 1296 ent->speed = 100; 1297 if (!ent->accel) 1298 ent->accel = ent->speed; 1299 if (!ent->decel) 1300 ent->decel = ent->speed; 1301 1302 if (!ent->wait) 1303 ent->wait = 3; 1304 if (!ent->dmg) 1305 ent->dmg = 2; 1306 1307 if (ent->sounds != 1) 1308 { 1309 ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); 1310 ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); 1311 ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); 1312 } 1313 1314 // if it starts open, switch the positions 1315 if (ent->spawnflags & DOOR_START_OPEN) 1316 { 1317 VectorCopy (ent->pos2, ent->s.angles); 1318 VectorCopy (ent->pos1, ent->pos2); 1319 VectorCopy (ent->s.angles, ent->pos1); 1320 VectorNegate (ent->movedir, ent->movedir); 1321 } 1322 1323 if (ent->health) 1324 { 1325 ent->takedamage = DAMAGE_YES; 1326 ent->die = door_killed; 1327 ent->max_health = ent->health; 1328 } 1329 1330 if (ent->targetname && ent->message) 1331 { 1332 gi.soundindex ("misc/talk.wav"); 1333 ent->touch = door_touch; 1334 } 1335 1336 ent->moveinfo.state = STATE_BOTTOM; 1337 ent->moveinfo.speed = ent->speed; 1338 ent->moveinfo.accel = ent->accel; 1339 ent->moveinfo.decel = ent->decel; 1340 ent->moveinfo.wait = ent->wait; 1341 VectorCopy (ent->s.origin, ent->moveinfo.start_origin); 1342 VectorCopy (ent->pos1, ent->moveinfo.start_angles); 1343 VectorCopy (ent->s.origin, ent->moveinfo.end_origin); 1344 VectorCopy (ent->pos2, ent->moveinfo.end_angles); 1345 1346 if (ent->spawnflags & 16) 1347 ent->s.effects |= EF_ANIM_ALL; 1348 1349 // to simplify logic elsewhere, make non-teamed doors into a team of one 1350 if (!ent->team) 1351 ent->teammaster = ent; 1352 1353 gi.linkentity (ent); 1354 1355 ent->nextthink = level.time + FRAMETIME; 1356 if (ent->health || ent->targetname) 1357 ent->think = Think_CalcMoveSpeed; 1358 else 1359 ent->think = Think_SpawnDoorTrigger; 1360 } 1361 1362 1363 /*QUAKED func_water (0 .5 .8) ? START_OPEN 1364 func_water is a moveable water brush. It must be targeted to operate. Use a non-water texture at your own risk. 1365 1366 START_OPEN causes the water to move to its destination when spawned and operate in reverse. 1367 1368 "angle" determines the opening direction (up or down only) 1369 "speed" movement speed (25 default) 1370 "wait" wait before returning (-1 default, -1 = TOGGLE) 1371 "lip" lip remaining at end of move (0 default) 1372 "sounds" (yes, these need to be changed) 1373 0) no sound 1374 1) water 1375 2) lava 1376 */ 1377 1378 void SP_func_water (edict_t *self) 1379 { 1380 vec3_t abs_movedir; 1381 1382 G_SetMovedir (self->s.angles, self->movedir); 1383 self->movetype = MOVETYPE_PUSH; 1384 self->solid = SOLID_BSP; 1385 gi.setmodel (self, self->model); 1386 1387 switch (self->sounds) 1388 { 1389 default: 1390 break; 1391 1392 case 1: // water 1393 self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); 1394 self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); 1395 break; 1396 1397 case 2: // lava 1398 self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); 1399 self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); 1400 break; 1401 } 1402 1403 // calculate second position 1404 VectorCopy (self->s.origin, self->pos1); 1405 abs_movedir[0] = fabs(self->movedir[0]); 1406 abs_movedir[1] = fabs(self->movedir[1]); 1407 abs_movedir[2] = fabs(self->movedir[2]); 1408 self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip; 1409 VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2); 1410 1411 // if it starts open, switch the positions 1412 if (self->spawnflags & DOOR_START_OPEN) 1413 { 1414 VectorCopy (self->pos2, self->s.origin); 1415 VectorCopy (self->pos1, self->pos2); 1416 VectorCopy (self->s.origin, self->pos1); 1417 } 1418 1419 VectorCopy (self->pos1, self->moveinfo.start_origin); 1420 VectorCopy (self->s.angles, self->moveinfo.start_angles); 1421 VectorCopy (self->pos2, self->moveinfo.end_origin); 1422 VectorCopy (self->s.angles, self->moveinfo.end_angles); 1423 1424 self->moveinfo.state = STATE_BOTTOM; 1425 1426 if (!self->speed) 1427 self->speed = 25; 1428 self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed; 1429 1430 if (!self->wait) 1431 self->wait = -1; 1432 self->moveinfo.wait = self->wait; 1433 1434 self->use = door_use; 1435 1436 if (self->wait == -1) 1437 self->spawnflags |= DOOR_TOGGLE; 1438 1439 self->classname = "func_door"; 1440 1441 gi.linkentity (self); 1442 } 1443 1444 1445 #define TRAIN_START_ON 1 1446 #define TRAIN_TOGGLE 2 1447 #define TRAIN_BLOCK_STOPS 4 1448 1449 /*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS 1450 Trains are moving platforms that players can ride. 1451 The targets origin specifies the min point of the train at each corner. 1452 The train spawns at the first target it is pointing at. 1453 If the train is the target of a button or trigger, it will not begin moving until activated. 1454 speed default 100 1455 dmg default 2 1456 noise looping sound to play when the train is in motion 1457 1458 */ 1459 void train_next (edict_t *self); 1460 1461 void train_blocked (edict_t *self, edict_t *other) 1462 { 1463 if (!(other->svflags & SVF_MONSTER) && (!other->client) ) 1464 { 1465 // give it a chance to go away on it's own terms (like gibs) 1466 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); 1467 // if it's still there, nuke it 1468 if (other) 1469 BecomeExplosion1 (other); 1470 return; 1471 } 1472 1473 if (level.time < self->touch_debounce_time) 1474 return; 1475 1476 if (!self->dmg) 1477 return; 1478 self->touch_debounce_time = level.time + 0.5; 1479 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); 1480 } 1481 1482 void train_wait (edict_t *self) 1483 { 1484 if (self->target_ent->pathtarget) 1485 { 1486 char *savetarget; 1487 edict_t *ent; 1488 1489 ent = self->target_ent; 1490 savetarget = ent->target; 1491 ent->target = ent->pathtarget; 1492 G_UseTargets (ent, self->activator); 1493 ent->target = savetarget; 1494 1495 // make sure we didn't get killed by a killtarget 1496 if (!self->inuse) 1497 return; 1498 } 1499 1500 if (self->moveinfo.wait) 1501 { 1502 if (self->moveinfo.wait > 0) 1503 { 1504 self->nextthink = level.time + self->moveinfo.wait; 1505 self->think = train_next; 1506 } 1507 else if (self->spawnflags & TRAIN_TOGGLE) // && wait < 0 1508 { 1509 train_next (self); 1510 self->spawnflags &= ~TRAIN_START_ON; 1511 VectorClear (self->velocity); 1512 self->nextthink = 0; 1513 } 1514 1515 if (!(self->flags & FL_TEAMSLAVE)) 1516 { 1517 if (self->moveinfo.sound_end) 1518 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); 1519 self->s.sound = 0; 1520 } 1521 } 1522 else 1523 { 1524 train_next (self); 1525 } 1526 1527 } 1528 1529 void train_next (edict_t *self) 1530 { 1531 edict_t *ent; 1532 vec3_t dest; 1533 qboolean first; 1534 1535 first = true; 1536 again: 1537 if (!self->target) 1538 { 1539 // gi.dprintf ("train_next: no next target\n"); 1540 return; 1541 } 1542 1543 ent = G_PickTarget (self->target); 1544 if (!ent) 1545 { 1546 gi.dprintf ("train_next: bad target %s\n", self->target); 1547 return; 1548 } 1549 1550 self->target = ent->target; 1551 1552 // check for a teleport path_corner 1553 if (ent->spawnflags & 1) 1554 { 1555 if (!first) 1556 { 1557 gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin)); 1558 return; 1559 } 1560 first = false; 1561 VectorSubtract (ent->s.origin, self->mins, self->s.origin); 1562 VectorCopy (self->s.origin, self->s.old_origin); 1563 self->s.event = EV_OTHER_TELEPORT; 1564 gi.linkentity (self); 1565 goto again; 1566 } 1567 1568 self->moveinfo.wait = ent->wait; 1569 self->target_ent = ent; 1570 1571 if (!(self->flags & FL_TEAMSLAVE)) 1572 { 1573 if (self->moveinfo.sound_start) 1574 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); 1575 self->s.sound = self->moveinfo.sound_middle; 1576 } 1577 1578 VectorSubtract (ent->s.origin, self->mins, dest); 1579 self->moveinfo.state = STATE_TOP; 1580 VectorCopy (self->s.origin, self->moveinfo.start_origin); 1581 VectorCopy (dest, self->moveinfo.end_origin); 1582 Move_Calc (self, dest, train_wait); 1583 self->spawnflags |= TRAIN_START_ON; 1584 } 1585 1586 void train_resume (edict_t *self) 1587 { 1588 edict_t *ent; 1589 vec3_t dest; 1590 1591 ent = self->target_ent; 1592 1593 VectorSubtract (ent->s.origin, self->mins, dest); 1594 self->moveinfo.state = STATE_TOP; 1595 VectorCopy (self->s.origin, self->moveinfo.start_origin); 1596 VectorCopy (dest, self->moveinfo.end_origin); 1597 Move_Calc (self, dest, train_wait); 1598 self->spawnflags |= TRAIN_START_ON; 1599 } 1600 1601 void func_train_find (edict_t *self) 1602 { 1603 edict_t *ent; 1604 1605 if (!self->target) 1606 { 1607 gi.dprintf ("train_find: no target\n"); 1608 return; 1609 } 1610 ent = G_PickTarget (self->target); 1611 if (!ent) 1612 { 1613 gi.dprintf ("train_find: target %s not found\n", self->target); 1614 return; 1615 } 1616 self->target = ent->target; 1617 1618 VectorSubtract (ent->s.origin, self->mins, self->s.origin); 1619 gi.linkentity (self); 1620 1621 // if not triggered, start immediately 1622 if (!self->targetname) 1623 self->spawnflags |= TRAIN_START_ON; 1624 1625 if (self->spawnflags & TRAIN_START_ON) 1626 { 1627 self->nextthink = level.time + FRAMETIME; 1628 self->think = train_next; 1629 self->activator = self; 1630 } 1631 } 1632 1633 void train_use (edict_t *self, edict_t *other, edict_t *activator) 1634 { 1635 self->activator = activator; 1636 1637 if (self->spawnflags & TRAIN_START_ON) 1638 { 1639 if (!(self->spawnflags & TRAIN_TOGGLE)) 1640 return; 1641 self->spawnflags &= ~TRAIN_START_ON; 1642 VectorClear (self->velocity); 1643 self->nextthink = 0; 1644 } 1645 else 1646 { 1647 if (self->target_ent) 1648 train_resume(self); 1649 else 1650 train_next(self); 1651 } 1652 } 1653 1654 void SP_func_train (edict_t *self) 1655 { 1656 self->movetype = MOVETYPE_PUSH; 1657 1658 VectorClear (self->s.angles); 1659 self->blocked = train_blocked; 1660 if (self->spawnflags & TRAIN_BLOCK_STOPS) 1661 self->dmg = 0; 1662 else 1663 { 1664 if (!self->dmg) 1665 self->dmg = 100; 1666 } 1667 self->solid = SOLID_BSP; 1668 gi.setmodel (self, self->model); 1669 1670 if (st.noise) 1671 self->moveinfo.sound_middle = gi.soundindex (st.noise); 1672 1673 if (!self->speed) 1674 self->speed = 100; 1675 1676 self->moveinfo.speed = self->speed; 1677 self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed; 1678 1679 self->use = train_use; 1680 1681 gi.linkentity (self); 1682 1683 if (self->target) 1684 { 1685 // start trains on the second frame, to make sure their targets have had 1686 // a chance to spawn 1687 self->nextthink = level.time + FRAMETIME; 1688 self->think = func_train_find; 1689 } 1690 else 1691 { 1692 gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin)); 1693 } 1694 } 1695 1696 1697 /*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) 1698 */ 1699 void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator) 1700 { 1701 edict_t *target; 1702 1703 if (self->movetarget->nextthink) 1704 { 1705 // gi.dprintf("elevator busy\n"); 1706 return; 1707 } 1708 1709 if (!other->pathtarget) 1710 { 1711 gi.dprintf("elevator used with no pathtarget\n"); 1712 return; 1713 } 1714 1715 target = G_PickTarget (other->pathtarget); 1716 if (!target) 1717 { 1718 gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget); 1719 return; 1720 } 1721 1722 self->movetarget->target_ent = target; 1723 train_resume (self->movetarget); 1724 } 1725 1726 void trigger_elevator_init (edict_t *self) 1727 { 1728 if (!self->target) 1729 { 1730 gi.dprintf("trigger_elevator has no target\n"); 1731 return; 1732 } 1733 self->movetarget = G_PickTarget (self->target); 1734 if (!self->movetarget) 1735 { 1736 gi.dprintf("trigger_elevator unable to find target %s\n", self->target); 1737 return; 1738 } 1739 if (strcmp(self->movetarget->classname, "func_train") != 0) 1740 { 1741 gi.dprintf("trigger_elevator target %s is not a train\n", self->target); 1742 return; 1743 } 1744 1745 self->use = trigger_elevator_use; 1746 self->svflags = SVF_NOCLIENT; 1747 1748 } 1749 1750 void SP_trigger_elevator (edict_t *self) 1751 { 1752 self->think = trigger_elevator_init; 1753 self->nextthink = level.time + FRAMETIME; 1754 } 1755 1756 1757 /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON 1758 "wait" base time between triggering all targets, default is 1 1759 "random" wait variance, default is 0 1760 1761 so, the basic time between firing is a random time between 1762 (wait - random) and (wait + random) 1763 1764 "delay" delay before first firing when turned on, default is 0 1765 1766 "pausetime" additional delay used only the very first time 1767 and only if spawned with START_ON 1768 1769 These can used but not touched. 1770 */ 1771 void func_timer_think (edict_t *self) 1772 { 1773 G_UseTargets (self, self->activator); 1774 self->nextthink = level.time + self->wait + crandom() * self->random; 1775 } 1776 1777 void func_timer_use (edict_t *self, edict_t *other, edict_t *activator) 1778 { 1779 self->activator = activator; 1780 1781 // if on, turn it off 1782 if (self->nextthink) 1783 { 1784 self->nextthink = 0; 1785 return; 1786 } 1787 1788 // turn it on 1789 if (self->delay) 1790 self->nextthink = level.time + self->delay; 1791 else 1792 func_timer_think (self); 1793 } 1794 1795 void SP_func_timer (edict_t *self) 1796 { 1797 if (!self->wait) 1798 self->wait = 1.0; 1799 1800 self->use = func_timer_use; 1801 self->think = func_timer_think; 1802 1803 if (self->random >= self->wait) 1804 { 1805 self->random = self->wait - FRAMETIME; 1806 gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin)); 1807 } 1808 1809 if (self->spawnflags & 1) 1810 { 1811 self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random; 1812 self->activator = self; 1813 } 1814 1815 self->svflags = SVF_NOCLIENT; 1816 } 1817 1818 1819 /*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE 1820 Conveyors are stationary brushes that move what's on them. 1821 The brush should be have a surface with at least one current content enabled. 1822 speed default 100 1823 */ 1824 1825 void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator) 1826 { 1827 if (self->spawnflags & 1) 1828 { 1829 self->speed = 0; 1830 self->spawnflags &= ~1; 1831 } 1832 else 1833 { 1834 self->speed = self->count; 1835 self->spawnflags |= 1; 1836 } 1837 1838 if (!(self->spawnflags & 2)) 1839 self->count = 0; 1840 } 1841 1842 void SP_func_conveyor (edict_t *self) 1843 { 1844 if (!self->speed) 1845 self->speed = 100; 1846 1847 if (!(self->spawnflags & 1)) 1848 { 1849 self->count = self->speed; 1850 self->speed = 0; 1851 } 1852 1853 self->use = func_conveyor_use; 1854 1855 gi.setmodel (self, self->model); 1856 self->solid = SOLID_BSP; 1857 gi.linkentity (self); 1858 } 1859 1860 1861 /*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down 1862 A secret door. Slide back and then to the side. 1863 1864 open_once doors never closes 1865 1st_left 1st move is left of arrow 1866 1st_down 1st move is down from arrow 1867 always_shoot door is shootebale even if targeted 1868 1869 "angle" determines the direction 1870 "dmg" damage to inflic when blocked (default 2) 1871 "wait" how long to hold in the open position (default 5, -1 means hold) 1872 */ 1873 1874 #define SECRET_ALWAYS_SHOOT 1 1875 #define SECRET_1ST_LEFT 2 1876 #define SECRET_1ST_DOWN 4 1877 1878 void door_secret_move1 (edict_t *self); 1879 void door_secret_move2 (edict_t *self); 1880 void door_secret_move3 (edict_t *self); 1881 void door_secret_move4 (edict_t *self); 1882 void door_secret_move5 (edict_t *self); 1883 void door_secret_move6 (edict_t *self); 1884 void door_secret_done (edict_t *self); 1885 1886 void door_secret_use (edict_t *self, edict_t *other, edict_t *activator) 1887 { 1888 // make sure we're not already moving 1889 if (!VectorCompare(self->s.origin, vec3_origin)) 1890 return; 1891 1892 Move_Calc (self, self->pos1, door_secret_move1); 1893 door_use_areaportals (self, true); 1894 } 1895 1896 void door_secret_move1 (edict_t *self) 1897 { 1898 self->nextthink = level.time + 1.0; 1899 self->think = door_secret_move2; 1900 } 1901 1902 void door_secret_move2 (edict_t *self) 1903 { 1904 Move_Calc (self, self->pos2, door_secret_move3); 1905 } 1906 1907 void door_secret_move3 (edict_t *self) 1908 { 1909 if (self->wait == -1) 1910 return; 1911 self->nextthink = level.time + self->wait; 1912 self->think = door_secret_move4; 1913 } 1914 1915 void door_secret_move4 (edict_t *self) 1916 { 1917 Move_Calc (self, self->pos1, door_secret_move5); 1918 } 1919 1920 void door_secret_move5 (edict_t *self) 1921 { 1922 self->nextthink = level.time + 1.0; 1923 self->think = door_secret_move6; 1924 } 1925 1926 void door_secret_move6 (edict_t *self) 1927 { 1928 Move_Calc (self, vec3_origin, door_secret_done); 1929 } 1930 1931 void door_secret_done (edict_t *self) 1932 { 1933 if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT)) 1934 { 1935 self->health = 0; 1936 self->takedamage = DAMAGE_YES; 1937 } 1938 door_use_areaportals (self, false); 1939 } 1940 1941 void door_secret_blocked (edict_t *self, edict_t *other) 1942 { 1943 if (!(other->svflags & SVF_MONSTER) && (!other->client) ) 1944 { 1945 // give it a chance to go away on it's own terms (like gibs) 1946 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); 1947 // if it's still there, nuke it 1948 if (other) 1949 BecomeExplosion1 (other); 1950 return; 1951 } 1952 1953 if (level.time < self->touch_debounce_time) 1954 return; 1955 self->touch_debounce_time = level.time + 0.5; 1956 1957 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); 1958 } 1959 1960 void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) 1961 { 1962 self->takedamage = DAMAGE_NO; 1963 door_secret_use (self, attacker, attacker); 1964 } 1965 1966 void SP_func_door_secret (edict_t *ent) 1967 { 1968 vec3_t forward, right, up; 1969 float side; 1970 float width; 1971 float length; 1972 1973 ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); 1974 ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); 1975 ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); 1976 1977 ent->movetype = MOVETYPE_PUSH; 1978 ent->solid = SOLID_BSP; 1979 gi.setmodel (ent, ent->model); 1980 1981 ent->blocked = door_secret_blocked; 1982 ent->use = door_secret_use; 1983 1984 if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT)) 1985 { 1986 ent->health = 0; 1987 ent->takedamage = DAMAGE_YES; 1988 ent->die = door_secret_die; 1989 } 1990 1991 if (!ent->dmg) 1992 ent->dmg = 2; 1993 1994 if (!ent->wait) 1995 ent->wait = 5; 1996 1997 ent->moveinfo.accel = 1998 ent->moveinfo.decel = 1999 ent->moveinfo.speed = 50; 2000 2001 // calculate positions 2002 AngleVectors (ent->s.angles, forward, right, up); 2003 VectorClear (ent->s.angles); 2004 side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT); 2005 if (ent->spawnflags & SECRET_1ST_DOWN) 2006 width = fabs(DotProduct(up, ent->size)); 2007 else 2008 width = fabs(DotProduct(right, ent->size)); 2009 length = fabs(DotProduct(forward, ent->size)); 2010 if (ent->spawnflags & SECRET_1ST_DOWN) 2011 VectorMA (ent->s.origin, -1 * width, up, ent->pos1); 2012 else 2013 VectorMA (ent->s.origin, side * width, right, ent->pos1); 2014 VectorMA (ent->pos1, length, forward, ent->pos2); 2015 2016 if (ent->health) 2017 { 2018 ent->takedamage = DAMAGE_YES; 2019 ent->die = door_killed; 2020 ent->max_health = ent->health; 2021 } 2022 else if (ent->targetname && ent->message) 2023 { 2024 gi.soundindex ("misc/talk.wav"); 2025 ent->touch = door_touch; 2026 } 2027 2028 ent->classname = "func_door"; 2029 2030 gi.linkentity (ent); 2031 } 2032 2033 2034 /*QUAKED func_killbox (1 0 0) ? 2035 Kills everything inside when fired, irrespective of protection. 2036 */ 2037 void use_killbox (edict_t *self, edict_t *other, edict_t *activator) 2038 { 2039 KillBox (self); 2040 } 2041 2042 void SP_func_killbox (edict_t *ent) 2043 { 2044 gi.setmodel (ent, ent->model); 2045 ent->use = use_killbox; 2046 ent->svflags = SVF_NOCLIENT; 2047 } 2048