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