]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/triggers/func/door.qc
8ce8e1fd7e2fde198c540c0c2682108683205b4b
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / triggers / func / door.qc
1 /*
2
3 Doors are similar to buttons, but can spawn a fat trigger field around them
4 to open without a touch, and they link together to form simultanious
5 double/quad doors.
6
7 Door.owner is the master door.  If there is only one door, it points to itself.
8 If multiple doors, all will point to a single one.
9
10 Door.enemy chains from the master door through all doors linked in the chain.
11
12 */
13
14
15 /*
16 =============================================================================
17
18 THINK FUNCTIONS
19
20 =============================================================================
21 */
22
23 void() door_go_down;
24 void() door_go_up;
25 void() door_rotating_go_down;
26 void() door_rotating_go_up;
27
28 void door_blocked()
29 {
30         if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO))
31         { // KIll Kill Kill!!
32 #ifdef SVQC
33                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
34 #endif
35         }
36         else
37         {
38 #ifdef SVQC
39                 if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
40                         Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
41 #endif
42
43                  //Dont chamge direction for dead or dying stuff
44                 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
45                 {
46                         if (self.wait >= 0)
47                         {
48                                 if (self.state == STATE_DOWN)
49                         if (self.classname == "door")
50                         {
51                                 door_go_up ();
52                         } else
53                         {
54                                 door_rotating_go_up ();
55                         }
56                                 else
57                         if (self.classname == "door")
58                         {
59                                 door_go_down ();
60                         } else
61                         {
62                                 door_rotating_go_down ();
63                         }
64                         }
65                 }
66 #ifdef SVQC
67                 else
68                 {
69                         //gib dying stuff just to make sure
70                         if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
71                                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
72                 }
73 #endif
74         }
75 }
76
77 void door_hit_top()
78 {
79         if (self.noise1 != "")
80                 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
81         self.state = STATE_TOP;
82         if (self.spawnflags & DOOR_TOGGLE)
83                 return;         // don't come down automatically
84         if (self.classname == "door")
85         {
86                 self.think = door_go_down;
87         } else
88         {
89                 self.think = door_rotating_go_down;
90         }
91         self.nextthink = self.ltime + self.wait;
92 }
93
94 void door_hit_bottom()
95 {
96         if (self.noise1 != "")
97                 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
98         self.state = STATE_BOTTOM;
99 }
100
101 void door_go_down()
102 {
103         if (self.noise2 != "")
104                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
105         if (self.max_health)
106         {
107                 self.takedamage = DAMAGE_YES;
108                 self.health = self.max_health;
109         }
110         print(
111 #ifdef SVQC
112         "Server ",
113 #elif defined(CSQC)
114         "Client ",
115 #endif
116         "going down at time ", ftos(time), "\n");
117
118         self.state = STATE_DOWN;
119         SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
120 }
121
122 void door_go_up()
123 {
124         if (self.state == STATE_UP)
125                 return;         // already going up
126
127         if (self.state == STATE_TOP)
128         {       // reset top wait time
129                 self.nextthink = self.ltime + self.wait;
130                 return;
131         }
132
133         if (self.noise2 != "")
134                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
135         self.state = STATE_UP;
136         SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
137
138         string oldmessage;
139         oldmessage = self.message;
140         self.message = "";
141         SUB_UseTargets();
142         self.message = oldmessage;
143 }
144
145
146 /*
147 =============================================================================
148
149 ACTIVATION FUNCTIONS
150
151 =============================================================================
152 */
153
154 float door_check_keys(void)
155 {
156         local entity door;
157
158
159         if (self.owner)
160                 door = self.owner;
161         else
162                 door = self;
163
164         // no key needed
165         if (!door.itemkeys)
166                 return TRUE;
167
168         // this door require a key
169         // only a player can have a key
170         if (!IS_PLAYER(other))
171                 return FALSE;
172
173 #ifdef SVQC
174         if (item_keys_usekey(door, other))
175         {
176                 // some keys were used
177                 if (other.key_door_messagetime <= time)
178                 {
179
180                         play2(other, "misc/talk.wav");
181                         Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
182                         other.key_door_messagetime = time + 2;
183                 }
184         }
185         else
186         {
187                 // no keys were used
188                 if (other.key_door_messagetime <= time)
189                 {
190                         play2(other, "misc/talk.wav");
191                         Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
192
193                         other.key_door_messagetime = time + 2;
194                 }
195         }
196 #endif
197
198         if (door.itemkeys)
199         {
200 #ifdef SVQC
201                 // door is now unlocked
202                 play2(other, "misc/talk.wav");
203                 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
204 #endif
205                 return TRUE;
206         }
207         else
208                 return FALSE;
209 }
210
211 void door_fire()
212 {
213         entity  oself;
214         entity  starte;
215
216         if (self.owner != self)
217                 objerror ("door_fire: self.owner != self");
218
219         oself = self;
220
221         if (self.spawnflags & DOOR_TOGGLE)
222         {
223                 if (self.state == STATE_UP || self.state == STATE_TOP)
224                 {
225                         starte = self;
226                         do
227                         {
228                                 if (self.classname == "door")
229                                 {
230                                         door_go_down ();
231                                 }
232                                 else
233                                 {
234                                         door_rotating_go_down ();
235                                 }
236                                 self = self.enemy;
237                         } while ( (self != starte) && (self != world) );
238                         self = oself;
239                         return;
240                 }
241         }
242
243 // trigger all paired doors
244         starte = self;
245         do
246         {
247                 if (self.classname == "door")
248                 {
249                         door_go_up ();
250                 } else
251                 {
252                         // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
253                         if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
254                         {
255                                 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
256                                 self.pos2 = '0 0 0' - self.pos2;
257                         }
258                         // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
259                         if (!((self.spawnflags & 2) &&  (self.spawnflags & 8) && self.state == STATE_DOWN
260                                 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
261                         {
262                                 door_rotating_go_up ();
263                         }
264                 }
265                 self = self.enemy;
266         } while ( (self != starte) && (self != world) );
267         self = oself;
268 }
269
270 void door_use()
271 {
272         entity oself;
273
274         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
275
276         if (self.owner)
277         {
278                 oself = self;
279                 self = self.owner;
280                 door_fire ();
281                 self = oself;
282         }
283 }
284
285 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
286 {
287         entity oself;
288         if(self.spawnflags & DOOR_NOSPLASH)
289                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
290                         return;
291         self.health = self.health - damage;
292
293         if (self.itemkeys)
294         {
295                 // don't allow opening doors through damage if keys are required
296                 return;
297         }
298
299         if (self.health <= 0)
300         {
301                 oself = self;
302                 self = self.owner;
303                 self.health = self.max_health;
304                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
305                 door_use ();
306                 self = oself;
307         }
308 }
309
310
311 /*
312 ================
313 door_touch
314
315 Prints messages
316 ================
317 */
318
319 void door_touch()
320 {
321         if (!IS_PLAYER(other))
322                 return;
323         if (self.owner.attack_finished_single > time)
324                 return;
325
326         self.owner.attack_finished_single = time + 2;
327
328 #ifdef SVQC
329         if (!(self.owner.dmg) && (self.owner.message != ""))
330         {
331                 if (IS_CLIENT(other))
332                         centerprint(other, self.owner.message);
333                 play2(other, "misc/talk.wav");
334         }
335 #endif
336 }
337
338 void door_generic_plat_blocked()
339 {
340
341         if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
342 #ifdef SVQC
343                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
344 #endif
345         }
346         else
347         {
348
349 #ifdef SVQC
350                 if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
351                         Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
352 #endif
353
354                  //Dont chamge direction for dead or dying stuff
355                 if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
356                 {
357                         if (self.wait >= 0)
358                         {
359                                 if (self.state == STATE_DOWN)
360                                         door_rotating_go_up ();
361                                 else
362                                         door_rotating_go_down ();
363                         }
364                 }
365 #ifdef SVQC
366                 else
367                 {
368                         //gib dying stuff just to make sure
369                         if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
370                                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
371                 }
372 #endif
373         }
374 }
375
376 void door_rotating_hit_top()
377 {
378         if (self.noise1 != "")
379                 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
380         self.state = STATE_TOP;
381         if (self.spawnflags & DOOR_TOGGLE)
382                 return;         // don't come down automatically
383         self.think = door_rotating_go_down;
384         self.nextthink = self.ltime + self.wait;
385 }
386
387 void door_rotating_hit_bottom()
388 {
389         if (self.noise1 != "")
390                 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
391         if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
392         {
393                 self.pos2 = '0 0 0' - self.pos2;
394                 self.lip = 0;
395         }
396         self.state = STATE_BOTTOM;
397 }
398
399 void door_rotating_go_down()
400 {
401         if (self.noise2 != "")
402                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
403         if (self.max_health)
404         {
405                 self.takedamage = DAMAGE_YES;
406                 self.health = self.max_health;
407         }
408
409         self.state = STATE_DOWN;
410         SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
411 }
412
413 void door_rotating_go_up()
414 {
415         if (self.state == STATE_UP)
416                 return;         // already going up
417
418         if (self.state == STATE_TOP)
419         {       // reset top wait time
420                 self.nextthink = self.ltime + self.wait;
421                 return;
422         }
423         if (self.noise2 != "")
424                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
425         self.state = STATE_UP;
426         SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
427
428         string oldmessage;
429         oldmessage = self.message;
430         self.message = "";
431         SUB_UseTargets();
432         self.message = oldmessage;
433 }
434
435
436 /*
437 =========================================
438 door trigger
439
440 Spawned if a door lacks a real activator
441 =========================================
442 */
443
444 void door_trigger_touch()
445 {
446         if (other.health < 1)
447 #ifdef SVQC
448                 if (!(other.iscreature && !PHYS_DEAD(other)))
449 #elif defined(CSQC)
450                 if(!(IS_CLIENT(other) && !PHYS_DEAD(other)))
451                         return;
452 #endif
453
454         if (time < self.attack_finished_single)
455                 return;
456
457         // check if door is locked
458         if (!door_check_keys())
459                 return;
460
461         self.attack_finished_single = time + 1;
462
463         activator = other;
464
465         self = self.owner;
466         door_use ();
467 }
468
469 #ifdef SVQC
470
471 float door_trigger_send(entity to, float sf)
472 {
473         WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR_TRIGGER);
474
475         WriteShort(MSG_ENTITY, num_for_edict(self.owner));
476         WriteCoord(MSG_ENTITY, self.origin_x);
477         WriteCoord(MSG_ENTITY, self.origin_y);
478         WriteCoord(MSG_ENTITY, self.origin_z);
479
480         WriteCoord(MSG_ENTITY, self.mins_x);
481         WriteCoord(MSG_ENTITY, self.mins_y);
482         WriteCoord(MSG_ENTITY, self.mins_z);
483         WriteCoord(MSG_ENTITY, self.maxs_x);
484         WriteCoord(MSG_ENTITY, self.maxs_y);
485         WriteCoord(MSG_ENTITY, self.maxs_z);
486
487         return TRUE;
488 }
489
490 void door_trigger_link(entity trig)
491 {
492         Net_LinkEntity(trig, FALSE, 0, door_trigger_send);
493 }
494
495 void spawn_field(vector fmins, vector fmaxs)
496 {
497         entity  trigger;
498         vector  t1 = fmins, t2 = fmaxs;
499
500         trigger = spawn();
501         trigger.classname = "doortriggerfield";
502         trigger.movetype = MOVETYPE_NONE;
503         trigger.solid = SOLID_TRIGGER;
504         trigger.owner = self;
505         trigger.touch = door_trigger_touch;
506
507         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
508         door_trigger_link(trigger);
509 }
510
511 #elif defined(CSQC)
512
513 void ent_door_trigger()
514 {
515         float entnum = ReadShort();
516         self.origin_x = ReadCoord();
517         self.origin_y = ReadCoord();
518         self.origin_z = ReadCoord();
519         setorigin(self, self.origin);
520         self.mins_x = ReadCoord();
521         self.mins_y = ReadCoord();
522         self.mins_z = ReadCoord();
523         self.maxs_x = ReadCoord();
524         self.maxs_y = ReadCoord();
525         self.maxs_z = ReadCoord();
526         setsize(self, self.mins, self.maxs);
527
528         self.owner = findfloat(world, sv_entnum, entnum); // if owner doesn't exist, it shouldn't matter much
529         self.classname = "doortriggerfield";
530         self.movetype = MOVETYPE_NONE;
531         self.solid = SOLID_TRIGGER;
532         self.trigger_touch = door_trigger_touch;
533         self.draw = trigger_draw_generic;
534         self.drawmask = MASK_NORMAL;
535         self.move_time = time;
536 }
537
538 #endif
539 #ifdef SVQC
540 /*
541 =============
542 LinkDoors
543
544
545 =============
546 */
547
548 entity LinkDoors_nextent(entity cur, entity near, entity pass)
549 {
550         while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
551         {
552         }
553         return cur;
554 }
555
556 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
557 {
558         float DELTA = 4;
559         if((e1.absmin_x > e2.absmax_x + DELTA)
560         || (e1.absmin_y > e2.absmax_y + DELTA)
561         || (e1.absmin_z > e2.absmax_z + DELTA)
562         || (e2.absmin_x > e1.absmax_x + DELTA)
563         || (e2.absmin_y > e1.absmax_y + DELTA)
564         || (e2.absmin_z > e1.absmax_z + DELTA)
565         ) { return FALSE; }
566         return TRUE;
567 }
568
569 void door_link();
570 void LinkDoors()
571 {
572         entity  t;
573         vector  cmins, cmaxs;
574
575         door_link();
576
577         if (self.enemy)
578                 return;         // already linked by another door
579         if (self.spawnflags & 4)
580         {
581                 self.owner = self.enemy = self;
582
583                 if (self.health)
584                         return;
585                 IFTARGETED
586                         return;
587                 if (self.items)
588                         return;
589                 spawn_field(self.absmin, self.absmax);
590
591                 return;         // don't want to link this door
592         }
593
594         FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
595
596         // set owner, and make a loop of the chain
597         dprint("LinkDoors: linking doors:");
598         for(t = self; ; t = t.enemy)
599         {
600                 dprint(" ", etos(t));
601                 t.owner = self;
602                 if(t.enemy == world)
603                 {
604                         t.enemy = self;
605                         break;
606                 }
607         }
608         dprint("\n");
609
610         // collect health, targetname, message, size
611         cmins = self.absmin;
612         cmaxs = self.absmax;
613         for(t = self; ; t = t.enemy)
614         {
615                 if(t.health && !self.health)
616                         self.health = t.health;
617                 if((t.targetname != "") && (self.targetname == ""))
618                         self.targetname = t.targetname;
619                 if((t.message != "") && (self.message == ""))
620                         self.message = t.message;
621                 if (t.absmin_x < cmins_x)
622                         cmins_x = t.absmin_x;
623                 if (t.absmin_y < cmins_y)
624                         cmins_y = t.absmin_y;
625                 if (t.absmin_z < cmins_z)
626                         cmins_z = t.absmin_z;
627                 if (t.absmax_x > cmaxs_x)
628                         cmaxs_x = t.absmax_x;
629                 if (t.absmax_y > cmaxs_y)
630                         cmaxs_y = t.absmax_y;
631                 if (t.absmax_z > cmaxs_z)
632                         cmaxs_z = t.absmax_z;
633                 if(t.enemy == self)
634                         break;
635         }
636
637         // distribute health, targetname, message
638         for(t = self; t; t = t.enemy)
639         {
640                 t.health = self.health;
641                 t.targetname = self.targetname;
642                 t.message = self.message;
643                 if(t.enemy == self)
644                         break;
645         }
646
647         // shootable, or triggered doors just needed the owner/enemy links,
648         // they don't spawn a field
649
650         if (self.health)
651                 return;
652         IFTARGETED
653                 return;
654         if (self.items)
655                 return;
656
657         spawn_field(cmins, cmaxs);
658 }
659
660
661 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
662 if two doors touch, they are assumed to be connected and operate as a unit.
663
664 TOGGLE causes the door to wait in both the start and end states for a trigger event.
665
666 START_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 useful for touch or takedamage doors).
667
668 GOLD_KEY causes the door to open only if the activator holds a gold key.
669
670 SILVER_KEY causes the door to open only if the activator holds a silver key.
671
672 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
673 "angle"         determines the opening direction
674 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
675 "health"        if set, door must be shot open
676 "speed"         movement speed (100 default)
677 "wait"          wait before returning (3 default, -1 = never return)
678 "lip"           lip remaining at end of move (8 default)
679 "dmg"           damage to inflict when blocked (2 default)
680 "sounds"
681 0)      no sound
682 1)      stone
683 2)      base
684 3)      stone chain
685 4)      screechy metal
686 FIXME: only one sound set available at the time being
687
688 */
689
690 float door_send(entity to, float sf)
691 {
692         WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR);
693         WriteByte(MSG_ENTITY, sf);
694
695         if(sf & SF_TRIGGER_INIT)
696         {
697                 WriteString(MSG_ENTITY, self.classname);
698                 WriteByte(MSG_ENTITY, self.spawnflags);
699                 WriteShort(MSG_ENTITY, ((self.owner == self || !self.owner) ? -1 : num_for_edict(self.owner)));
700                 WriteShort(MSG_ENTITY, ((self.enemy == self || !self.enemy) ? -1 : num_for_edict(self.enemy)));
701                 WriteShort(MSG_ENTITY, num_for_edict(self));
702
703                 WriteByte(MSG_ENTITY, self.scale);
704
705                 WriteCoord(MSG_ENTITY, self.origin_x);
706                 WriteCoord(MSG_ENTITY, self.origin_y);
707                 WriteCoord(MSG_ENTITY, self.origin_z);
708
709                 WriteString(MSG_ENTITY, self.model);
710
711                 WriteCoord(MSG_ENTITY, self.mins_x);
712                 WriteCoord(MSG_ENTITY, self.mins_y);
713                 WriteCoord(MSG_ENTITY, self.mins_z);
714                 WriteCoord(MSG_ENTITY, self.maxs_x);
715                 WriteCoord(MSG_ENTITY, self.maxs_y);
716                 WriteCoord(MSG_ENTITY, self.maxs_z);
717
718                 WriteCoord(MSG_ENTITY, self.movedir_x);
719                 WriteCoord(MSG_ENTITY, self.movedir_y);
720                 WriteCoord(MSG_ENTITY, self.movedir_z);
721
722                 WriteAngle(MSG_ENTITY, self.angles_x);
723                 WriteAngle(MSG_ENTITY, self.angles_y);
724                 WriteAngle(MSG_ENTITY, self.angles_z);
725
726                 WriteCoord(MSG_ENTITY, self.pos1_x);
727                 WriteCoord(MSG_ENTITY, self.pos1_y);
728                 WriteCoord(MSG_ENTITY, self.pos1_z);
729                 WriteCoord(MSG_ENTITY, self.pos2_x);
730                 WriteCoord(MSG_ENTITY, self.pos2_y);
731                 WriteCoord(MSG_ENTITY, self.pos2_z);
732
733                 WriteCoord(MSG_ENTITY, self.size_x);
734                 WriteCoord(MSG_ENTITY, self.size_y);
735                 WriteCoord(MSG_ENTITY, self.size_z);
736
737                 WriteShort(MSG_ENTITY, self.wait);
738                 WriteShort(MSG_ENTITY, self.speed);
739                 WriteByte(MSG_ENTITY, self.lip);
740                 WriteByte(MSG_ENTITY, self.state);
741                 WriteShort(MSG_ENTITY, self.ltime);
742         }
743
744         if(sf & SF_TRIGGER_RESET)
745         {
746                 // client makes use of this, we do not
747         }
748
749         if(sf & SF_TRIGGER_UPDATE)
750         {
751                 WriteCoord(MSG_ENTITY, self.origin_x);
752                 WriteCoord(MSG_ENTITY, self.origin_y);
753                 WriteCoord(MSG_ENTITY, self.origin_z);
754
755                 WriteCoord(MSG_ENTITY, self.pos1_x);
756                 WriteCoord(MSG_ENTITY, self.pos1_y);
757                 WriteCoord(MSG_ENTITY, self.pos1_z);
758                 WriteCoord(MSG_ENTITY, self.pos2_x);
759                 WriteCoord(MSG_ENTITY, self.pos2_y);
760                 WriteCoord(MSG_ENTITY, self.pos2_z);
761         }
762
763         return TRUE;
764 }
765
766 void door_link()
767 {
768         // set size now, as everything is loaded
769         FixSize(self);
770         Net_LinkEntity(self, FALSE, 0, door_send);
771 }
772
773 void door_init_startopen()
774 {
775         setorigin (self, self.pos2);
776         self.pos2 = self.pos1;
777         self.pos1 = self.origin;
778
779         self.SendFlags |= SF_TRIGGER_UPDATE;
780 }
781
782 #endif
783
784 void door_reset()
785 {
786         setorigin(self, self.pos1);
787         self.velocity = '0 0 0';
788         self.state = STATE_BOTTOM;
789         self.think = func_null;
790         self.nextthink = 0;
791
792 #ifdef SVQC
793         self.SendFlags |= SF_TRIGGER_RESET;
794 #endif
795 }
796
797 #ifdef SVQC
798
799 // spawnflags require key (for now only func_door)
800 void spawnfunc_func_door()
801 {
802         // Quake 1 keys compatibility
803         if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
804                 self.itemkeys |= ITEM_KEY_BIT(0);
805         if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
806                 self.itemkeys |= ITEM_KEY_BIT(1);
807
808         SetMovedir ();
809
810         self.max_health = self.health;
811         if (!InitMovingBrushTrigger())
812                 return;
813         self.effects |= EF_LOWPRECISION;
814         self.classname = "door";
815
816         self.blocked = door_blocked;
817         self.use = door_use;
818
819         if(self.dmg && (self.message == ""))
820                 self.message = "was squished";
821         if(self.dmg && (self.message2 == ""))
822                 self.message2 = "was squished by";
823
824         if (self.sounds > 0)
825         {
826                 precache_sound ("plats/medplat1.wav");
827                 precache_sound ("plats/medplat2.wav");
828                 self.noise2 = "plats/medplat1.wav";
829                 self.noise1 = "plats/medplat2.wav";
830         }
831
832         if (!self.speed)
833                 self.speed = 100;
834         if (!self.wait)
835                 self.wait = 3;
836         if (!self.lip)
837                 self.lip = 8;
838
839         self.pos1 = self.origin;
840         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
841
842 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
843 // but spawn in the open position
844         if (self.spawnflags & DOOR_START_OPEN)
845                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
846
847         self.state = STATE_BOTTOM;
848
849         if (self.health)
850         {
851                 self.takedamage = DAMAGE_YES;
852                 self.event_damage = door_damage;
853         }
854
855         if (self.items)
856                 self.wait = -1;
857
858         self.touch = door_touch;
859
860 // LinkDoors can't be done until all of the doors have been spawned, so
861 // the sizes can be detected properly.
862         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
863
864         self.reset = door_reset;
865 }
866
867 #elif defined(CSQC)
868
869 void ent_door()
870 {
871         float sf = ReadByte();
872
873         if(sf & SF_TRIGGER_INIT)
874         {
875                 self.classname = strzone(ReadString());
876                 self.spawnflags = ReadByte();
877                 float myowner = ReadShort();
878                 float myenemy = ReadShort();
879                 self.sv_entnum = ReadShort();
880
881                 self.scale = ReadByte();
882
883                 self.origin_x = ReadCoord();
884                 self.origin_y = ReadCoord();
885                 self.origin_z = ReadCoord();
886                 setorigin(self, self.origin);
887
888                 self.mdl = strzone(ReadString());
889                 setmodel(self, self.mdl);
890
891                 self.mins_x = ReadCoord();
892                 self.mins_y = ReadCoord();
893                 self.mins_z = ReadCoord();
894                 self.maxs_x = ReadCoord();
895                 self.maxs_y = ReadCoord();
896                 self.maxs_z = ReadCoord();
897                 setsize(self, self.mins, self.maxs);
898
899                 self.movedir_x = ReadCoord();
900                 self.movedir_y = ReadCoord();
901                 self.movedir_z = ReadCoord();
902
903                 self.angles_x = ReadAngle();
904                 self.angles_y = ReadAngle();
905                 self.angles_z = ReadAngle();
906
907                 self.pos1_x = ReadCoord();
908                 self.pos1_y = ReadCoord();
909                 self.pos1_z = ReadCoord();
910                 self.pos2_x = ReadCoord();
911                 self.pos2_y = ReadCoord();
912                 self.pos2_z = ReadCoord();
913
914                 self.size_x = ReadCoord();
915                 self.size_y = ReadCoord();
916                 self.size_z = ReadCoord();
917
918                 self.wait = ReadShort();
919                 self.speed = ReadShort();
920                 self.lip = ReadByte();
921                 self.state = ReadByte();
922                 self.ltime = ReadShort();
923
924                 self.movetype = MOVETYPE_PUSH;
925                 self.solid = SOLID_BSP;
926                 self.trigger_touch = door_touch;
927                 self.draw = trigger_draw_generic;
928                 self.drawmask = MASK_NORMAL;
929                 self.move_time = time;
930                 self.use = door_use;
931                 self.blocked = door_blocked;
932
933                 print(ftos(self.entnum), " ", ftos(self.sv_entnum), "\n");
934
935                 self.owner = ((myowner == -1) ? self : findfloat(world, sv_entnum, myowner));
936                 self.enemy = ((myenemy == -1) ? self : findfloat(world, sv_entnum, myenemy));
937         }
938
939         if(sf & SF_TRIGGER_RESET)
940         {
941                 door_reset();
942         }
943
944         if(sf & SF_TRIGGER_UPDATE)
945         {
946                 self.origin_x = ReadCoord();
947                 self.origin_y = ReadCoord();
948                 self.origin_z = ReadCoord();
949                 setorigin(self, self.origin);
950
951                 self.pos1_x = ReadCoord();
952                 self.pos1_y = ReadCoord();
953                 self.pos1_z = ReadCoord();
954                 self.pos2_x = ReadCoord();
955                 self.pos2_y = ReadCoord();
956                 self.pos2_z = ReadCoord();
957         }
958 }
959
960 #endif