File: game\g_func.c
1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 */
20 #include "g_local.h"
21
22 /*
23 =========================================================
24
25 PLATS
26
27 movement options:
28
29 linear
30 smooth start, hard stop
31 smooth start, smooth stop
32
33 start
34 end
35 acceleration
36 speed
37 deceleration
38 begin sound
39 end sound
40 target fired when reaching end
41 wait at end
42
43 object characteristics that use move segments
44 ---------------------------------------------
45 movetype_push, or movetype_stop
46 action when touched
47 action when blocked
48 action when used
49 disabled?
50 auto trigger spawning
51
52
53 =========================================================
54 */
55
56 #define PLAT_LOW_TRIGGER 1
57
58 #define STATE_TOP 0
59 #define STATE_BOTTOM 1
60 #define STATE_UP 2
61 #define STATE_DOWN 3
62
63 #define DOOR_START_OPEN 1
64 #define DOOR_REVERSE 2
65 #define DOOR_CRUSHER 4
66 #define DOOR_NOMONSTER 8
67 #define DOOR_TOGGLE 32
68 #define DOOR_X_AXIS 64
69 #define DOOR_Y_AXIS 128
70
71
72 //
73 // Support routines for movement (changes in origin using velocity)
74 //
75
76 void Move_Done (edict_t *ent)
77 {
78 VectorClear (ent->velocity);
79 ent->moveinfo.endfunc (ent);
80 }
81
82 void Move_Final (edict_t *ent)
83 {
84 if (ent->moveinfo.remaining_distance == 0)
85 {
86 Move_Done (ent);
87 return;
88 }
89
90 VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity);
91
92 ent->think = Move_Done;
93 ent->nextthink = level.time + FRAMETIME;
94 }
95
96 void Move_Begin (edict_t *ent)
97 {
98 float frames;
99
100 if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance)
101 {
102 Move_Final (ent);
103 return;
104 }
105 VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity);
106 frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME);
107 ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME;
108 ent->nextthink = level.time + (frames * FRAMETIME);
109 ent->think = Move_Final;
110 }
111
112 void Think_AccelMove (edict_t *ent);
113
114 void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*))
115 {
116 VectorClear (ent->velocity);
117 VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir);
118 ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir);
119 ent->moveinfo.endfunc = func;
120
121 if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel)
122 {
123 if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
124 {
125 Move_Begin (ent);
126 }
127 else
128 {
129 ent->nextthink = level.time + FRAMETIME;
130 ent->think = Move_Begin;
131 }
132 }
133 else
134 {
135 // accelerative
136 ent->moveinfo.current_speed = 0;
137 ent->think = Think_AccelMove;
138 ent->nextthink = level.time + FRAMETIME;
139 }
140 }
141
142
143 //
144 // Support routines for angular movement (changes in angle using avelocity)
145 //
146
147 void AngleMove_Done (edict_t *ent)
148 {
149 VectorClear (ent->avelocity);
150 ent->moveinfo.endfunc (ent);
151 }
152
153 void AngleMove_Final (edict_t *ent)
154 {
155 vec3_t move;
156
157 if (ent->moveinfo.state == STATE_UP)
158 VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move);
159 else
160 VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move);
161
162 if (VectorCompare (move, vec3_origin))
163 {
164 AngleMove_Done (ent);
165 return;
166 }
167
168 VectorScale (move, 1.0/FRAMETIME, ent->avelocity);
169
170 ent->think = AngleMove_Done;
171 ent->nextthink = level.time + FRAMETIME;
172 }
173
174 void AngleMove_Begin (edict_t *ent)
175 {
176 vec3_t destdelta;
177 float len;
178 float traveltime;
179 float frames;
180
181 // set destdelta to the vector needed to move
182 if (ent->moveinfo.state == STATE_UP)
183 VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta);
184 else
185 VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta);
186
187 // calculate length of vector
188 len = VectorLength (destdelta);
189
190 // divide by speed to get time to reach dest
191 traveltime = len / ent->moveinfo.speed;
192
193 if (traveltime < FRAMETIME)
194 {
195 AngleMove_Final (ent);
196 return;
197 }
198
199 frames = floor(traveltime / FRAMETIME);
200
201 // scale the destdelta vector by the time spent traveling to get velocity
202 VectorScale (destdelta, 1.0 / traveltime, ent->avelocity);
203
204 // set nextthink to trigger a think when dest is reached
205 ent->nextthink = level.time + frames * FRAMETIME;
206 ent->think = AngleMove_Final;
207 }
208
209 void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*))
210 {
211 VectorClear (ent->avelocity);
212 ent->moveinfo.endfunc = func;
213 if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
214 {
215 AngleMove_Begin (ent);
216 }
217 else
218 {
219 ent->nextthink = level.time + FRAMETIME;
220 ent->think = AngleMove_Begin;
221 }
222 }
223
224
225 /*
226 ==============
227 Think_AccelMove
228
229 The team has completed a frame of movement, so
230 change the speed for the next frame
231 ==============
232 */
233 #define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2)
234
235 void plat_CalcAcceleratedMove(moveinfo_t *moveinfo)
236 {
237 float accel_dist;
238 float decel_dist;
239
240 moveinfo->move_speed = moveinfo->speed;
241
242 if (moveinfo->remaining_distance < moveinfo->accel)
243 {
244 moveinfo->current_speed = moveinfo->remaining_distance;
245 return;
246 }
247
248 accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel);
249 decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel);
250
251 if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0)
252 {
253 float f;
254
255 f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel);
256 moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f);
257 decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel);
258 }
259
260 moveinfo->decel_distance = decel_dist;
261 };
262
263 void plat_Accelerate (moveinfo_t *moveinfo)
264 {
265 // are we decelerating?
266 if (moveinfo->remaining_distance <= moveinfo->decel_distance)
267 {
268 if (moveinfo->remaining_distance < moveinfo->decel_distance)
269 {
270 if (moveinfo->next_speed)
271 {
272 moveinfo->current_speed = moveinfo->next_speed;
273 moveinfo->next_speed = 0;
274 return;
275 }
276 if (moveinfo->current_speed > moveinfo->decel)
277 moveinfo->current_speed -= moveinfo->decel;
278 }
279 return;
280 }
281
282 // are we at full speed and need to start decelerating during this move?
283 if (moveinfo->current_speed == moveinfo->move_speed)
284 if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance)
285 {
286 float p1_distance;
287 float p2_distance;
288 float distance;
289
290 p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
291 p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed));
292 distance = p1_distance + p2_distance;
293 moveinfo->current_speed = moveinfo->move_speed;
294 moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
295 return;
296 }
297
298 // are we accelerating?
299 if (moveinfo->current_speed < moveinfo->speed)
300 {
301 float old_speed;
302 float p1_distance;
303 float p1_speed;
304 float p2_distance;
305 float distance;
306
307 old_speed = moveinfo->current_speed;
308
309 // figure simple acceleration up to move_speed
310 moveinfo->current_speed += moveinfo->accel;
311 if (moveinfo->current_speed > moveinfo->speed)
312 moveinfo->current_speed = moveinfo->speed;
313
314 // are we accelerating throughout this entire move?
315 if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance)
316 return;
317
318 // during this move we will accelrate from current_speed to move_speed
319 // and cross over the decel_distance; figure the average speed for the
320 // entire move
321 p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
322 p1_speed = (old_speed + moveinfo->move_speed) / 2.0;
323 p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed));
324 distance = p1_distance + p2_distance;
325 moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance));
326 moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
327 return;
328 }
329
330 // we are at constant velocity (move_speed)
331 return;
332 };
333
334 void Think_AccelMove (edict_t *ent)
335 {
336 ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed;
337
338 if (ent->moveinfo.current_speed == 0) // starting or blocked
339 plat_CalcAcceleratedMove(&ent->moveinfo);
340
341 plat_Accelerate (&ent->moveinfo);
342
343 // will the entire move complete on next frame?
344 if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed)
345 {
346 Move_Final (ent);
347 return;
348 }
349
350 VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity);
351 ent->nextthink = level.time + FRAMETIME;
352 ent->think = Think_AccelMove;
353 }
354
355
356 void plat_go_down (edict_t *ent);
357
358 void plat_hit_top (edict_t *ent)
359 {
360 if (!(ent->flags & FL_TEAMSLAVE))
361 {
362 if (ent->moveinfo.sound_end)
363 gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
364 ent->s.sound = 0;
365 }
366 ent->moveinfo.state = STATE_TOP;
367
368 ent->think = plat_go_down;
369 ent->nextthink = level.time + 3;
370 }
371
372 void plat_hit_bottom (edict_t *ent)
373 {
374 if (!(ent->flags & FL_TEAMSLAVE))
375 {
376 if (ent->moveinfo.sound_end)
377 gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
378 ent->s.sound = 0;
379 }
380 ent->moveinfo.state = STATE_BOTTOM;
381 }
382
383 void plat_go_down (edict_t *ent)
384 {
385 if (!(ent->flags & FL_TEAMSLAVE))
386 {
387 if (ent->moveinfo.sound_start)
388 gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
389 ent->s.sound = ent->moveinfo.sound_middle;
390 }
391 ent->moveinfo.state = STATE_DOWN;
392 Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom);
393 }
394
395 void plat_go_up (edict_t *ent)
396 {
397 if (!(ent->flags & FL_TEAMSLAVE))
398 {
399 if (ent->moveinfo.sound_start)
400 gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
401 ent->s.sound = ent->moveinfo.sound_middle;
402 }
403 ent->moveinfo.state = STATE_UP;
404 Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top);
405 }
406
407 void plat_blocked (edict_t *self, edict_t *other)
408 {
409 if (!(other->svflags & SVF_MONSTER) && (!other->client) )
410 {
411 // give it a chance to go away on it's own terms (like gibs)
412 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
413 // if it's still there, nuke it
414 if (other)
415 BecomeExplosion1 (other);
416 return;
417 }
418
419 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
420
421 if (self->moveinfo.state == STATE_UP)
422 plat_go_down (self);
423 else if (self->moveinfo.state == STATE_DOWN)
424 plat_go_up (self);
425 }
426
427
428 void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator)
429 {
430 if (ent->think)
431 return; // already down
432 plat_go_down (ent);
433 }
434
435
436 void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
437 {
438 if (!other->client)
439 return;
440
441 if (other->health <= 0)
442 return;
443
444 ent = ent->enemy; // now point at the plat, not the trigger
445 if (ent->moveinfo.state == STATE_BOTTOM)
446 plat_go_up (ent);
447 else if (ent->moveinfo.state == STATE_TOP)
448 ent->nextthink = level.time + 1; // the player is still on the plat, so delay going down
449 }
450
451 void plat_spawn_inside_trigger (edict_t *ent)
452 {
453 edict_t *trigger;
454 vec3_t tmin, tmax;
455
456 //
457 // middle trigger
458 //
459 trigger = G_Spawn();
460 trigger->touch = Touch_Plat_Center;
461 trigger->movetype = MOVETYPE_NONE;
462 trigger->solid = SOLID_TRIGGER;
463 trigger->enemy = ent;
464
465 tmin[0] = ent->mins[0] + 25;
466 tmin[1] = ent->mins[1] + 25;
467 tmin[2] = ent->mins[2];
468
469 tmax[0] = ent->maxs[0] - 25;
470 tmax[1] = ent->maxs[1] - 25;
471 tmax[2] = ent->maxs[2] + 8;
472
473 tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip);
474
475 if (ent->spawnflags & PLAT_LOW_TRIGGER)
476 tmax[2] = tmin[2] + 8;
477
478 if (tmax[0] - tmin[0] <= 0)
479 {
480 tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5;
481 tmax[0] = tmin[0] + 1;
482 }
483 if (tmax[1] - tmin[1] <= 0)
484 {
485 tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5;
486 tmax[1] = tmin[1] + 1;
487 }
488
489 VectorCopy (tmin, trigger->mins);
490 VectorCopy (tmax, trigger->maxs);
491
492 gi.linkentity (trigger);
493 }
494
495
496 /*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER
497 speed default 150
498
499 Plats are always drawn in the extended position, so they will light correctly.
500
501 If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat.
502
503 "speed" overrides default 200.
504 "accel" overrides default 500
505 "lip" overrides default 8 pixel lip
506
507 If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height.
508
509 Set "sounds" to one of the following:
510 1) base fast
511 2) chain slow
512 */
513 void SP_func_plat (edict_t *ent)
514 {
515 VectorClear (ent->s.angles);
516 ent->solid = SOLID_BSP;
517 ent->movetype = MOVETYPE_PUSH;
518
519 gi.setmodel (ent, ent->model);
520
521 ent->blocked = plat_blocked;
522
523 if (!ent->speed)
524 ent->speed = 20;
525 else
526 ent->speed *= 0.1;
527
528 if (!ent->accel)
529 ent->accel = 5;
530 else
531 ent->accel *= 0.1;
532
533 if (!ent->decel)
534 ent->decel = 5;
535 else
536 ent->decel *= 0.1;
537
538 if (!ent->dmg)
539 ent->dmg = 2;
540
541 if (!st.lip)
542 st.lip = 8;
543
544 // pos1 is the top position, pos2 is the bottom
545 VectorCopy (ent->s.origin, ent->pos1);
546 VectorCopy (ent->s.origin, ent->pos2);
547 if (st.height)
548 ent->pos2[2] -= st.height;
549 else
550 ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
551
552 ent->use = Use_Plat;
553
554 plat_spawn_inside_trigger (ent); // the "start moving" trigger
555
556 if (ent->targetname)
557 {
558 ent->moveinfo.state = STATE_UP;
559 }
560 else
561 {
562 VectorCopy (ent->pos2, ent->s.origin);
563 gi.linkentity (ent);
564 ent->moveinfo.state = STATE_BOTTOM;
565 }
566
567 ent->moveinfo.speed = ent->speed;
568 ent->moveinfo.accel = ent->accel;
569 ent->moveinfo.decel = ent->decel;
570 ent->moveinfo.wait = ent->wait;
571 VectorCopy (ent->pos1, ent->moveinfo.start_origin);
572 VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
573 VectorCopy (ent->pos2, ent->moveinfo.end_origin);
574 VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
575
576 ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav");
577 ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav");
578 ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav");
579 }
580
581 //====================================================================
582
583 /*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST
584 You need to have an origin brush as part of this entity. The center of that brush will be
585 the point around which it is rotated. It will rotate around the Z axis by default. You can
586 check either the X_AXIS or Y_AXIS box to change that.
587
588 "speed" determines how fast it moves; default value is 100.
589 "dmg" damage to inflict when blocked (2 default)
590
591 REVERSE will cause the it to rotate in the opposite direction.
592 STOP mean it will stop moving instead of pushing entities
593 */
594
595 void rotating_blocked (edict_t *self, edict_t *other)
596 {
597 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
598 }
599
600 void rotating_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
601 {
602 if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2])
603 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
604 }
605
606 void rotating_use (edict_t *self, edict_t *other, edict_t *activator)
607 {
608 if (!VectorCompare (self->avelocity, vec3_origin))
609 {
610 self->s.sound = 0;
611 VectorClear (self->avelocity);
612 self->touch = NULL;
613 }
614 else
615 {
616 self->s.sound = self->moveinfo.sound_middle;
617 VectorScale (self->movedir, self->speed, self->avelocity);
618 if (self->spawnflags & 16)
619 self->touch = rotating_touch;
620 }
621 }
622
623 void SP_func_rotating (edict_t *ent)
624 {
625 ent->solid = SOLID_BSP;
626 if (ent->spawnflags & 32)
627 ent->movetype = MOVETYPE_STOP;
628 else
629 ent->movetype = MOVETYPE_PUSH;
630
631 // set the axis of rotation
632 VectorClear(ent->movedir);
633 if (ent->spawnflags & 4)
634 ent->movedir[2] = 1.0;
635 else if (ent->spawnflags & 8)
636 ent->movedir[0] = 1.0;
637 else // Z_AXIS
638 ent->movedir[1] = 1.0;
639
640 // check for reverse rotation
641 if (ent->spawnflags & 2)
642 VectorNegate (ent->movedir, ent->movedir);
643
644 if (!ent->speed)
645 ent->speed = 100;
646 if (!ent->dmg)
647 ent->dmg = 2;
648
649 // ent->moveinfo.sound_middle = "doors/hydro1.wav";
650
651 ent->use = rotating_use;
652 if (ent->dmg)
653 ent->blocked = rotating_blocked;
654
655 if (ent->spawnflags & 1)
656 ent->use (ent, NULL, NULL);
657
658 if (ent->spawnflags & 64)
659 ent->s.effects |= EF_ANIM_ALL;
660 if (ent->spawnflags & 128)
661 ent->s.effects |= EF_ANIM_ALLFAST;
662
663 gi.setmodel (ent, ent->model);
664 gi.linkentity (ent);
665 }
666
667 /*
668 ======================================================================
669
670 BUTTONS
671
672 ======================================================================
673 */
674
675 /*QUAKED func_button (0 .5 .8) ?
676 When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
677
678 "angle" determines the opening direction
679 "target" all entities with a matching targetname will be used
680 "speed" override the default 40 speed
681 "wait" override the default 1 second wait (-1 = never return)
682 "lip" override the default 4 pixel lip remaining at end of move
683 "health" if set, the button must be killed instead of touched
684 "sounds"
685 1) silent
686 2) steam metal
687 3) wooden clunk
688 4) metallic click
689 5) in-out
690 */
691
692 void button_done (edict_t *self)
693 {
694 self->moveinfo.state = STATE_BOTTOM;
695 self->s.effects &= ~EF_ANIM23;
696 self->s.effects |= EF_ANIM01;
697 }
698
699 void button_return (edict_t *self)
700 {
701 self->moveinfo.state = STATE_DOWN;
702
703 Move_Calc (self, self->moveinfo.start_origin, button_done);
704
705 self->s.frame = 0;
706
707 if (self->health)
708 self->takedamage = DAMAGE_YES;
709 }
710
711 void button_wait (edict_t *self)
712 {
713 self->moveinfo.state = STATE_TOP;
714 self->s.effects &= ~EF_ANIM01;
715 self->s.effects |= EF_ANIM23;
716
717 G_UseTargets (self, self->activator);
718 self->s.frame = 1;
719 if (self->moveinfo.wait >= 0)
720 {
721 self->nextthink = level.time + self->moveinfo.wait;
722 self->think = button_return;
723 }
724 }
725
726 void button_fire (edict_t *self)
727 {
728 if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
729 return;
730
731 self->moveinfo.state = STATE_UP;
732 if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE))
733 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
734 Move_Calc (self, self->moveinfo.end_origin, button_wait);
735 }
736
737 void button_use (edict_t *self, edict_t *other, edict_t *activator)
738 {
739 self->activator = activator;
740 button_fire (self);
741 }
742
743 void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
744 {
745 if (!other->client)
746 return;
747
748 if (other->health <= 0)
749 return;
750
751 self->activator = other;
752 button_fire (self);
753 }
754
755 void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
756 {
757 self->activator = attacker;
758 self->health = self->max_health;
759 self->takedamage = DAMAGE_NO;
760 button_fire (self);
761 }
762
763 void SP_func_button (edict_t *ent)
764 {
765 vec3_t abs_movedir;
766 float dist;
767
768 G_SetMovedir (ent->s.angles, ent->movedir);
769 ent->movetype = MOVETYPE_STOP;
770 ent->solid = SOLID_BSP;
771 gi.setmodel (ent, ent->model);
772
773 if (ent->sounds != 1)
774 ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav");
775
776 if (!ent->speed)
777 ent->speed = 40;
778 if (!ent->accel)
779 ent->accel = ent->speed;
780 if (!ent->decel)
781 ent->decel = ent->speed;
782
783 if (!ent->wait)
784 ent->wait = 3;
785 if (!st.lip)
786 st.lip = 4;
787
788 VectorCopy (ent->s.origin, ent->pos1);
789 abs_movedir[0] = fabs(ent->movedir[0]);
790 abs_movedir[1] = fabs(ent->movedir[1]);
791 abs_movedir[2] = fabs(ent->movedir[2]);
792 dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
793 VectorMA (ent->pos1, dist, ent->movedir, ent->pos2);
794
795 ent->use = button_use;
796 ent->s.effects |= EF_ANIM01;
797
798 if (ent->health)
799 {
800 ent->max_health = ent->health;
801 ent->die = button_killed;
802 ent->takedamage = DAMAGE_YES;
803 }
804 else if (! ent->targetname)
805 ent->touch = button_touch;
806
807 ent->moveinfo.state = STATE_BOTTOM;
808
809 ent->moveinfo.speed = ent->speed;
810 ent->moveinfo.accel = ent->accel;
811 ent->moveinfo.decel = ent->decel;
812 ent->moveinfo.wait = ent->wait;
813 VectorCopy (ent->pos1, ent->moveinfo.start_origin);
814 VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
815 VectorCopy (ent->pos2, ent->moveinfo.end_origin);
816 VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
817
818 gi.linkentity (ent);
819 }
820
821 /*
822 ======================================================================
823
824 DOORS
825
826 spawn a trigger surrounding the entire team unless it is
827 already targeted by another
828
829 ======================================================================
830 */
831
832 /*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST
833 TOGGLE wait in both the start and end states for a trigger event.
834 START_OPEN the door to moves 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).
835 NOMONSTER monsters will not trigger this door
836
837 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
838 "angle" determines the opening direction
839 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
840 "health" if set, door must be shot open
841 "speed" movement speed (100 default)
842 "wait" wait before returning (3 default, -1 = never return)
843 "lip" lip remaining at end of move (8 default)
844 "dmg" damage to inflict when blocked (2 default)
845 "sounds"
846 1) silent
847 2) light
848 3) medium
849 4) heavy
850 */
851
852 void door_use_areaportals (edict_t *self, qboolean open)
853 {
854 edict_t *t = NULL;
855
856 if (!self->target)
857 return;
858
859 while ((t = G_Find (t, FOFS(targetname), self->target)))
860 {
861 if (Q_stricmp(t->classname, "func_areaportal") == 0)
862 {
863 gi.SetAreaPortalState (t->style, open);
864 }
865 }
866 }
867
868 void door_go_down (edict_t *self);
869
870 void door_hit_top (edict_t *self)
871 {
872 if (!(self->flags & FL_TEAMSLAVE))
873 {
874 if (self->moveinfo.sound_end)
875 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
876 self->s.sound = 0;
877 }
878 self->moveinfo.state = STATE_TOP;
879 if (self->spawnflags & DOOR_TOGGLE)
880 return;
881 if (self->moveinfo.wait >= 0)
882 {
883 self->think = door_go_down;
884 self->nextthink = level.time + self->moveinfo.wait;
885 }
886 }
887
888 void door_hit_bottom (edict_t *self)
889 {
890 if (!(self->flags & FL_TEAMSLAVE))
891 {
892 if (self->moveinfo.sound_end)
893 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
894 self->s.sound = 0;
895 }
896 self->moveinfo.state = STATE_BOTTOM;
897 door_use_areaportals (self, false);
898 }
899
900 void door_go_down (edict_t *self)
901 {
902 if (!(self->flags & FL_TEAMSLAVE))
903 {
904 if (self->moveinfo.sound_start)
905 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
906 self->s.sound = self->moveinfo.sound_middle;
907 }
908 if (self->max_health)
909 {
910 self->takedamage = DAMAGE_YES;
911 self->health = self->max_health;
912 }
913
914 self->moveinfo.state = STATE_DOWN;
915 if (strcmp(self->classname, "func_door") == 0)
916 Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom);
917 else if (strcmp(self->classname, "func_door_rotating") == 0)
918 AngleMove_Calc (self, door_hit_bottom);
919 }
920
921 void door_go_up (edict_t *self, edict_t *activator)
922 {
923 if (self->moveinfo.state == STATE_UP)
924 return; // already going up
925
926 if (self->moveinfo.state == STATE_TOP)
927 { // reset top wait time
928 if (self->moveinfo.wait >= 0)
929 self->nextthink = level.time + self->moveinfo.wait;
930 return;
931 }
932
933 if (!(self->flags & FL_TEAMSLAVE))
934 {
935 if (self->moveinfo.sound_start)
936 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
937 self->s.sound = self->moveinfo.sound_middle;
938 }
939 self->moveinfo.state = STATE_UP;
940 if (strcmp(self->classname, "func_door") == 0)
941 Move_Calc (self, self->moveinfo.end_origin, door_hit_top);
942 else if (strcmp(self->classname, "func_door_rotating") == 0)
943 AngleMove_Calc (self, door_hit_top);
944
945 G_UseTargets (self, activator);
946 door_use_areaportals (self, true);
947 }
948
949 void door_use (edict_t *self, edict_t *other, edict_t *activator)
950 {
951 edict_t *ent;
952
953 if (self->flags & FL_TEAMSLAVE)
954 return;
955
956 if (self->spawnflags & DOOR_TOGGLE)
957 {
958 if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
959 {
960 // trigger all paired doors
961 for (ent = self ; ent ; ent = ent->teamchain)
962 {
963 ent->message = NULL;
964 ent->touch = NULL;
965 door_go_down (ent);
966 }
967 return;
968 }
969 }
970
971 // trigger all paired doors
972 for (ent = self ; ent ; ent = ent->teamchain)
973 {
974 ent->message = NULL;
975 ent->touch = NULL;
976 door_go_up (ent, activator);
977 }
978 };
979
980 void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
981 {
982 if (other->health <= 0)
983 return;
984
985 if (!(other->svflags & SVF_MONSTER) && (!other->client))
986 return;
987
988 if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER))
989 return;
990
991 if (level.time < self->touch_debounce_time)
992 return;
993 self->touch_debounce_time = level.time + 1.0;
994
995 door_use (self->owner, other, other);
996 }
997
998 void Think_CalcMoveSpeed (edict_t *self)
999 {
1000 edict_t *ent;
1001 float min;
1002 float time;
1003 float newspeed;
1004 float ratio;
1005 float dist;
1006
1007 if (self->flags & FL_TEAMSLAVE)
1008 return; // only the team master does this
1009
1010 // find the smallest distance any member of the team will be moving
1011 min = fabs(self->moveinfo.distance);
1012 for (ent = self->teamchain; ent; ent = ent->teamchain)
1013 {
1014 dist = fabs(ent->moveinfo.distance);
1015 if (dist < min)
1016 min = dist;
1017 }
1018
1019 time = min / self->moveinfo.speed;
1020
1021 // adjust speeds so they will all complete at the same time
1022 for (ent = self; ent; ent = ent->teamchain)
1023 {
1024 newspeed = fabs(ent->moveinfo.distance) / time;
1025 ratio = newspeed / ent->moveinfo.speed;
1026 if (ent->moveinfo.accel == ent->moveinfo.speed)
1027 ent->moveinfo.accel = newspeed;
1028 else
1029 ent->moveinfo.accel *= ratio;
1030 if (ent->moveinfo.decel == ent->moveinfo.speed)
1031 ent->moveinfo.decel = newspeed;
1032 else
1033 ent->moveinfo.decel *= ratio;
1034 ent->moveinfo.speed = newspeed;
1035 }
1036 }
1037
1038 void Think_SpawnDoorTrigger (edict_t *ent)
1039 {
1040 edict_t *other;
1041 vec3_t mins, maxs;
1042
1043 if (ent->flags & FL_TEAMSLAVE)
1044 return; // only the team leader spawns a trigger
1045
1046 VectorCopy (ent->absmin, mins);
1047 VectorCopy (ent->absmax, maxs);
1048
1049 for (other = ent->teamchain ; other ; other=other->teamchain)
1050 {
1051 AddPointToBounds (other->absmin, mins, maxs);
1052 AddPointToBounds (other->absmax, mins, maxs);
1053 }
1054
1055 // expand
1056 mins[0] -= 60;
1057 mins[1] -= 60;
1058 maxs[0] += 60;
1059 maxs[1] += 60;
1060
1061 other = G_Spawn ();
1062 VectorCopy (mins, other->mins);
1063 VectorCopy (maxs, other->maxs);
1064 other->owner = ent;
1065 other->solid = SOLID_TRIGGER;
1066 other->movetype = MOVETYPE_NONE;
1067 other->touch = Touch_DoorTrigger;
1068 gi.linkentity (other);
1069
1070 if (ent->spawnflags & DOOR_START_OPEN)
1071 door_use_areaportals (ent, true);
1072
1073 Think_CalcMoveSpeed (ent);
1074 }
1075
1076 void door_blocked (edict_t *self, edict_t *other)
1077 {
1078 edict_t *ent;
1079
1080 if (!(other->svflags & SVF_MONSTER) && (!other->client) )
1081 {
1082 // give it a chance to go away on it's own terms (like gibs)
1083 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
1084 // if it's still there, nuke it
1085 if (other)
1086 BecomeExplosion1 (other);
1087 return;
1088 }
1089
1090 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
1091
1092 if (self->spawnflags & DOOR_CRUSHER)
1093 return;
1094
1095
1096 // if a door has a negative wait, it would never come back if blocked,
1097 // so let it just squash the object to death real fast
1098 if (self->moveinfo.wait >= 0)
1099 {
1100 if (self->moveinfo.state == STATE_DOWN)
1101 {
1102 for (ent = self->teammaster ; ent ; ent = ent->teamchain)
1103 door_go_up (ent, ent->activator);
1104 }
1105 else
1106 {
1107 for (ent = self->teammaster ; ent ; ent = ent->teamchain)
1108 door_go_down (ent);
1109 }
1110 }
1111 }
1112
1113 void door_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
1114 {
1115 edict_t *ent;
1116
1117 for (ent = self->teammaster ; ent ; ent = ent->teamchain)
1118 {
1119 ent->health = ent->max_health;
1120 ent->takedamage = DAMAGE_NO;
1121 }
1122 door_use (self->teammaster, attacker, attacker);
1123 }
1124
1125 void door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
1126 {
1127 if (!other->client)
1128 return;
1129
1130 if (level.time < self->touch_debounce_time)
1131 return;
1132 self->touch_debounce_time = level.time + 5.0;
1133
1134 gi.centerprintf (other, "%s", self->message);
1135 gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
1136 }
1137
1138 void SP_func_door (edict_t *ent)
1139 {
1140 vec3_t abs_movedir;
1141
1142 if (ent->sounds != 1)
1143 {
1144 ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav");
1145 ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav");
1146 ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav");
1147 }
1148
1149 G_SetMovedir (ent->s.angles, ent->movedir);
1150 ent->movetype = MOVETYPE_PUSH;
1151 ent->solid = SOLID_BSP;
1152 gi.setmodel (ent, ent->model);
1153
1154 ent->blocked = door_blocked;
1155 ent->use = door_use;
1156
1157 if (!ent->speed)
1158 ent->speed = 100;
1159 if (deathmatch->value)
1160 ent->speed *= 2;
1161
1162 if (!ent->accel)
1163 ent->accel = ent->speed;
1164 if (!ent->decel)
1165 ent->decel = ent->speed;
1166
1167 if (!ent->wait)
1168 ent->wait = 3;
1169 if (!st.lip)
1170 st.lip = 8;
1171 if (!ent->dmg)
1172 ent->dmg = 2;
1173
1174 // calculate second position
1175 VectorCopy (ent->s.origin, ent->pos1);
1176 abs_movedir[0] = fabs(ent->movedir[0]);
1177 abs_movedir[1] = fabs(ent->movedir[1]);
1178 abs_movedir[2] = fabs(ent->movedir[2]);
1179 ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
1180 VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2);
1181
1182 // if it starts open, switch the positions
1183 if (ent->spawnflags & DOOR_START_OPEN)
1184 {
1185 VectorCopy (ent->pos2, ent->s.origin);
1186 VectorCopy (ent->pos1, ent->pos2);
1187 VectorCopy (ent->s.origin, ent->pos1);
1188 }
1189
1190 ent->moveinfo.state = STATE_BOTTOM;
1191
1192 if (ent->health)
1193 {
1194 ent->takedamage = DAMAGE_YES;
1195 ent->die = door_killed;
1196 ent->max_health = ent->health;
1197 }
1198 else if (ent->targetname && ent->message)
1199 {
1200 gi.soundindex ("misc/talk.wav");
1201 ent->touch = door_touch;
1202 }
1203
1204 ent->moveinfo.speed = ent->speed;
1205 ent->moveinfo.accel = ent->accel;
1206 ent->moveinfo.decel = ent->decel;
1207 ent->moveinfo.wait = ent->wait;
1208 VectorCopy (ent->pos1, ent->moveinfo.start_origin);
1209 VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
1210 VectorCopy (ent->pos2, ent->moveinfo.end_origin);
1211 VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
1212
1213 if (ent->spawnflags & 16)
1214 ent->s.effects |= EF_ANIM_ALL;
1215 if (ent->spawnflags & 64)
1216 ent->s.effects |= EF_ANIM_ALLFAST;
1217
1218 // to simplify logic elsewhere, make non-teamed doors into a team of one
1219 if (!ent->team)
1220 ent->teammaster = ent;
1221
1222 gi.linkentity (ent);
1223
1224 ent->nextthink = level.time + FRAMETIME;
1225 if (ent->health || ent->targetname)
1226 ent->think = Think_CalcMoveSpeed;
1227 else
1228 ent->think = Think_SpawnDoorTrigger;
1229 }
1230
1231
1232 /*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS
1233 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1234
1235 START_OPEN the door to moves 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).
1236 NOMONSTER monsters will not trigger this door
1237
1238 You need to have an origin brush as part of this entity. The center of that brush will be
1239 the point around which it is rotated. It will rotate around the Z axis by default. You can
1240 check either the X_AXIS or Y_AXIS box to change that.
1241
1242 "distance" is how many degrees the door will be rotated.
1243 "speed" determines how fast the door moves; default value is 100.
1244
1245 REVERSE will cause the door to rotate in the opposite direction.
1246
1247 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1248 "angle" determines the opening direction
1249 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1250 "health" if set, door must be shot open
1251 "speed" movement speed (100 default)
1252 "wait" wait before returning (3 default, -1 = never return)
1253 "dmg" damage to inflict when blocked (2 default)
1254 "sounds"
1255 1) silent
1256 2) light
1257 3) medium
1258 4) heavy
1259 */
1260
1261 void SP_func_door_rotating (edict_t *ent)
1262 {
1263 VectorClear (ent->s.angles);
1264
1265 // set the axis of rotation
1266 VectorClear(ent->movedir);
1267 if (ent->spawnflags & DOOR_X_AXIS)
1268 ent->movedir[2] = 1.0;
1269 else if (ent->spawnflags & DOOR_Y_AXIS)
1270 ent->movedir[0] = 1.0;
1271 else // Z_AXIS
1272 ent->movedir[1] = 1.0;
1273
1274 // check for reverse rotation
1275 if (ent->spawnflags & DOOR_REVERSE)
1276 VectorNegate (ent->movedir, ent->movedir);
1277
1278 if (!st.distance)
1279 {
1280 gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin));
1281 st.distance = 90;
1282 }
1283
1284 VectorCopy (ent->s.angles, ent->pos1);
1285 VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2);
1286 ent->moveinfo.distance = st.distance;
1287
1288 ent->movetype = MOVETYPE_PUSH;
1289 ent->solid = SOLID_BSP;
1290 gi.setmodel (ent, ent->model);
1291
1292 ent->blocked = door_blocked;
1293 ent->use = door_use;
1294
1295 if (!ent->speed)
1296 ent->speed = 100;
1297 if (!ent->accel)
1298 ent->accel = ent->speed;
1299 if (!ent->decel)
1300 ent->decel = ent->speed;
1301
1302 if (!ent->wait)
1303 ent->wait = 3;
1304 if (!ent->dmg)
1305 ent->dmg = 2;
1306
1307 if (ent->sounds != 1)
1308 {
1309 ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav");
1310 ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav");
1311 ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav");
1312 }
1313
1314 // if it starts open, switch the positions
1315 if (ent->spawnflags & DOOR_START_OPEN)
1316 {
1317 VectorCopy (ent->pos2, ent->s.angles);
1318 VectorCopy (ent->pos1, ent->pos2);
1319 VectorCopy (ent->s.angles, ent->pos1);
1320 VectorNegate (ent->movedir, ent->movedir);
1321 }
1322
1323 if (ent->health)
1324 {
1325 ent->takedamage = DAMAGE_YES;
1326 ent->die = door_killed;
1327 ent->max_health = ent->health;
1328 }
1329
1330 if (ent->targetname && ent->message)
1331 {
1332 gi.soundindex ("misc/talk.wav");
1333 ent->touch = door_touch;
1334 }
1335
1336 ent->moveinfo.state = STATE_BOTTOM;
1337 ent->moveinfo.speed = ent->speed;
1338 ent->moveinfo.accel = ent->accel;
1339 ent->moveinfo.decel = ent->decel;
1340 ent->moveinfo.wait = ent->wait;
1341 VectorCopy (ent->s.origin, ent->moveinfo.start_origin);
1342 VectorCopy (ent->pos1, ent->moveinfo.start_angles);
1343 VectorCopy (ent->s.origin, ent->moveinfo.end_origin);
1344 VectorCopy (ent->pos2, ent->moveinfo.end_angles);
1345
1346 if (ent->spawnflags & 16)
1347 ent->s.effects |= EF_ANIM_ALL;
1348
1349 // to simplify logic elsewhere, make non-teamed doors into a team of one
1350 if (!ent->team)
1351 ent->teammaster = ent;
1352
1353 gi.linkentity (ent);
1354
1355 ent->nextthink = level.time + FRAMETIME;
1356 if (ent->health || ent->targetname)
1357 ent->think = Think_CalcMoveSpeed;
1358 else
1359 ent->think = Think_SpawnDoorTrigger;
1360 }
1361
1362
1363 /*QUAKED func_water (0 .5 .8) ? START_OPEN
1364 func_water is a moveable water brush. It must be targeted to operate. Use a non-water texture at your own risk.
1365
1366 START_OPEN causes the water to move to its destination when spawned and operate in reverse.
1367
1368 "angle" determines the opening direction (up or down only)
1369 "speed" movement speed (25 default)
1370 "wait" wait before returning (-1 default, -1 = TOGGLE)
1371 "lip" lip remaining at end of move (0 default)
1372 "sounds" (yes, these need to be changed)
1373 0) no sound
1374 1) water
1375 2) lava
1376 */
1377
1378 void SP_func_water (edict_t *self)
1379 {
1380 vec3_t abs_movedir;
1381
1382 G_SetMovedir (self->s.angles, self->movedir);
1383 self->movetype = MOVETYPE_PUSH;
1384 self->solid = SOLID_BSP;
1385 gi.setmodel (self, self->model);
1386
1387 switch (self->sounds)
1388 {
1389 default:
1390 break;
1391
1392 case 1: // water
1393 self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav");
1394 self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav");
1395 break;
1396
1397 case 2: // lava
1398 self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav");
1399 self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav");
1400 break;
1401 }
1402
1403 // calculate second position
1404 VectorCopy (self->s.origin, self->pos1);
1405 abs_movedir[0] = fabs(self->movedir[0]);
1406 abs_movedir[1] = fabs(self->movedir[1]);
1407 abs_movedir[2] = fabs(self->movedir[2]);
1408 self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip;
1409 VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2);
1410
1411 // if it starts open, switch the positions
1412 if (self->spawnflags & DOOR_START_OPEN)
1413 {
1414 VectorCopy (self->pos2, self->s.origin);
1415 VectorCopy (self->pos1, self->pos2);
1416 VectorCopy (self->s.origin, self->pos1);
1417 }
1418
1419 VectorCopy (self->pos1, self->moveinfo.start_origin);
1420 VectorCopy (self->s.angles, self->moveinfo.start_angles);
1421 VectorCopy (self->pos2, self->moveinfo.end_origin);
1422 VectorCopy (self->s.angles, self->moveinfo.end_angles);
1423
1424 self->moveinfo.state = STATE_BOTTOM;
1425
1426 if (!self->speed)
1427 self->speed = 25;
1428 self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed;
1429
1430 if (!self->wait)
1431 self->wait = -1;
1432 self->moveinfo.wait = self->wait;
1433
1434 self->use = door_use;
1435
1436 if (self->wait == -1)
1437 self->spawnflags |= DOOR_TOGGLE;
1438
1439 self->classname = "func_door";
1440
1441 gi.linkentity (self);
1442 }
1443
1444
1445 #define TRAIN_START_ON 1
1446 #define TRAIN_TOGGLE 2
1447 #define TRAIN_BLOCK_STOPS 4
1448
1449 /*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
1450 Trains are moving platforms that players can ride.
1451 The targets origin specifies the min point of the train at each corner.
1452 The train spawns at the first target it is pointing at.
1453 If the train is the target of a button or trigger, it will not begin moving until activated.
1454 speed default 100
1455 dmg default 2
1456 noise looping sound to play when the train is in motion
1457
1458 */
1459 void train_next (edict_t *self);
1460
1461 void train_blocked (edict_t *self, edict_t *other)
1462 {
1463 if (!(other->svflags & SVF_MONSTER) && (!other->client) )
1464 {
1465 // give it a chance to go away on it's own terms (like gibs)
1466 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
1467 // if it's still there, nuke it
1468 if (other)
1469 BecomeExplosion1 (other);
1470 return;
1471 }
1472
1473 if (level.time < self->touch_debounce_time)
1474 return;
1475
1476 if (!self->dmg)
1477 return;
1478 self->touch_debounce_time = level.time + 0.5;
1479 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
1480 }
1481
1482 void train_wait (edict_t *self)
1483 {
1484 if (self->target_ent->pathtarget)
1485 {
1486 char *savetarget;
1487 edict_t *ent;
1488
1489 ent = self->target_ent;
1490 savetarget = ent->target;
1491 ent->target = ent->pathtarget;
1492 G_UseTargets (ent, self->activator);
1493 ent->target = savetarget;
1494
1495 // make sure we didn't get killed by a killtarget
1496 if (!self->inuse)
1497 return;
1498 }
1499
1500 if (self->moveinfo.wait)
1501 {
1502 if (self->moveinfo.wait > 0)
1503 {
1504 self->nextthink = level.time + self->moveinfo.wait;
1505 self->think = train_next;
1506 }
1507 else if (self->spawnflags & TRAIN_TOGGLE) // && wait < 0
1508 {
1509 train_next (self);
1510 self->spawnflags &= ~TRAIN_START_ON;
1511 VectorClear (self->velocity);
1512 self->nextthink = 0;
1513 }
1514
1515 if (!(self->flags & FL_TEAMSLAVE))
1516 {
1517 if (self->moveinfo.sound_end)
1518 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
1519 self->s.sound = 0;
1520 }
1521 }
1522 else
1523 {
1524 train_next (self);
1525 }
1526
1527 }
1528
1529 void train_next (edict_t *self)
1530 {
1531 edict_t *ent;
1532 vec3_t dest;
1533 qboolean first;
1534
1535 first = true;
1536 again:
1537 if (!self->target)
1538 {
1539 // gi.dprintf ("train_next: no next target\n");
1540 return;
1541 }
1542
1543 ent = G_PickTarget (self->target);
1544 if (!ent)
1545 {
1546 gi.dprintf ("train_next: bad target %s\n", self->target);
1547 return;
1548 }
1549
1550 self->target = ent->target;
1551
1552 // check for a teleport path_corner
1553 if (ent->spawnflags & 1)
1554 {
1555 if (!first)
1556 {
1557 gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin));
1558 return;
1559 }
1560 first = false;
1561 VectorSubtract (ent->s.origin, self->mins, self->s.origin);
1562 VectorCopy (self->s.origin, self->s.old_origin);
1563 self->s.event = EV_OTHER_TELEPORT;
1564 gi.linkentity (self);
1565 goto again;
1566 }
1567
1568 self->moveinfo.wait = ent->wait;
1569 self->target_ent = ent;
1570
1571 if (!(self->flags & FL_TEAMSLAVE))
1572 {
1573 if (self->moveinfo.sound_start)
1574 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
1575 self->s.sound = self->moveinfo.sound_middle;
1576 }
1577
1578 VectorSubtract (ent->s.origin, self->mins, dest);
1579 self->moveinfo.state = STATE_TOP;
1580 VectorCopy (self->s.origin, self->moveinfo.start_origin);
1581 VectorCopy (dest, self->moveinfo.end_origin);
1582 Move_Calc (self, dest, train_wait);
1583 self->spawnflags |= TRAIN_START_ON;
1584 }
1585
1586 void train_resume (edict_t *self)
1587 {
1588 edict_t *ent;
1589 vec3_t dest;
1590
1591 ent = self->target_ent;
1592
1593 VectorSubtract (ent->s.origin, self->mins, dest);
1594 self->moveinfo.state = STATE_TOP;
1595 VectorCopy (self->s.origin, self->moveinfo.start_origin);
1596 VectorCopy (dest, self->moveinfo.end_origin);
1597 Move_Calc (self, dest, train_wait);
1598 self->spawnflags |= TRAIN_START_ON;
1599 }
1600
1601 void func_train_find (edict_t *self)
1602 {
1603 edict_t *ent;
1604
1605 if (!self->target)
1606 {
1607 gi.dprintf ("train_find: no target\n");
1608 return;
1609 }
1610 ent = G_PickTarget (self->target);
1611 if (!ent)
1612 {
1613 gi.dprintf ("train_find: target %s not found\n", self->target);
1614 return;
1615 }
1616 self->target = ent->target;
1617
1618 VectorSubtract (ent->s.origin, self->mins, self->s.origin);
1619 gi.linkentity (self);
1620
1621 // if not triggered, start immediately
1622 if (!self->targetname)
1623 self->spawnflags |= TRAIN_START_ON;
1624
1625 if (self->spawnflags & TRAIN_START_ON)
1626 {
1627 self->nextthink = level.time + FRAMETIME;
1628 self->think = train_next;
1629 self->activator = self;
1630 }
1631 }
1632
1633 void train_use (edict_t *self, edict_t *other, edict_t *activator)
1634 {
1635 self->activator = activator;
1636
1637 if (self->spawnflags & TRAIN_START_ON)
1638 {
1639 if (!(self->spawnflags & TRAIN_TOGGLE))
1640 return;
1641 self->spawnflags &= ~TRAIN_START_ON;
1642 VectorClear (self->velocity);
1643 self->nextthink = 0;
1644 }
1645 else
1646 {
1647 if (self->target_ent)
1648 train_resume(self);
1649 else
1650 train_next(self);
1651 }
1652 }
1653
1654 void SP_func_train (edict_t *self)
1655 {
1656 self->movetype = MOVETYPE_PUSH;
1657
1658 VectorClear (self->s.angles);
1659 self->blocked = train_blocked;
1660 if (self->spawnflags & TRAIN_BLOCK_STOPS)
1661 self->dmg = 0;
1662 else
1663 {
1664 if (!self->dmg)
1665 self->dmg = 100;
1666 }
1667 self->solid = SOLID_BSP;
1668 gi.setmodel (self, self->model);
1669
1670 if (st.noise)
1671 self->moveinfo.sound_middle = gi.soundindex (st.noise);
1672
1673 if (!self->speed)
1674 self->speed = 100;
1675
1676 self->moveinfo.speed = self->speed;
1677 self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
1678
1679 self->use = train_use;
1680
1681 gi.linkentity (self);
1682
1683 if (self->target)
1684 {
1685 // start trains on the second frame, to make sure their targets have had
1686 // a chance to spawn
1687 self->nextthink = level.time + FRAMETIME;
1688 self->think = func_train_find;
1689 }
1690 else
1691 {
1692 gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin));
1693 }
1694 }
1695
1696
1697 /*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
1698 */
1699 void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator)
1700 {
1701 edict_t *target;
1702
1703 if (self->movetarget->nextthink)
1704 {
1705 // gi.dprintf("elevator busy\n");
1706 return;
1707 }
1708
1709 if (!other->pathtarget)
1710 {
1711 gi.dprintf("elevator used with no pathtarget\n");
1712 return;
1713 }
1714
1715 target = G_PickTarget (other->pathtarget);
1716 if (!target)
1717 {
1718 gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget);
1719 return;
1720 }
1721
1722 self->movetarget->target_ent = target;
1723 train_resume (self->movetarget);
1724 }
1725
1726 void trigger_elevator_init (edict_t *self)
1727 {
1728 if (!self->target)
1729 {
1730 gi.dprintf("trigger_elevator has no target\n");
1731 return;
1732 }
1733 self->movetarget = G_PickTarget (self->target);
1734 if (!self->movetarget)
1735 {
1736 gi.dprintf("trigger_elevator unable to find target %s\n", self->target);
1737 return;
1738 }
1739 if (strcmp(self->movetarget->classname, "func_train") != 0)
1740 {
1741 gi.dprintf("trigger_elevator target %s is not a train\n", self->target);
1742 return;
1743 }
1744
1745 self->use = trigger_elevator_use;
1746 self->svflags = SVF_NOCLIENT;
1747
1748 }
1749
1750 void SP_trigger_elevator (edict_t *self)
1751 {
1752 self->think = trigger_elevator_init;
1753 self->nextthink = level.time + FRAMETIME;
1754 }
1755
1756
1757 /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
1758 "wait" base time between triggering all targets, default is 1
1759 "random" wait variance, default is 0
1760
1761 so, the basic time between firing is a random time between
1762 (wait - random) and (wait + random)
1763
1764 "delay" delay before first firing when turned on, default is 0
1765
1766 "pausetime" additional delay used only the very first time
1767 and only if spawned with START_ON
1768
1769 These can used but not touched.
1770 */
1771 void func_timer_think (edict_t *self)
1772 {
1773 G_UseTargets (self, self->activator);
1774 self->nextthink = level.time + self->wait + crandom() * self->random;
1775 }
1776
1777 void func_timer_use (edict_t *self, edict_t *other, edict_t *activator)
1778 {
1779 self->activator = activator;
1780
1781 // if on, turn it off
1782 if (self->nextthink)
1783 {
1784 self->nextthink = 0;
1785 return;
1786 }
1787
1788 // turn it on
1789 if (self->delay)
1790 self->nextthink = level.time + self->delay;
1791 else
1792 func_timer_think (self);
1793 }
1794
1795 void SP_func_timer (edict_t *self)
1796 {
1797 if (!self->wait)
1798 self->wait = 1.0;
1799
1800 self->use = func_timer_use;
1801 self->think = func_timer_think;
1802
1803 if (self->random >= self->wait)
1804 {
1805 self->random = self->wait - FRAMETIME;
1806 gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin));
1807 }
1808
1809 if (self->spawnflags & 1)
1810 {
1811 self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random;
1812 self->activator = self;
1813 }
1814
1815 self->svflags = SVF_NOCLIENT;
1816 }
1817
1818
1819 /*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE
1820 Conveyors are stationary brushes that move what's on them.
1821 The brush should be have a surface with at least one current content enabled.
1822 speed default 100
1823 */
1824
1825 void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator)
1826 {
1827 if (self->spawnflags & 1)
1828 {
1829 self->speed = 0;
1830 self->spawnflags &= ~1;
1831 }
1832 else
1833 {
1834 self->speed = self->count;
1835 self->spawnflags |= 1;
1836 }
1837
1838 if (!(self->spawnflags & 2))
1839 self->count = 0;
1840 }
1841
1842 void SP_func_conveyor (edict_t *self)
1843 {
1844 if (!self->speed)
1845 self->speed = 100;
1846
1847 if (!(self->spawnflags & 1))
1848 {
1849 self->count = self->speed;
1850 self->speed = 0;
1851 }
1852
1853 self->use = func_conveyor_use;
1854
1855 gi.setmodel (self, self->model);
1856 self->solid = SOLID_BSP;
1857 gi.linkentity (self);
1858 }
1859
1860
1861 /*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down
1862 A secret door. Slide back and then to the side.
1863
1864 open_once doors never closes
1865 1st_left 1st move is left of arrow
1866 1st_down 1st move is down from arrow
1867 always_shoot door is shootebale even if targeted
1868
1869 "angle" determines the direction
1870 "dmg" damage to inflic when blocked (default 2)
1871 "wait" how long to hold in the open position (default 5, -1 means hold)
1872 */
1873
1874 #define SECRET_ALWAYS_SHOOT 1
1875 #define SECRET_1ST_LEFT 2
1876 #define SECRET_1ST_DOWN 4
1877
1878 void door_secret_move1 (edict_t *self);
1879 void door_secret_move2 (edict_t *self);
1880 void door_secret_move3 (edict_t *self);
1881 void door_secret_move4 (edict_t *self);
1882 void door_secret_move5 (edict_t *self);
1883 void door_secret_move6 (edict_t *self);
1884 void door_secret_done (edict_t *self);
1885
1886 void door_secret_use (edict_t *self, edict_t *other, edict_t *activator)
1887 {
1888 // make sure we're not already moving
1889 if (!VectorCompare(self->s.origin, vec3_origin))
1890 return;
1891
1892 Move_Calc (self, self->pos1, door_secret_move1);
1893 door_use_areaportals (self, true);
1894 }
1895
1896 void door_secret_move1 (edict_t *self)
1897 {
1898 self->nextthink = level.time + 1.0;
1899 self->think = door_secret_move2;
1900 }
1901
1902 void door_secret_move2 (edict_t *self)
1903 {
1904 Move_Calc (self, self->pos2, door_secret_move3);
1905 }
1906
1907 void door_secret_move3 (edict_t *self)
1908 {
1909 if (self->wait == -1)
1910 return;
1911 self->nextthink = level.time + self->wait;
1912 self->think = door_secret_move4;
1913 }
1914
1915 void door_secret_move4 (edict_t *self)
1916 {
1917 Move_Calc (self, self->pos1, door_secret_move5);
1918 }
1919
1920 void door_secret_move5 (edict_t *self)
1921 {
1922 self->nextthink = level.time + 1.0;
1923 self->think = door_secret_move6;
1924 }
1925
1926 void door_secret_move6 (edict_t *self)
1927 {
1928 Move_Calc (self, vec3_origin, door_secret_done);
1929 }
1930
1931 void door_secret_done (edict_t *self)
1932 {
1933 if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT))
1934 {
1935 self->health = 0;
1936 self->takedamage = DAMAGE_YES;
1937 }
1938 door_use_areaportals (self, false);
1939 }
1940
1941 void door_secret_blocked (edict_t *self, edict_t *other)
1942 {
1943 if (!(other->svflags & SVF_MONSTER) && (!other->client) )
1944 {
1945 // give it a chance to go away on it's own terms (like gibs)
1946 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
1947 // if it's still there, nuke it
1948 if (other)
1949 BecomeExplosion1 (other);
1950 return;
1951 }
1952
1953 if (level.time < self->touch_debounce_time)
1954 return;
1955 self->touch_debounce_time = level.time + 0.5;
1956
1957 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
1958 }
1959
1960 void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
1961 {
1962 self->takedamage = DAMAGE_NO;
1963 door_secret_use (self, attacker, attacker);
1964 }
1965
1966 void SP_func_door_secret (edict_t *ent)
1967 {
1968 vec3_t forward, right, up;
1969 float side;
1970 float width;
1971 float length;
1972
1973 ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav");
1974 ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav");
1975 ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav");
1976
1977 ent->movetype = MOVETYPE_PUSH;
1978 ent->solid = SOLID_BSP;
1979 gi.setmodel (ent, ent->model);
1980
1981 ent->blocked = door_secret_blocked;
1982 ent->use = door_secret_use;
1983
1984 if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT))
1985 {
1986 ent->health = 0;
1987 ent->takedamage = DAMAGE_YES;
1988 ent->die = door_secret_die;
1989 }
1990
1991 if (!ent->dmg)
1992 ent->dmg = 2;
1993
1994 if (!ent->wait)
1995 ent->wait = 5;
1996
1997 ent->moveinfo.accel =
1998 ent->moveinfo.decel =
1999 ent->moveinfo.speed = 50;
2000
2001 // calculate positions
2002 AngleVectors (ent->s.angles, forward, right, up);
2003 VectorClear (ent->s.angles);
2004 side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT);
2005 if (ent->spawnflags & SECRET_1ST_DOWN)
2006 width = fabs(DotProduct(up, ent->size));
2007 else
2008 width = fabs(DotProduct(right, ent->size));
2009 length = fabs(DotProduct(forward, ent->size));
2010 if (ent->spawnflags & SECRET_1ST_DOWN)
2011 VectorMA (ent->s.origin, -1 * width, up, ent->pos1);
2012 else
2013 VectorMA (ent->s.origin, side * width, right, ent->pos1);
2014 VectorMA (ent->pos1, length, forward, ent->pos2);
2015
2016 if (ent->health)
2017 {
2018 ent->takedamage = DAMAGE_YES;
2019 ent->die = door_killed;
2020 ent->max_health = ent->health;
2021 }
2022 else if (ent->targetname && ent->message)
2023 {
2024 gi.soundindex ("misc/talk.wav");
2025 ent->touch = door_touch;
2026 }
2027
2028 ent->classname = "func_door";
2029
2030 gi.linkentity (ent);
2031 }
2032
2033
2034 /*QUAKED func_killbox (1 0 0) ?
2035 Kills everything inside when fired, irrespective of protection.
2036 */
2037 void use_killbox (edict_t *self, edict_t *other, edict_t *activator)
2038 {
2039 KillBox (self);
2040 }
2041
2042 void SP_func_killbox (edict_t *ent)
2043 {
2044 gi.setmodel (ent, ent->model);
2045 ent->use = use_killbox;
2046 ent->svflags = SVF_NOCLIENT;
2047 }
2048
2049