1 2 entity stemp, otemp, s, old; 3 4 5 void() trigger_reactivate = 6 { 7 self.solid = SOLID_TRIGGER; 8 }; 9 10 //============================================================================= 11 12 float SPAWNFLAG_NOMESSAGE = 1; 13 float SPAWNFLAG_NOTOUCH = 1; 14 15 // the wait time has passed, so set back up for another activation 16 void() multi_wait = 17 { 18 if (self.max_health) 19 { 20 self.health = self.max_health; 21 self.takedamage = DAMAGE_YES; 22 self.solid = SOLID_BBOX; 23 } 24 }; 25 26 27 // the trigger was just touched/killed/used 28 // self.enemy should be set to the activator so it can be held through a delay 29 // so wait for the delay time before firing 30 void() multi_trigger = 31 { 32 if (self.nextthink > time) 33 { 34 return; // allready been triggered 35 } 36 37 if (self.classname == "trigger_secret") 38 { 39 if (self.enemy.classname != "player") 40 return; 41 found_secrets = found_secrets + 1; 42 WriteByte (MSG_ALL, SVC_FOUNDSECRET); 43 } 44 45 if (self.noise) 46 sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); 47 48 // don't trigger again until reset 49 self.takedamage = DAMAGE_NO; 50 51 activator = self.enemy; 52 53 SUB_UseTargets(); 54 55 if (self.wait > 0) 56 { 57 self.think = multi_wait; 58 self.nextthink = time + self.wait; 59 } 60 else 61 { // we can't just remove (self) here, because this is a touch function 62 // called wheil C code is looping through area links... 63 self.touch = SUB_Null; 64 self.nextthink = time + 0.1; 65 self.think = SUB_Remove; 66 } 67 }; 68 69 void() multi_killed = 70 { 71 self.enemy = damage_attacker; 72 multi_trigger(); 73 }; 74 75 void() multi_use = 76 { 77 self.enemy = activator; 78 multi_trigger(); 79 }; 80 81 void() multi_touch = 82 { 83 if (other.classname != "player") 84 return; 85 86 // if the trigger has an angles field, check player's facing direction 87 if (self.movedir != '0 0 0') 88 { 89 makevectors (other.angles); 90 if (v_forward * self.movedir < 0) 91 return; // not facing the right way 92 } 93 94 self.enemy = other; 95 multi_trigger (); 96 }; 97 98 /*QUAKED trigger_multiple (.5 .5 .5) ? notouch 99 Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time. 100 If "delay" is set, the trigger waits some time after activating before firing. 101 "wait" : Seconds between triggerings. (.2 default) 102 If notouch is set, the trigger is only fired by other entities, not by touching. 103 NOTOUCH has been obsoleted by trigger_relay! 104 sounds 105 1) secret 106 2) beep beep 107 3) large switch 108 4) 109 set "message" to text string 110 */ 111 void() trigger_multiple = 112 { 113 if (self.sounds == 1) 114 { 115 precache_sound ("misc/secret.wav"); 116 self.noise = "misc/secret.wav"; 117 } 118 else if (self.sounds == 2) 119 { 120 precache_sound ("misc/talk.wav"); 121 self.noise = "misc/talk.wav"; 122 } 123 else if (self.sounds == 3) 124 { 125 precache_sound ("misc/trigger1.wav"); 126 self.noise = "misc/trigger1.wav"; 127 } 128 129 if (!self.wait) 130 self.wait = 0.2; 131 self.use = multi_use; 132 133 InitTrigger (); 134 135 if (self.health) 136 { 137 if (self.spawnflags & SPAWNFLAG_NOTOUCH) 138 objerror ("health and notouch don't make sense\n"); 139 self.max_health = self.health; 140 self.th_die = multi_killed; 141 self.takedamage = DAMAGE_YES; 142 self.solid = SOLID_BBOX; 143 setorigin (self, self.origin); // make sure it links into the world 144 } 145 else 146 { 147 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) ) 148 { 149 self.touch = multi_touch; 150 } 151 } 152 }; 153 154 155 /*QUAKED trigger_once (.5 .5 .5) ? notouch 156 Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching 157 "targetname". If "health" is set, the trigger must be killed to activate. 158 If notouch is set, the trigger is only fired by other entities, not by touching. 159 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired. 160 if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0. 161 sounds 162 1) secret 163 2) beep beep 164 3) large switch 165 4) 166 set "message" to text string 167 */ 168 void() trigger_once = 169 { 170 self.wait = -1; 171 trigger_multiple(); 172 }; 173 174 //============================================================================= 175 176 /*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) 177 This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages. 178 */ 179 void() trigger_relay = 180 { 181 self.use = SUB_UseTargets; 182 }; 183 184 185 //============================================================================= 186 187 /*QUAKED trigger_secret (.5 .5 .5) ? 188 secret counter trigger 189 sounds 190 1) secret 191 2) beep beep 192 3) 193 4) 194 set "message" to text string 195 */ 196 void() trigger_secret = 197 { 198 total_secrets = total_secrets + 1; 199 self.wait = -1; 200 if (!self.message) 201 self.message = "You found a secret area!"; 202 if (!self.sounds) 203 self.sounds = 1; 204 205 if (self.sounds == 1) 206 { 207 precache_sound ("misc/secret.wav"); 208 self.noise = "misc/secret.wav"; 209 } 210 else if (self.sounds == 2) 211 { 212 precache_sound ("misc/talk.wav"); 213 self.noise = "misc/talk.wav"; 214 } 215 216 trigger_multiple (); 217 }; 218 219 //============================================================================= 220 221 222 void() counter_use = 223 { 224 local string junk; 225 226 self.count = self.count - 1; 227 if (self.count < 0) 228 return; 229 230 if (self.count != 0) 231 { 232 if (activator.classname == "player" 233 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0) 234 { 235 if (self.count >= 4) 236 centerprint (activator, "There are more to go..."); 237 else if (self.count == 3) 238 centerprint (activator, "Only 3 more to go..."); 239 else if (self.count == 2) 240 centerprint (activator, "Only 2 more to go..."); 241 else 242 centerprint (activator, "Only 1 more to go..."); 243 } 244 return; 245 } 246 247 if (activator.classname == "player" 248 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0) 249 centerprint(activator, "Sequence completed!"); 250 self.enemy = activator; 251 multi_trigger (); 252 }; 253 254 /*QUAKED trigger_counter (.5 .5 .5) ? nomessage 255 Acts as an intermediary for an action that takes multiple inputs. 256 257 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished. 258 259 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself. 260 */ 261 void() trigger_counter = 262 { 263 self.wait = -1; 264 if (!self.count) 265 self.count = 2; 266 267 self.use = counter_use; 268 }; 269 270 271 /* 272 ============================================================================== 273 274 TELEPORT TRIGGERS 275 276 ============================================================================== 277 */ 278 279 float PLAYER_ONLY = 1; 280 float SILENT = 2; 281 282 void() play_teleport = 283 { 284 local float v; 285 local string tmpstr; 286 287 v = random() * 5; 288 if (v < 1) 289 tmpstr = "misc/r_tele1.wav"; 290 else if (v < 2) 291 tmpstr = "misc/r_tele2.wav"; 292 else if (v < 3) 293 tmpstr = "misc/r_tele3.wav"; 294 else if (v < 4) 295 tmpstr = "misc/r_tele4.wav"; 296 else 297 tmpstr = "misc/r_tele5.wav"; 298 299 sound (self, CHAN_VOICE, tmpstr, 1, ATTN_NORM); 300 remove (self); 301 }; 302 303 void(vector org) spawn_tfog = 304 { 305 s = spawn (); 306 s.origin = org; 307 s.nextthink = time + 0.2; 308 s.think = play_teleport; 309 310 WriteByte (MSG_MULTICAST, SVC_TEMPENTITY); 311 WriteByte (MSG_MULTICAST, TE_TELEPORT); 312 WriteCoord (MSG_MULTICAST, org_x); 313 WriteCoord (MSG_MULTICAST, org_y); 314 WriteCoord (MSG_MULTICAST, org_z); 315 multicast (org, MULTICAST_PHS); 316 }; 317 318 319 void() tdeath_touch = 320 { 321 local entity other2; 322 323 if (other == self.owner) 324 return; 325 326 // frag anyone who teleports in on top of an invincible player 327 if (other.classname == "player") 328 { 329 if (other.invincible_finished > time && 330 self.owner.invincible_finished > time) { 331 self.classname = "teledeath3"; 332 other.invincible_finished = 0; 333 self.owner.invincible_finished = 0; 334 T_Damage (other, self, self, 50000); 335 other2 = self.owner; 336 self.owner = other; 337 T_Damage (other2, self, self, 50000); 338 } 339 340 if (other.invincible_finished > time) 341 { 342 self.classname = "teledeath2"; 343 T_Damage (self.owner, self, self, 50000); 344 return; 345 } 346 347 } 348 349 if (other.health) 350 { 351 T_Damage (other, self, self, 50000); 352 } 353 }; 354 355 356 void(vector org, entity death_owner) spawn_tdeath = 357 { 358 local entity death; 359 360 death = spawn(); 361 death.classname = "teledeath"; 362 death.movetype = MOVETYPE_NONE; 363 death.solid = SOLID_TRIGGER; 364 death.angles = '0 0 0'; 365 setsize (death, death_owner.mins - '1 1 1', death_owner.maxs + '1 1 1'); 366 setorigin (death, org); 367 death.touch = tdeath_touch; 368 death.nextthink = time + 0.2; 369 death.think = SUB_Remove; 370 death.owner = death_owner; 371 372 force_retouch = 2; // make sure even still objects get hit 373 }; 374 375 void() teleport_touch = 376 { 377 local entity t; 378 local vector org; 379 380 if (self.targetname) 381 { 382 if (self.nextthink < time) 383 { 384 return; // not fired yet 385 } 386 } 387 388 if (self.spawnflags & PLAYER_ONLY) 389 { 390 if (other.classname != "player") 391 return; 392 } 393 394 // only teleport living creatures 395 if (other.health <= 0 || other.solid != SOLID_SLIDEBOX) 396 return; 397 398 SUB_UseTargets (); 399 400 // put a tfog where the player was 401 spawn_tfog (other.origin); 402 403 t = find (world, targetname, self.target); 404 if (!t) 405 objerror ("couldn't find target"); 406 407 // spawn a tfog flash in front of the destination 408 makevectors (t.mangle); 409 org = t.origin + 32 * v_forward; 410 411 spawn_tfog (org); 412 spawn_tdeath(t.origin, other); 413 414 // move the player and lock him down for a little while 415 if (!other.health) 416 { 417 other.origin = t.origin; 418 other.velocity = (v_forward * other.velocity_x) + (v_forward * other.velocity_y); 419 return; 420 } 421 422 setorigin (other, t.origin); 423 other.angles = t.mangle; 424 if (other.classname == "player") 425 { 426 other.fixangle = 1; // turn this way immediately 427 other.teleport_time = time + 0.7; 428 if (other.flags & FL_ONGROUND) 429 other.flags = other.flags - FL_ONGROUND; 430 other.velocity = v_forward * 300; 431 } 432 other.flags = other.flags - other.flags & FL_ONGROUND; 433 }; 434 435 /*QUAKED info_teleport_destination (.5 .5 .5) (-8 -8 -8) (8 8 32) 436 This is the destination marker for a teleporter. It should have a "targetname" field with the same value as a teleporter's "target" field. 437 */ 438 void() info_teleport_destination = 439 { 440 // this does nothing, just serves as a target spot 441 self.mangle = self.angles; 442 self.angles = '0 0 0'; 443 self.model = ""; 444 self.origin = self.origin + '0 0 27'; 445 if (!self.targetname) 446 objerror ("no targetname"); 447 }; 448 449 void() teleport_use = 450 { 451 self.nextthink = time + 0.2; 452 force_retouch = 2; // make sure even still objects get hit 453 self.think = SUB_Null; 454 }; 455 456 /*QUAKED trigger_teleport (.5 .5 .5) ? PLAYER_ONLY SILENT 457 Any object touching this will be transported to the corresponding info_teleport_destination entity. You must set the "target" field, and create an object with a "targetname" field that matches. 458 459 If the trigger_teleport has a targetname, it will only teleport entities when it has been fired. 460 */ 461 void() trigger_teleport = 462 { 463 local vector o; 464 465 InitTrigger (); 466 self.touch = teleport_touch; 467 // find the destination 468 if (!self.target) 469 objerror ("no target"); 470 self.use = teleport_use; 471 472 if (!(self.spawnflags & SILENT)) 473 { 474 precache_sound ("ambience/hum1.wav"); 475 o = (self.mins + self.maxs)*0.5; 476 ambientsound (o, "ambience/hum1.wav",0.5 , ATTN_STATIC); 477 } 478 }; 479 480 /* 481 ============================================================================== 482 483 trigger_setskill 484 485 ============================================================================== 486 */ 487 488 /*QUAKED trigger_setskill (.5 .5 .5) ? 489 sets skill level to the value of "message". 490 Only used on start map. 491 */ 492 void() trigger_setskill = 493 { 494 remove (self); 495 }; 496 497 498 /* 499 ============================================================================== 500 501 ONLY REGISTERED TRIGGERS 502 503 ============================================================================== 504 */ 505 506 void() trigger_onlyregistered_touch = 507 { 508 if (other.classname != "player") 509 return; 510 if (self.attack_finished > time) 511 return; 512 513 self.attack_finished = time + 2; 514 if (cvar("registered")) 515 { 516 self.message = ""; 517 SUB_UseTargets (); 518 remove (self); 519 } 520 else 521 { 522 if (self.message != "") 523 { 524 centerprint (other, self.message); 525 sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); 526 } 527 } 528 }; 529 530 /*QUAKED trigger_onlyregistered (.5 .5 .5) ? 531 Only fires if playing the registered version, otherwise prints the message 532 */ 533 void() trigger_onlyregistered = 534 { 535 precache_sound ("misc/talk.wav"); 536 InitTrigger (); 537 self.touch = trigger_onlyregistered_touch; 538 }; 539 540 //============================================================================ 541 542 void() hurt_on = 543 { 544 self.solid = SOLID_TRIGGER; 545 self.nextthink = -1; 546 }; 547 548 void() hurt_touch = 549 { 550 if (other.takedamage) 551 { 552 self.solid = SOLID_NOT; 553 T_Damage (other, self, self, self.dmg); 554 self.think = hurt_on; 555 self.nextthink = time + 1; 556 } 557 558 return; 559 }; 560 561 /*QUAKED trigger_hurt (.5 .5 .5) ? 562 Any object touching this will be hurt 563 set dmg to damage amount 564 defalt dmg = 5 565 */ 566 void() trigger_hurt = 567 { 568 InitTrigger (); 569 self.touch = hurt_touch; 570 if (!self.dmg) 571 self.dmg = 5; 572 }; 573 574 //============================================================================ 575 576 float PUSH_ONCE = 1; 577 578 void() trigger_push_touch = 579 { 580 if (other.classname == "grenade") 581 other.velocity = self.speed * self.movedir * 10; 582 else if (other.health > 0) 583 { 584 other.velocity = self.speed * self.movedir * 10; 585 if (other.classname == "player") 586 { 587 if (other.fly_sound < time) 588 { 589 other.fly_sound = time + 1.5; 590 sound (other, CHAN_AUTO, "ambience/windfly.wav", 1, ATTN_NORM); 591 } 592 } 593 } 594 if (self.spawnflags & PUSH_ONCE) 595 remove(self); 596 }; 597 598 599 /*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE 600 Pushes the player 601 */ 602 void() trigger_push = 603 { 604 InitTrigger (); 605 precache_sound ("ambience/windfly.wav"); 606 self.touch = trigger_push_touch; 607 if (!self.speed) 608 self.speed = 1000; 609 }; 610 611 //============================================================================ 612 613 void() trigger_monsterjump_touch = 614 { 615 if ( other.flags & (FL_MONSTER | FL_FLY | FL_SWIM) != FL_MONSTER ) 616 return; 617 618 // set XY even if not on ground, so the jump will clear lips 619 other.velocity_x = self.movedir_x * self.speed; 620 other.velocity_y = self.movedir_y * self.speed; 621 622 if ( !(other.flags & FL_ONGROUND) ) 623 return; 624 625 other.flags = other.flags - FL_ONGROUND; 626 627 other.velocity_z = self.height; 628 }; 629 630 /*QUAKED trigger_monsterjump (.5 .5 .5) ? 631 Walking monsters that touch this will jump in the direction of the trigger's angle 632 "speed" default to 200, the speed thrown forward 633 "height" default to 200, the speed thrown upwards 634 */ 635 void() trigger_monsterjump = 636 { 637 if (!self.speed) 638 self.speed = 200; 639 if (!self.height) 640 self.height = 200; 641 if (self.angles == '0 0 0') 642 self.angles = '0 360 0'; 643 InitTrigger (); 644 self.touch = trigger_monsterjump_touch; 645 }; 646 647