g_func.c (51138B)
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 allready 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 gi.linkentity (self); 1564 goto again; 1565 } 1566 1567 self->moveinfo.wait = ent->wait; 1568 self->target_ent = ent; 1569 1570 if (!(self->flags & FL_TEAMSLAVE)) 1571 { 1572 if (self->moveinfo.sound_start) 1573 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); 1574 self->s.sound = self->moveinfo.sound_middle; 1575 } 1576 1577 VectorSubtract (ent->s.origin, self->mins, dest); 1578 self->moveinfo.state = STATE_TOP; 1579 VectorCopy (self->s.origin, self->moveinfo.start_origin); 1580 VectorCopy (dest, self->moveinfo.end_origin); 1581 Move_Calc (self, dest, train_wait); 1582 self->spawnflags |= TRAIN_START_ON; 1583 } 1584 1585 void train_resume (edict_t *self) 1586 { 1587 edict_t *ent; 1588 vec3_t dest; 1589 1590 ent = self->target_ent; 1591 1592 VectorSubtract (ent->s.origin, self->mins, dest); 1593 self->moveinfo.state = STATE_TOP; 1594 VectorCopy (self->s.origin, self->moveinfo.start_origin); 1595 VectorCopy (dest, self->moveinfo.end_origin); 1596 Move_Calc (self, dest, train_wait); 1597 self->spawnflags |= TRAIN_START_ON; 1598 } 1599 1600 void func_train_find (edict_t *self) 1601 { 1602 edict_t *ent; 1603 1604 if (!self->target) 1605 { 1606 gi.dprintf ("train_find: no target\n"); 1607 return; 1608 } 1609 ent = G_PickTarget (self->target); 1610 if (!ent) 1611 { 1612 gi.dprintf ("train_find: target %s not found\n", self->target); 1613 return; 1614 } 1615 self->target = ent->target; 1616 1617 VectorSubtract (ent->s.origin, self->mins, self->s.origin); 1618 gi.linkentity (self); 1619 1620 // if not triggered, start immediately 1621 if (!self->targetname) 1622 self->spawnflags |= TRAIN_START_ON; 1623 1624 if (self->spawnflags & TRAIN_START_ON) 1625 { 1626 self->nextthink = level.time + FRAMETIME; 1627 self->think = train_next; 1628 self->activator = self; 1629 } 1630 } 1631 1632 void train_use (edict_t *self, edict_t *other, edict_t *activator) 1633 { 1634 self->activator = activator; 1635 1636 if (self->spawnflags & TRAIN_START_ON) 1637 { 1638 if (!(self->spawnflags & TRAIN_TOGGLE)) 1639 return; 1640 self->spawnflags &= ~TRAIN_START_ON; 1641 VectorClear (self->velocity); 1642 self->nextthink = 0; 1643 } 1644 else 1645 { 1646 if (self->target_ent) 1647 train_resume(self); 1648 else 1649 train_next(self); 1650 } 1651 } 1652 1653 void SP_func_train (edict_t *self) 1654 { 1655 self->movetype = MOVETYPE_PUSH; 1656 1657 VectorClear (self->s.angles); 1658 self->blocked = train_blocked; 1659 if (self->spawnflags & TRAIN_BLOCK_STOPS) 1660 self->dmg = 0; 1661 else 1662 { 1663 if (!self->dmg) 1664 self->dmg = 100; 1665 } 1666 self->solid = SOLID_BSP; 1667 gi.setmodel (self, self->model); 1668 1669 if (st.noise) 1670 self->moveinfo.sound_middle = gi.soundindex (st.noise); 1671 1672 if (!self->speed) 1673 self->speed = 100; 1674 1675 self->moveinfo.speed = self->speed; 1676 self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed; 1677 1678 self->use = train_use; 1679 1680 gi.linkentity (self); 1681 1682 if (self->target) 1683 { 1684 // start trains on the second frame, to make sure their targets have had 1685 // a chance to spawn 1686 self->nextthink = level.time + FRAMETIME; 1687 self->think = func_train_find; 1688 } 1689 else 1690 { 1691 gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin)); 1692 } 1693 } 1694 1695 1696 /*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) 1697 */ 1698 void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator) 1699 { 1700 edict_t *target; 1701 1702 if (self->movetarget->nextthink) 1703 { 1704 // gi.dprintf("elevator busy\n"); 1705 return; 1706 } 1707 1708 if (!other->pathtarget) 1709 { 1710 gi.dprintf("elevator used with no pathtarget\n"); 1711 return; 1712 } 1713 1714 target = G_PickTarget (other->pathtarget); 1715 if (!target) 1716 { 1717 gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget); 1718 return; 1719 } 1720 1721 self->movetarget->target_ent = target; 1722 train_resume (self->movetarget); 1723 } 1724 1725 void trigger_elevator_init (edict_t *self) 1726 { 1727 if (!self->target) 1728 { 1729 gi.dprintf("trigger_elevator has no target\n"); 1730 return; 1731 } 1732 self->movetarget = G_PickTarget (self->target); 1733 if (!self->movetarget) 1734 { 1735 gi.dprintf("trigger_elevator unable to find target %s\n", self->target); 1736 return; 1737 } 1738 if (strcmp(self->movetarget->classname, "func_train") != 0) 1739 { 1740 gi.dprintf("trigger_elevator target %s is not a train\n", self->target); 1741 return; 1742 } 1743 1744 self->use = trigger_elevator_use; 1745 self->svflags = SVF_NOCLIENT; 1746 1747 } 1748 1749 void SP_trigger_elevator (edict_t *self) 1750 { 1751 self->think = trigger_elevator_init; 1752 self->nextthink = level.time + FRAMETIME; 1753 } 1754 1755 1756 /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON 1757 "wait" base time between triggering all targets, default is 1 1758 "random" wait variance, default is 0 1759 1760 so, the basic time between firing is a random time between 1761 (wait - random) and (wait + random) 1762 1763 "delay" delay before first firing when turned on, default is 0 1764 1765 "pausetime" additional delay used only the very first time 1766 and only if spawned with START_ON 1767 1768 These can used but not touched. 1769 */ 1770 void func_timer_think (edict_t *self) 1771 { 1772 G_UseTargets (self, self->activator); 1773 self->nextthink = level.time + self->wait + crandom() * self->random; 1774 } 1775 1776 void func_timer_use (edict_t *self, edict_t *other, edict_t *activator) 1777 { 1778 self->activator = activator; 1779 1780 // if on, turn it off 1781 if (self->nextthink) 1782 { 1783 self->nextthink = 0; 1784 return; 1785 } 1786 1787 // turn it on 1788 if (self->delay) 1789 self->nextthink = level.time + self->delay; 1790 else 1791 func_timer_think (self); 1792 } 1793 1794 void SP_func_timer (edict_t *self) 1795 { 1796 if (!self->wait) 1797 self->wait = 1.0; 1798 1799 self->use = func_timer_use; 1800 self->think = func_timer_think; 1801 1802 if (self->random >= self->wait) 1803 { 1804 self->random = self->wait - FRAMETIME; 1805 gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin)); 1806 } 1807 1808 if (self->spawnflags & 1) 1809 { 1810 self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random; 1811 self->activator = self; 1812 } 1813 1814 self->svflags = SVF_NOCLIENT; 1815 } 1816 1817 1818 /*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE 1819 Conveyors are stationary brushes that move what's on them. 1820 The brush should be have a surface with at least one current content enabled. 1821 speed default 100 1822 */ 1823 1824 void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator) 1825 { 1826 if (self->spawnflags & 1) 1827 { 1828 self->speed = 0; 1829 self->spawnflags &= ~1; 1830 } 1831 else 1832 { 1833 self->speed = self->count; 1834 self->spawnflags |= 1; 1835 } 1836 1837 if (!(self->spawnflags & 2)) 1838 self->count = 0; 1839 } 1840 1841 void SP_func_conveyor (edict_t *self) 1842 { 1843 if (!self->speed) 1844 self->speed = 100; 1845 1846 if (!(self->spawnflags & 1)) 1847 { 1848 self->count = self->speed; 1849 self->speed = 0; 1850 } 1851 1852 self->use = func_conveyor_use; 1853 1854 gi.setmodel (self, self->model); 1855 self->solid = SOLID_BSP; 1856 gi.linkentity (self); 1857 } 1858 1859 1860 /*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down 1861 A secret door. Slide back and then to the side. 1862 1863 open_once doors never closes 1864 1st_left 1st move is left of arrow 1865 1st_down 1st move is down from arrow 1866 always_shoot door is shootebale even if targeted 1867 1868 "angle" determines the direction 1869 "dmg" damage to inflic when blocked (default 2) 1870 "wait" how long to hold in the open position (default 5, -1 means hold) 1871 */ 1872 1873 #define SECRET_ALWAYS_SHOOT 1 1874 #define SECRET_1ST_LEFT 2 1875 #define SECRET_1ST_DOWN 4 1876 1877 void door_secret_move1 (edict_t *self); 1878 void door_secret_move2 (edict_t *self); 1879 void door_secret_move3 (edict_t *self); 1880 void door_secret_move4 (edict_t *self); 1881 void door_secret_move5 (edict_t *self); 1882 void door_secret_move6 (edict_t *self); 1883 void door_secret_done (edict_t *self); 1884 1885 void door_secret_use (edict_t *self, edict_t *other, edict_t *activator) 1886 { 1887 // make sure we're not already moving 1888 if (!VectorCompare(self->s.origin, vec3_origin)) 1889 return; 1890 1891 Move_Calc (self, self->pos1, door_secret_move1); 1892 door_use_areaportals (self, true); 1893 } 1894 1895 void door_secret_move1 (edict_t *self) 1896 { 1897 self->nextthink = level.time + 1.0; 1898 self->think = door_secret_move2; 1899 } 1900 1901 void door_secret_move2 (edict_t *self) 1902 { 1903 Move_Calc (self, self->pos2, door_secret_move3); 1904 } 1905 1906 void door_secret_move3 (edict_t *self) 1907 { 1908 if (self->wait == -1) 1909 return; 1910 self->nextthink = level.time + self->wait; 1911 self->think = door_secret_move4; 1912 } 1913 1914 void door_secret_move4 (edict_t *self) 1915 { 1916 Move_Calc (self, self->pos1, door_secret_move5); 1917 } 1918 1919 void door_secret_move5 (edict_t *self) 1920 { 1921 self->nextthink = level.time + 1.0; 1922 self->think = door_secret_move6; 1923 } 1924 1925 void door_secret_move6 (edict_t *self) 1926 { 1927 Move_Calc (self, vec3_origin, door_secret_done); 1928 } 1929 1930 void door_secret_done (edict_t *self) 1931 { 1932 if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT)) 1933 { 1934 self->health = 0; 1935 self->takedamage = DAMAGE_YES; 1936 } 1937 door_use_areaportals (self, false); 1938 } 1939 1940 void door_secret_blocked (edict_t *self, edict_t *other) 1941 { 1942 if (!(other->svflags & SVF_MONSTER) && (!other->client) ) 1943 { 1944 // give it a chance to go away on it's own terms (like gibs) 1945 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); 1946 // if it's still there, nuke it 1947 if (other) 1948 BecomeExplosion1 (other); 1949 return; 1950 } 1951 1952 if (level.time < self->touch_debounce_time) 1953 return; 1954 self->touch_debounce_time = level.time + 0.5; 1955 1956 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); 1957 } 1958 1959 void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) 1960 { 1961 self->takedamage = DAMAGE_NO; 1962 door_secret_use (self, attacker, attacker); 1963 } 1964 1965 void SP_func_door_secret (edict_t *ent) 1966 { 1967 vec3_t forward, right, up; 1968 float side; 1969 float width; 1970 float length; 1971 1972 ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); 1973 ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); 1974 ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); 1975 1976 ent->movetype = MOVETYPE_PUSH; 1977 ent->solid = SOLID_BSP; 1978 gi.setmodel (ent, ent->model); 1979 1980 ent->blocked = door_secret_blocked; 1981 ent->use = door_secret_use; 1982 1983 if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT)) 1984 { 1985 ent->health = 0; 1986 ent->takedamage = DAMAGE_YES; 1987 ent->die = door_secret_die; 1988 } 1989 1990 if (!ent->dmg) 1991 ent->dmg = 2; 1992 1993 if (!ent->wait) 1994 ent->wait = 5; 1995 1996 ent->moveinfo.accel = 1997 ent->moveinfo.decel = 1998 ent->moveinfo.speed = 50; 1999 2000 // calculate positions 2001 AngleVectors (ent->s.angles, forward, right, up); 2002 VectorClear (ent->s.angles); 2003 side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT); 2004 if (ent->spawnflags & SECRET_1ST_DOWN) 2005 width = fabs(DotProduct(up, ent->size)); 2006 else 2007 width = fabs(DotProduct(right, ent->size)); 2008 length = fabs(DotProduct(forward, ent->size)); 2009 if (ent->spawnflags & SECRET_1ST_DOWN) 2010 VectorMA (ent->s.origin, -1 * width, up, ent->pos1); 2011 else 2012 VectorMA (ent->s.origin, side * width, right, ent->pos1); 2013 VectorMA (ent->pos1, length, forward, ent->pos2); 2014 2015 if (ent->health) 2016 { 2017 ent->takedamage = DAMAGE_YES; 2018 ent->die = door_killed; 2019 ent->max_health = ent->health; 2020 } 2021 else if (ent->targetname && ent->message) 2022 { 2023 gi.soundindex ("misc/talk.wav"); 2024 ent->touch = door_touch; 2025 } 2026 2027 ent->classname = "func_door"; 2028 2029 gi.linkentity (ent); 2030 } 2031 2032 2033 /*QUAKED func_killbox (1 0 0) ? 2034 Kills everything inside when fired, irrespective of protection. 2035 */ 2036 void use_killbox (edict_t *self, edict_t *other, edict_t *activator) 2037 { 2038 KillBox (self); 2039 } 2040 2041 void SP_func_killbox (edict_t *ent) 2042 { 2043 gi.setmodel (ent, ent->model); 2044 ent->use = use_killbox; 2045 ent->svflags = SVF_NOCLIENT; 2046 } 2047