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