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