1 2float DOOR_START_OPEN = 1; 3float DOOR_DONT_LINK = 4; 4float DOOR_GOLD_KEY = 8; 5float DOOR_SILVER_KEY = 16; 6float DOOR_TOGGLE = 32; 7 8/* 9 10Doors are similar to buttons, but can spawn a fat trigger field around them 11to open without a touch, and they link together to form simultanious 12double/quad doors. 13 14Door.owner is the master door. If there is only one door, it points to itself. 15If multiple doors, all will point to a single one. 16 17Door.enemy chains from the master door through all doors linked in the chain. 18 19*/ 20 21/* 22============================================================================= 23 24THINK FUNCTIONS 25 26============================================================================= 27*/ 28 29void() door_go_down; 30void() door_go_up; 31 32void() door_blocked = 33{ 34 other.deathtype = "squish"; 35 T_Damage (other, self, self.goalentity, self.dmg); 36 37// if a door has a negative wait, it would never come back if blocked, 38// so let it just squash the object to death real fast 39 if (self.wait >= 0) 40 { 41 if (self.state == STATE_DOWN) 42 door_go_up (); 43 else 44 door_go_down (); 45 } 46}; 47 48 49void() door_hit_top = 50{ 51 sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self.noise1, 1, ATTN_NORM); 52 self.state = STATE_TOP; 53 if (self.spawnflags & DOOR_TOGGLE) 54 return; // don't come down automatically 55 self.think = door_go_down; 56 self.nextthink = self.ltime + self.wait; 57}; 58 59void() door_hit_bottom = 60{ 61 sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self.noise1, 1, ATTN_NORM); 62 self.state = STATE_BOTTOM; 63}; 64 65void() door_go_down = 66{ 67 sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); 68 if (self.max_health) 69 { 70 self.takedamage = DAMAGE_YES; 71 self.health = self.max_health; 72 } 73 74 self.state = STATE_DOWN; 75 SUB_CalcMove (self.pos1, self.speed, door_hit_bottom); 76}; 77 78void() door_go_up = 79{ 80 if (self.state == STATE_UP) 81 return; // allready going up 82 83 if (self.state == STATE_TOP) 84 { // reset top wait time 85 self.nextthink = self.ltime + self.wait; 86 return; 87 } 88 89 sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); 90 self.state = STATE_UP; 91 SUB_CalcMove (self.pos2, self.speed, door_hit_top); 92 93 SUB_UseTargets(); 94}; 95 96 97/* 98============================================================================= 99 100ACTIVATION FUNCTIONS 101 102============================================================================= 103*/ 104 105void() door_fire = 106{ 107 local entity oself; 108 local entity starte; 109 110 if (self.owner != self) 111 objerror ("door_fire: self.owner != self"); 112 113// play use key sound 114 115 if (self.items) 116 sound (self, CHAN_VOICE, self.noise4, 1, ATTN_NORM); 117 118 self.message = string_null; // no more message 119 oself = self; 120 121 if (self.spawnflags & DOOR_TOGGLE) 122 { 123 if (self.state == STATE_UP || self.state == STATE_TOP) 124 { 125 starte = self; 126 do 127 { 128 door_go_down (); 129 self = self.enemy; 130 } while ( (self != starte) && (self != world) ); 131 self = oself; 132 return; 133 } 134 } 135 136// trigger all paired doors 137 starte = self; 138 139 do 140 { 141 self.goalentity = activator; // Who fired us 142 door_go_up (); 143 self = self.enemy; 144 } while ( (self != starte) && (self != world) ); 145 self = oself; 146}; 147 148 149void() door_use = 150{ 151 local entity oself; 152 153 self.message = ""; // door message are for touch only 154 self.owner.message = ""; 155 self.enemy.message = ""; 156 157 oself = self; 158 self = self.owner; 159 door_fire (); 160 self = oself; 161}; 162 163 164void() door_trigger_touch = 165{ 166 if (other.health <= 0) 167 return; 168 169 if (time < self.attack_finished) 170 return; 171 self.attack_finished = time + 1; 172 173 activator = other; 174 175 self = self.owner; 176 door_use (); 177}; 178 179 180void() door_killed = 181{ 182 local entity oself; 183 184 oself = self; 185 self = self.owner; 186 self.health = self.max_health; 187 self.takedamage = DAMAGE_NO; // wil be reset upon return 188 door_use (); 189 self = oself; 190}; 191 192 193/* 194================ 195door_touch 196 197Prints messages and opens key doors 198================ 199*/ 200void() door_touch = 201{ 202 if (other.classname != "player") 203 return; 204 if (self.owner.attack_finished > time) 205 return; 206 207 self.owner.attack_finished = time + 2; 208 209 if (self.owner.message != "") 210 { 211 centerprint (other, self.owner.message); 212 sound (other, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); 213 } 214 215// key door stuff 216 if (!self.items) 217 return; 218 219// FIXME: blink key on player's status bar 220 if ( (self.items & other.items) != self.items ) 221 { 222 if (self.owner.items == IT_KEY1) 223 { 224 if (world.worldtype == 2) 225 { 226 centerprint (other, "You need the silver keycard"); 227 sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); 228 } 229 else if (world.worldtype == 1) 230 { 231 centerprint (other, "You need the silver runekey"); 232 sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); 233 } 234 else if (world.worldtype == 0) 235 { 236 centerprint (other, "You need the silver key"); 237 sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); 238 } 239 } 240 else 241 { 242 if (world.worldtype == 2) 243 { 244 centerprint (other, "You need the gold keycard"); 245 sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); 246 } 247 else if (world.worldtype == 1) 248 { 249 centerprint (other, "You need the gold runekey"); 250 sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); 251 } 252 else if (world.worldtype == 0) 253 { 254 centerprint (other, "You need the gold key"); 255 sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); 256 } 257 } 258 return; 259 } 260 261 other.items = other.items - self.items; 262 self.touch = SUB_Null; 263 if (self.enemy) 264 self.enemy.touch = SUB_Null; // get paired door 265 door_use (); 266}; 267 268/* 269============================================================================= 270 271SPAWNING FUNCTIONS 272 273============================================================================= 274*/ 275 276 277entity(vector fmins, vector fmaxs) spawn_field = 278{ 279 local entity trigger; 280 local vector t1, t2; 281 282 trigger = spawn(); 283 trigger.movetype = MOVETYPE_NONE; 284 trigger.solid = SOLID_TRIGGER; 285 trigger.owner = self; 286 trigger.touch = door_trigger_touch; 287 288 t1 = fmins; 289 t2 = fmaxs; 290 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8'); 291 return (trigger); 292}; 293 294 295float (entity e1, entity e2) EntitiesTouching = 296{ 297 if (e1.mins_x > e2.maxs_x) 298 return FALSE; 299 if (e1.mins_y > e2.maxs_y) 300 return FALSE; 301 if (e1.mins_z > e2.maxs_z) 302 return FALSE; 303 if (e1.maxs_x < e2.mins_x) 304 return FALSE; 305 if (e1.maxs_y < e2.mins_y) 306 return FALSE; 307 if (e1.maxs_z < e2.mins_z) 308 return FALSE; 309 return TRUE; 310}; 311 312 313/* 314============= 315LinkDoors 316 317 318============= 319*/ 320void() LinkDoors = 321{ 322 local entity t, starte; 323 local vector cmins, cmaxs; 324 325 if (self.enemy) 326 return; // already linked by another door 327 if (self.spawnflags & 4) 328 { 329 self.owner = self.enemy = self; 330 return; // don't want to link this door 331 } 332 333 cmins = self.mins; 334 cmaxs = self.maxs; 335 336 starte = self; 337 t = self; 338 339 do 340 { 341 self.owner = starte; // master door 342 343 if (self.health) 344 starte.health = self.health; 345 if (self.targetname) 346 starte.targetname = self.targetname; 347 if (self.message != "") 348 starte.message = self.message; 349 350 t = find (t, classname, self.classname); 351 if (!t) 352 { 353 self.enemy = starte; // make the chain a loop 354 355 // shootable, fired, or key doors just needed the owner/enemy links, 356 // they don't spawn a field 357 358 self = self.owner; 359 360 if (self.health) 361 return; 362 if (self.targetname) 363 return; 364 if (self.items) 365 return; 366 367 self.owner.trigger_field = spawn_field(cmins, cmaxs); 368 369 return; 370 } 371 372 if (EntitiesTouching(self,t)) 373 { 374 if (t.enemy) 375 objerror ("cross connected doors"); 376 377 self.enemy = t; 378 self = t; 379 380 if (t.mins_x < cmins_x) 381 cmins_x = t.mins_x; 382 if (t.mins_y < cmins_y) 383 cmins_y = t.mins_y; 384 if (t.mins_z < cmins_z) 385 cmins_z = t.mins_z; 386 if (t.maxs_x > cmaxs_x) 387 cmaxs_x = t.maxs_x; 388 if (t.maxs_y > cmaxs_y) 389 cmaxs_y = t.maxs_y; 390 if (t.maxs_z > cmaxs_z) 391 cmaxs_z = t.maxs_z; 392 } 393 } while (1 ); 394 395}; 396 397 398/*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE 399if two doors touch, they are assumed to be connected and operate as a unit. 400 401TOGGLE causes the door to wait in both the start and end states for a trigger event. 402 403START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). 404 405Key doors are allways wait -1. 406 407"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet 408"angle" determines the opening direction 409"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. 410"health" if set, door must be shot open 411"speed" movement speed (100 default) 412"wait" wait before returning (3 default, -1 = never return) 413"lip" lip remaining at end of move (8 default) 414"dmg" damage to inflict when blocked (2 default) 415"sounds" 4160) no sound 4171) stone 4182) base 4193) stone chain 4204) screechy metal 421*/ 422 423void() func_door = 424 425{ 426 427 if (world.worldtype == 0) 428 { 429 precache_sound ("doors/medtry.wav"); 430 precache_sound ("doors/meduse.wav"); 431 self.noise3 = "doors/medtry.wav"; 432 self.noise4 = "doors/meduse.wav"; 433 } 434 else if (world.worldtype == 1) 435 { 436 precache_sound ("doors/runetry.wav"); 437 precache_sound ("doors/runeuse.wav"); 438 self.noise3 = "doors/runetry.wav"; 439 self.noise4 = "doors/runeuse.wav"; 440 } 441 else if (world.worldtype == 2) 442 { 443 precache_sound ("doors/basetry.wav"); 444 precache_sound ("doors/baseuse.wav"); 445 self.noise3 = "doors/basetry.wav"; 446 self.noise4 = "doors/baseuse.wav"; 447 } 448 else 449 { 450 dprint ("no worldtype set!\n"); 451 } 452 if (self.sounds == 0) 453 { 454 precache_sound ("misc/null.wav"); 455 precache_sound ("misc/null.wav"); 456 self.noise1 = "misc/null.wav"; 457 self.noise2 = "misc/null.wav"; 458 } 459 if (self.sounds == 1) 460 { 461 precache_sound ("doors/drclos4.wav"); 462 precache_sound ("doors/doormv1.wav"); 463 self.noise1 = "doors/drclos4.wav"; 464 self.noise2 = "doors/doormv1.wav"; 465 } 466 if (self.sounds == 2) 467 { 468 precache_sound ("doors/hydro1.wav"); 469 precache_sound ("doors/hydro2.wav"); 470 self.noise2 = "doors/hydro1.wav"; 471 self.noise1 = "doors/hydro2.wav"; 472 } 473 if (self.sounds == 3) 474 { 475 precache_sound ("doors/stndr1.wav"); 476 precache_sound ("doors/stndr2.wav"); 477 self.noise2 = "doors/stndr1.wav"; 478 self.noise1 = "doors/stndr2.wav"; 479 } 480 if (self.sounds == 4) 481 { 482 precache_sound ("doors/ddoor1.wav"); 483 precache_sound ("doors/ddoor2.wav"); 484 self.noise1 = "doors/ddoor2.wav"; 485 self.noise2 = "doors/ddoor1.wav"; 486 } 487 488 489 SetMovedir (); 490 491 self.max_health = self.health; 492 self.solid = SOLID_BSP; 493 self.movetype = MOVETYPE_PUSH; 494 setorigin (self, self.origin); 495 setmodel (self, self.model); 496 self.classname = "door"; 497 498 self.blocked = door_blocked; 499 self.use = door_use; 500 501 if (self.spawnflags & DOOR_SILVER_KEY) 502 self.items = IT_KEY1; 503 if (self.spawnflags & DOOR_GOLD_KEY) 504 self.items = IT_KEY2; 505 506 if (!self.speed) 507 self.speed = 100; 508 if (!self.wait) 509 self.wait = 3; 510 if (!self.lip) 511 self.lip = 8; 512 if (!self.dmg) 513 self.dmg = 2; 514 515 self.pos1 = self.origin; 516 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip); 517 518// DOOR_START_OPEN is to allow an entity to be lighted in the closed position 519// but spawn in the open position 520 if (self.spawnflags & DOOR_START_OPEN) 521 { 522 setorigin (self, self.pos2); 523 self.pos2 = self.pos1; 524 self.pos1 = self.origin; 525 } 526 527 self.state = STATE_BOTTOM; 528 529 if (self.health) 530 { 531 self.takedamage = DAMAGE_YES; 532 self.th_die = door_killed; 533 } 534 535 if (self.items) 536 self.wait = -1; 537 538 self.touch = door_touch; 539 540// LinkDoors can't be done until all of the doors have been spawned, so 541// the sizes can be detected properly. 542 self.think = LinkDoors; 543 self.nextthink = self.ltime + 0.1; 544}; 545 546/* 547============================================================================= 548 549SECRET DOORS 550 551============================================================================= 552*/ 553 554void() fd_secret_move1; 555void() fd_secret_move2; 556void() fd_secret_move3; 557void() fd_secret_move4; 558void() fd_secret_move5; 559void() fd_secret_move6; 560void() fd_secret_done; 561 562float SECRET_OPEN_ONCE = 1; // stays open 563float SECRET_1ST_LEFT = 2; // 1st move is left of arrow 564float SECRET_1ST_DOWN = 4; // 1st move is down from arrow 565float SECRET_NO_SHOOT = 8; // only opened by trigger 566float SECRET_YES_SHOOT = 16; // shootable even if targeted 567 568 569void () fd_secret_use = 570{ 571 local float temp; 572 573 self.health = 10000; 574 575 // exit if still moving around... 576 if (self.origin != self.oldorigin) 577 return; 578 579 self.message = string_null; // no more message 580 581 SUB_UseTargets(); // fire all targets / killtargets 582 583 if (!(self.spawnflags & SECRET_NO_SHOOT)) 584 { 585 self.th_pain = SUB_Null; 586 self.takedamage = DAMAGE_NO; 587 } 588 self.velocity = '0 0 0'; 589 590 // Make a sound, wait a little... 591 592 sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); 593 self.nextthink = self.ltime + 0.1; 594 595 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1 596 makevectors(self.mangle); 597 598 if (!self.t_width) 599 { 600 if (self.spawnflags & SECRET_1ST_DOWN) 601 self. t_width = fabs(v_up * self.size); 602 else 603 self. t_width = fabs(v_right * self.size); 604 } 605 606 if (!self.t_length) 607 self. t_length = fabs(v_forward * self.size); 608 609 if (self.spawnflags & SECRET_1ST_DOWN) 610 self.dest1 = self.origin - v_up * self.t_width; 611 else 612 self.dest1 = self.origin + v_right * (self.t_width * temp); 613 614 self.dest2 = self.dest1 + v_forward * self.t_length; 615 SUB_CalcMove(self.dest1, self.speed, fd_secret_move1); 616 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); 617}; 618 619// Wait after first movement... 620void () fd_secret_move1 = 621{ 622 self.nextthink = self.ltime + 1.0; 623 self.think = fd_secret_move2; 624 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); 625}; 626 627// Start moving sideways w/sound... 628void () fd_secret_move2 = 629{ 630 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); 631 SUB_CalcMove(self.dest2, self.speed, fd_secret_move3); 632}; 633 634// Wait here until time to go back... 635void () fd_secret_move3 = 636{ 637 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); 638 if (!(self.spawnflags & SECRET_OPEN_ONCE)) 639 { 640 self.nextthink = self.ltime + self.wait; 641 self.think = fd_secret_move4; 642 } 643}; 644 645// Move backward... 646void () fd_secret_move4 = 647{ 648 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); 649 SUB_CalcMove(self.dest1, self.speed, fd_secret_move5); 650}; 651 652// Wait 1 second... 653void () fd_secret_move5 = 654{ 655 self.nextthink = self.ltime + 1.0; 656 self.think = fd_secret_move6; 657 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); 658}; 659 660void () fd_secret_move6 = 661{ 662 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); 663 SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done); 664}; 665 666void () fd_secret_done = 667{ 668 if (!self.targetname || self.spawnflags&SECRET_YES_SHOOT) 669 { 670 self.health = 10000; 671 self.takedamage = DAMAGE_YES; 672 self.th_pain = fd_secret_use; 673 self.th_die = fd_secret_use; 674 } 675 sound(self, CHAN_NO_PHS_ADD+CHAN_VOICE, self.noise3, 1, ATTN_NORM); 676}; 677 678void () secret_blocked = 679{ 680 if (time < self.attack_finished) 681 return; 682 self.attack_finished = time + 0.5; 683 other.deathtype = "squish"; 684 T_Damage (other, self, self, self.dmg); 685}; 686 687/* 688================ 689secret_touch 690 691Prints messages 692================ 693*/ 694void() secret_touch = 695{ 696 if (other.classname != "player") 697 return; 698 if (self.attack_finished > time) 699 return; 700 701 self.attack_finished = time + 2; 702 703 if (self.message) 704 { 705 centerprint (other, self.message); 706 sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); 707 } 708}; 709 710 711/*QUAKED func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot 712Basic secret door. Slides back, then to the side. Angle determines direction. 713wait = # of seconds before coming back 7141st_left = 1st move is left of arrow 7151st_down = 1st move is down from arrow 716always_shoot = even if targeted, keep shootable 717t_width = override WIDTH to move back (or height if going down) 718t_length = override LENGTH to move sideways 719"dmg" damage to inflict when blocked (2 default) 720 721If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage. 722"sounds" 7231) medieval 7242) metal 7253) base 726*/ 727 728void () func_door_secret = 729{ 730 if (self.sounds == 0) 731 self.sounds = 3; 732 if (self.sounds == 1) 733 { 734 precache_sound ("doors/latch2.wav"); 735 precache_sound ("doors/winch2.wav"); 736 precache_sound ("doors/drclos4.wav"); 737 self.noise1 = "doors/latch2.wav"; 738 self.noise2 = "doors/winch2.wav"; 739 self.noise3 = "doors/drclos4.wav"; 740 } 741 if (self.sounds == 2) 742 { 743 precache_sound ("doors/airdoor1.wav"); 744 precache_sound ("doors/airdoor2.wav"); 745 self.noise2 = "doors/airdoor1.wav"; 746 self.noise1 = "doors/airdoor2.wav"; 747 self.noise3 = "doors/airdoor2.wav"; 748 } 749 if (self.sounds == 3) 750 { 751 precache_sound ("doors/basesec1.wav"); 752 precache_sound ("doors/basesec2.wav"); 753 self.noise2 = "doors/basesec1.wav"; 754 self.noise1 = "doors/basesec2.wav"; 755 self.noise3 = "doors/basesec2.wav"; 756 } 757 758 if (!self.dmg) 759 self.dmg = 2; 760 761 // Magic formula... 762 self.mangle = self.angles; 763 self.angles = '0 0 0'; 764 self.solid = SOLID_BSP; 765 self.movetype = MOVETYPE_PUSH; 766 self.classname = "door"; 767 setmodel (self, self.model); 768 setorigin (self, self.origin); 769 770 self.touch = secret_touch; 771 self.blocked = secret_blocked; 772 self.speed = 50; 773 self.use = fd_secret_use; 774 if ( !self.targetname || self.spawnflags&SECRET_YES_SHOOT) 775 { 776 self.health = 10000; 777 self.takedamage = DAMAGE_YES; 778 self.th_pain = fd_secret_use; 779 } 780 self.oldorigin = self.origin; 781 if (!self.wait) 782 self.wait = 5; // 5 seconds before closing 783}; 784