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