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
Move_Done(edict_t * ent)76 void Move_Done (edict_t *ent)
77 {
78 VectorClear (ent->velocity);
79 ent->moveinfo.endfunc (ent);
80 }
81
Move_Final(edict_t * ent)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
Move_Begin(edict_t * ent)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
Move_Calc(edict_t * ent,vec3_t dest,void (* func)(edict_t *))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
AngleMove_Done(edict_t * ent)147 void AngleMove_Done (edict_t *ent)
148 {
149 VectorClear (ent->avelocity);
150 ent->moveinfo.endfunc (ent);
151 }
152
AngleMove_Final(edict_t * ent)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
AngleMove_Begin(edict_t * ent)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
AngleMove_Calc(edict_t * ent,void (* func)(edict_t *))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
plat_CalcAcceleratedMove(moveinfo_t * moveinfo)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
plat_Accelerate(moveinfo_t * moveinfo)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
Think_AccelMove(edict_t * ent)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
plat_hit_top(edict_t * ent)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
plat_hit_bottom(edict_t * ent)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
plat_go_down(edict_t * ent)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
plat_go_up(edict_t * ent)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
plat_blocked(edict_t * self,edict_t * other)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
Use_Plat(edict_t * ent,edict_t * other,edict_t * activator)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
Touch_Plat_Center(edict_t * ent,edict_t * other,cplane_t * plane,csurface_t * surf)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
plat_spawn_inside_trigger(edict_t * ent)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 */
SP_func_plat(edict_t * ent)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
rotating_blocked(edict_t * self,edict_t * other)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
rotating_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)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
rotating_use(edict_t * self,edict_t * other,edict_t * activator)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
SP_func_rotating(edict_t * ent)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
button_done(edict_t * self)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
button_return(edict_t * self)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
button_wait(edict_t * self)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
button_fire(edict_t * self)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
button_use(edict_t * self,edict_t * other,edict_t * activator)737 void button_use (edict_t *self, edict_t *other, edict_t *activator)
738 {
739 self->activator = activator;
740 button_fire (self);
741 }
742
button_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)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
button_killed(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)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
SP_func_button(edict_t * ent)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 allready 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
door_use_areaportals(edict_t * self,qboolean open)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
door_hit_top(edict_t * self)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
door_hit_bottom(edict_t * self)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
door_go_down(edict_t * self)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
door_go_up(edict_t * self,edict_t * activator)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
door_use(edict_t * self,edict_t * other,edict_t * activator)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
Touch_DoorTrigger(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)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
Think_CalcMoveSpeed(edict_t * self)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
Think_SpawnDoorTrigger(edict_t * ent)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
door_blocked(edict_t * self,edict_t * other)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
door_killed(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)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
door_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)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
SP_func_door(edict_t * ent)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
SP_func_door_rotating(edict_t * ent)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
SP_func_water(edict_t * self)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
train_blocked(edict_t * self,edict_t * other)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
train_wait(edict_t * self)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
train_next(edict_t * self)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 gi.linkentity (self);
1564 goto again;
1565 }
1566
1567 self->moveinfo.wait = ent->wait;
1568 self->target_ent = ent;
1569
1570 if (!(self->flags & FL_TEAMSLAVE))
1571 {
1572 if (self->moveinfo.sound_start)
1573 gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
1574 self->s.sound = self->moveinfo.sound_middle;
1575 }
1576
1577 VectorSubtract (ent->s.origin, self->mins, dest);
1578 self->moveinfo.state = STATE_TOP;
1579 VectorCopy (self->s.origin, self->moveinfo.start_origin);
1580 VectorCopy (dest, self->moveinfo.end_origin);
1581 Move_Calc (self, dest, train_wait);
1582 self->spawnflags |= TRAIN_START_ON;
1583 }
1584
train_resume(edict_t * self)1585 void train_resume (edict_t *self)
1586 {
1587 edict_t *ent;
1588 vec3_t dest;
1589
1590 ent = self->target_ent;
1591
1592 VectorSubtract (ent->s.origin, self->mins, dest);
1593 self->moveinfo.state = STATE_TOP;
1594 VectorCopy (self->s.origin, self->moveinfo.start_origin);
1595 VectorCopy (dest, self->moveinfo.end_origin);
1596 Move_Calc (self, dest, train_wait);
1597 self->spawnflags |= TRAIN_START_ON;
1598 }
1599
func_train_find(edict_t * self)1600 void func_train_find (edict_t *self)
1601 {
1602 edict_t *ent;
1603
1604 if (!self->target)
1605 {
1606 gi.dprintf ("train_find: no target\n");
1607 return;
1608 }
1609 ent = G_PickTarget (self->target);
1610 if (!ent)
1611 {
1612 gi.dprintf ("train_find: target %s not found\n", self->target);
1613 return;
1614 }
1615 self->target = ent->target;
1616
1617 VectorSubtract (ent->s.origin, self->mins, self->s.origin);
1618 gi.linkentity (self);
1619
1620 // if not triggered, start immediately
1621 if (!self->targetname)
1622 self->spawnflags |= TRAIN_START_ON;
1623
1624 if (self->spawnflags & TRAIN_START_ON)
1625 {
1626 self->nextthink = level.time + FRAMETIME;
1627 self->think = train_next;
1628 self->activator = self;
1629 }
1630 }
1631
train_use(edict_t * self,edict_t * other,edict_t * activator)1632 void train_use (edict_t *self, edict_t *other, edict_t *activator)
1633 {
1634 self->activator = activator;
1635
1636 if (self->spawnflags & TRAIN_START_ON)
1637 {
1638 if (!(self->spawnflags & TRAIN_TOGGLE))
1639 return;
1640 self->spawnflags &= ~TRAIN_START_ON;
1641 VectorClear (self->velocity);
1642 self->nextthink = 0;
1643 }
1644 else
1645 {
1646 if (self->target_ent)
1647 train_resume(self);
1648 else
1649 train_next(self);
1650 }
1651 }
1652
SP_func_train(edict_t * self)1653 void SP_func_train (edict_t *self)
1654 {
1655 self->movetype = MOVETYPE_PUSH;
1656
1657 VectorClear (self->s.angles);
1658 self->blocked = train_blocked;
1659 if (self->spawnflags & TRAIN_BLOCK_STOPS)
1660 self->dmg = 0;
1661 else
1662 {
1663 if (!self->dmg)
1664 self->dmg = 100;
1665 }
1666 self->solid = SOLID_BSP;
1667 gi.setmodel (self, self->model);
1668
1669 if (st.noise)
1670 self->moveinfo.sound_middle = gi.soundindex (st.noise);
1671
1672 if (!self->speed)
1673 self->speed = 100;
1674
1675 self->moveinfo.speed = self->speed;
1676 self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
1677
1678 self->use = train_use;
1679
1680 gi.linkentity (self);
1681
1682 if (self->target)
1683 {
1684 // start trains on the second frame, to make sure their targets have had
1685 // a chance to spawn
1686 self->nextthink = level.time + FRAMETIME;
1687 self->think = func_train_find;
1688 }
1689 else
1690 {
1691 gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin));
1692 }
1693 }
1694
1695
1696 /*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
1697 */
trigger_elevator_use(edict_t * self,edict_t * other,edict_t * activator)1698 void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator)
1699 {
1700 edict_t *target;
1701
1702 if (self->movetarget->nextthink)
1703 {
1704 // gi.dprintf("elevator busy\n");
1705 return;
1706 }
1707
1708 if (!other->pathtarget)
1709 {
1710 gi.dprintf("elevator used with no pathtarget\n");
1711 return;
1712 }
1713
1714 target = G_PickTarget (other->pathtarget);
1715 if (!target)
1716 {
1717 gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget);
1718 return;
1719 }
1720
1721 self->movetarget->target_ent = target;
1722 train_resume (self->movetarget);
1723 }
1724
trigger_elevator_init(edict_t * self)1725 void trigger_elevator_init (edict_t *self)
1726 {
1727 if (!self->target)
1728 {
1729 gi.dprintf("trigger_elevator has no target\n");
1730 return;
1731 }
1732 self->movetarget = G_PickTarget (self->target);
1733 if (!self->movetarget)
1734 {
1735 gi.dprintf("trigger_elevator unable to find target %s\n", self->target);
1736 return;
1737 }
1738 if (strcmp(self->movetarget->classname, "func_train") != 0)
1739 {
1740 gi.dprintf("trigger_elevator target %s is not a train\n", self->target);
1741 return;
1742 }
1743
1744 self->use = trigger_elevator_use;
1745 self->svflags = SVF_NOCLIENT;
1746
1747 }
1748
SP_trigger_elevator(edict_t * self)1749 void SP_trigger_elevator (edict_t *self)
1750 {
1751 self->think = trigger_elevator_init;
1752 self->nextthink = level.time + FRAMETIME;
1753 }
1754
1755
1756 /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
1757 "wait" base time between triggering all targets, default is 1
1758 "random" wait variance, default is 0
1759
1760 so, the basic time between firing is a random time between
1761 (wait - random) and (wait + random)
1762
1763 "delay" delay before first firing when turned on, default is 0
1764
1765 "pausetime" additional delay used only the very first time
1766 and only if spawned with START_ON
1767
1768 These can used but not touched.
1769 */
func_timer_think(edict_t * self)1770 void func_timer_think (edict_t *self)
1771 {
1772 G_UseTargets (self, self->activator);
1773 self->nextthink = level.time + self->wait + crandom() * self->random;
1774 }
1775
func_timer_use(edict_t * self,edict_t * other,edict_t * activator)1776 void func_timer_use (edict_t *self, edict_t *other, edict_t *activator)
1777 {
1778 self->activator = activator;
1779
1780 // if on, turn it off
1781 if (self->nextthink)
1782 {
1783 self->nextthink = 0;
1784 return;
1785 }
1786
1787 // turn it on
1788 if (self->delay)
1789 self->nextthink = level.time + self->delay;
1790 else
1791 func_timer_think (self);
1792 }
1793
SP_func_timer(edict_t * self)1794 void SP_func_timer (edict_t *self)
1795 {
1796 if (!self->wait)
1797 self->wait = 1.0;
1798
1799 self->use = func_timer_use;
1800 self->think = func_timer_think;
1801
1802 if (self->random >= self->wait)
1803 {
1804 self->random = self->wait - FRAMETIME;
1805 gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin));
1806 }
1807
1808 if (self->spawnflags & 1)
1809 {
1810 self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random;
1811 self->activator = self;
1812 }
1813
1814 self->svflags = SVF_NOCLIENT;
1815 }
1816
1817
1818 /*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE
1819 Conveyors are stationary brushes that move what's on them.
1820 The brush should be have a surface with at least one current content enabled.
1821 speed default 100
1822 */
1823
func_conveyor_use(edict_t * self,edict_t * other,edict_t * activator)1824 void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator)
1825 {
1826 if (self->spawnflags & 1)
1827 {
1828 self->speed = 0;
1829 self->spawnflags &= ~1;
1830 }
1831 else
1832 {
1833 self->speed = self->count;
1834 self->spawnflags |= 1;
1835 }
1836
1837 if (!(self->spawnflags & 2))
1838 self->count = 0;
1839 }
1840
SP_func_conveyor(edict_t * self)1841 void SP_func_conveyor (edict_t *self)
1842 {
1843 if (!self->speed)
1844 self->speed = 100;
1845
1846 if (!(self->spawnflags & 1))
1847 {
1848 self->count = self->speed;
1849 self->speed = 0;
1850 }
1851
1852 self->use = func_conveyor_use;
1853
1854 gi.setmodel (self, self->model);
1855 self->solid = SOLID_BSP;
1856 gi.linkentity (self);
1857 }
1858
1859
1860 /*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down
1861 A secret door. Slide back and then to the side.
1862
1863 open_once doors never closes
1864 1st_left 1st move is left of arrow
1865 1st_down 1st move is down from arrow
1866 always_shoot door is shootebale even if targeted
1867
1868 "angle" determines the direction
1869 "dmg" damage to inflic when blocked (default 2)
1870 "wait" how long to hold in the open position (default 5, -1 means hold)
1871 */
1872
1873 #define SECRET_ALWAYS_SHOOT 1
1874 #define SECRET_1ST_LEFT 2
1875 #define SECRET_1ST_DOWN 4
1876
1877 void door_secret_move1 (edict_t *self);
1878 void door_secret_move2 (edict_t *self);
1879 void door_secret_move3 (edict_t *self);
1880 void door_secret_move4 (edict_t *self);
1881 void door_secret_move5 (edict_t *self);
1882 void door_secret_move6 (edict_t *self);
1883 void door_secret_done (edict_t *self);
1884
door_secret_use(edict_t * self,edict_t * other,edict_t * activator)1885 void door_secret_use (edict_t *self, edict_t *other, edict_t *activator)
1886 {
1887 // make sure we're not already moving
1888 if (!VectorCompare(self->s.origin, vec3_origin))
1889 return;
1890
1891 Move_Calc (self, self->pos1, door_secret_move1);
1892 door_use_areaportals (self, true);
1893 }
1894
door_secret_move1(edict_t * self)1895 void door_secret_move1 (edict_t *self)
1896 {
1897 self->nextthink = level.time + 1.0;
1898 self->think = door_secret_move2;
1899 }
1900
door_secret_move2(edict_t * self)1901 void door_secret_move2 (edict_t *self)
1902 {
1903 Move_Calc (self, self->pos2, door_secret_move3);
1904 }
1905
door_secret_move3(edict_t * self)1906 void door_secret_move3 (edict_t *self)
1907 {
1908 if (self->wait == -1)
1909 return;
1910 self->nextthink = level.time + self->wait;
1911 self->think = door_secret_move4;
1912 }
1913
door_secret_move4(edict_t * self)1914 void door_secret_move4 (edict_t *self)
1915 {
1916 Move_Calc (self, self->pos1, door_secret_move5);
1917 }
1918
door_secret_move5(edict_t * self)1919 void door_secret_move5 (edict_t *self)
1920 {
1921 self->nextthink = level.time + 1.0;
1922 self->think = door_secret_move6;
1923 }
1924
door_secret_move6(edict_t * self)1925 void door_secret_move6 (edict_t *self)
1926 {
1927 Move_Calc (self, vec3_origin, door_secret_done);
1928 }
1929
door_secret_done(edict_t * self)1930 void door_secret_done (edict_t *self)
1931 {
1932 if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT))
1933 {
1934 self->health = 0;
1935 self->takedamage = DAMAGE_YES;
1936 }
1937 door_use_areaportals (self, false);
1938 }
1939
door_secret_blocked(edict_t * self,edict_t * other)1940 void door_secret_blocked (edict_t *self, edict_t *other)
1941 {
1942 if (!(other->svflags & SVF_MONSTER) && (!other->client) )
1943 {
1944 // give it a chance to go away on it's own terms (like gibs)
1945 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
1946 // if it's still there, nuke it
1947 if (other)
1948 BecomeExplosion1 (other);
1949 return;
1950 }
1951
1952 if (level.time < self->touch_debounce_time)
1953 return;
1954 self->touch_debounce_time = level.time + 0.5;
1955
1956 T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
1957 }
1958
door_secret_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)1959 void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
1960 {
1961 self->takedamage = DAMAGE_NO;
1962 door_secret_use (self, attacker, attacker);
1963 }
1964
SP_func_door_secret(edict_t * ent)1965 void SP_func_door_secret (edict_t *ent)
1966 {
1967 vec3_t forward, right, up;
1968 float side;
1969 float width;
1970 float length;
1971
1972 ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav");
1973 ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav");
1974 ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav");
1975
1976 ent->movetype = MOVETYPE_PUSH;
1977 ent->solid = SOLID_BSP;
1978 gi.setmodel (ent, ent->model);
1979
1980 ent->blocked = door_secret_blocked;
1981 ent->use = door_secret_use;
1982
1983 if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT))
1984 {
1985 ent->health = 0;
1986 ent->takedamage = DAMAGE_YES;
1987 ent->die = door_secret_die;
1988 }
1989
1990 if (!ent->dmg)
1991 ent->dmg = 2;
1992
1993 if (!ent->wait)
1994 ent->wait = 5;
1995
1996 ent->moveinfo.accel =
1997 ent->moveinfo.decel =
1998 ent->moveinfo.speed = 50;
1999
2000 // calculate positions
2001 AngleVectors (ent->s.angles, forward, right, up);
2002 VectorClear (ent->s.angles);
2003 side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT);
2004 if (ent->spawnflags & SECRET_1ST_DOWN)
2005 width = fabs(DotProduct(up, ent->size));
2006 else
2007 width = fabs(DotProduct(right, ent->size));
2008 length = fabs(DotProduct(forward, ent->size));
2009 if (ent->spawnflags & SECRET_1ST_DOWN)
2010 VectorMA (ent->s.origin, -1 * width, up, ent->pos1);
2011 else
2012 VectorMA (ent->s.origin, side * width, right, ent->pos1);
2013 VectorMA (ent->pos1, length, forward, ent->pos2);
2014
2015 if (ent->health)
2016 {
2017 ent->takedamage = DAMAGE_YES;
2018 ent->die = door_killed;
2019 ent->max_health = ent->health;
2020 }
2021 else if (ent->targetname && ent->message)
2022 {
2023 gi.soundindex ("misc/talk.wav");
2024 ent->touch = door_touch;
2025 }
2026
2027 ent->classname = "func_door";
2028
2029 gi.linkentity (ent);
2030 }
2031
2032
2033 /*QUAKED func_killbox (1 0 0) ?
2034 Kills everything inside when fired, irrespective of protection.
2035 */
use_killbox(edict_t * self,edict_t * other,edict_t * activator)2036 void use_killbox (edict_t *self, edict_t *other, edict_t *activator)
2037 {
2038 KillBox (self);
2039 }
2040
SP_func_killbox(edict_t * ent)2041 void SP_func_killbox (edict_t *ent)
2042 {
2043 gi.setmodel (ent, ent->model);
2044 ent->use = use_killbox;
2045 ent->svflags = SVF_NOCLIENT;
2046 }
2047
2048