1 #include "g_local.h"
2 
3 /*
4 =========================================================
5 
6   PLATS
7 
8   movement options:
9 
10   linear
11   smooth start, hard stop
12   smooth start, smooth stop
13 
14   start
15   end
16   acceleration
17   speed
18   deceleration
19   begin sound
20   end sound
21   target fired when reaching end
22   wait at end
23 
24   object characteristics that use move segments
25   ---------------------------------------------
26   movetype_push, or movetype_stop
27   action when touched
28   action when blocked
29   action when used
30 	disabled?
31   auto trigger spawning
32 
33 
34 =========================================================
35 */
36 
37 #define PLAT_LOW_TRIGGER	1
38 
39 //====
40 //PGM
41 #define PLAT2_TOGGLE			2
42 #define PLAT2_TOP				4
43 #define PLAT2_TRIGGER_TOP		8
44 #define PLAT2_TRIGGER_BOTTOM	16
45 #define PLAT2_BOX_LIFT			32
46 
47 void plat2_spawn_danger_area (edict_t *ent);
48 void plat2_kill_danger_area (edict_t *ent);
49 
50 //PGM
51 //====
52 
53 #define	STATE_TOP			0
54 #define	STATE_BOTTOM		1
55 #define STATE_UP			2
56 #define STATE_DOWN			3
57 
58 #define DOOR_START_OPEN		1
59 #define DOOR_REVERSE		2
60 #define DOOR_CRUSHER		4
61 #define DOOR_NOMONSTER		8
62 #define DOOR_TOGGLE			32
63 #define DOOR_X_AXIS			64
64 #define DOOR_Y_AXIS			128
65 // !easy					256
66 // !med						512
67 // !hard					1024
68 // !dm						2048
69 // !coop					4096
70 #define DOOR_INACTIVE		8192
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 //PGM		accelerate as needed
182 	if(ent->moveinfo.speed < ent->speed)
183 	{
184 		ent->moveinfo.speed += ent->accel;
185 		if(ent->moveinfo.speed > ent->speed)
186 			ent->moveinfo.speed = ent->speed;
187 	}
188 //PGM
189 
190 	// set destdelta to the vector needed to move
191 	if (ent->moveinfo.state == STATE_UP)
192 		VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta);
193 	else
194 		VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta);
195 
196 	// calculate length of vector
197 	len = VectorLength (destdelta);
198 
199 	// divide by speed to get time to reach dest
200 	traveltime = len / ent->moveinfo.speed;
201 
202 	if (traveltime < FRAMETIME)
203 	{
204 		AngleMove_Final (ent);
205 		return;
206 	}
207 
208 	frames = floor(traveltime / FRAMETIME);
209 
210 	// scale the destdelta vector by the time spent traveling to get velocity
211 	VectorScale (destdelta, 1.0 / traveltime, ent->avelocity);
212 
213 //PGM
214 	// if we're done accelerating, act as a normal rotation
215 	if(ent->moveinfo.speed >= ent->speed)
216 	{
217 		// set nextthink to trigger a think when dest is reached
218 		ent->nextthink = level.time + frames * FRAMETIME;
219 		ent->think = AngleMove_Final;
220 	}
221 	else
222 	{
223 		ent->nextthink = level.time + FRAMETIME;
224 		ent->think = AngleMove_Begin;
225 	}
226 //PGM
227 }
228 
AngleMove_Calc(edict_t * ent,void (* func)(edict_t *))229 void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*))
230 {
231 	VectorClear (ent->avelocity);
232 	ent->moveinfo.endfunc = func;
233 
234 //PGM
235 	// if we're supposed to accelerate, this will tell anglemove_begin to do so
236 	if(ent->accel != ent->speed)
237 		ent->moveinfo.speed = 0;
238 //PGM
239 
240 	if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
241 	{
242 		AngleMove_Begin (ent);
243 	}
244 	else
245 	{
246 		ent->nextthink = level.time + FRAMETIME;
247 		ent->think = AngleMove_Begin;
248 	}
249 }
250 
251 
252 /*
253 ==============
254 Think_AccelMove
255 
256 The team has completed a frame of movement, so
257 change the speed for the next frame
258 ==============
259 */
260 #define AccelerationDistance(target, rate)	(target * ((target / rate) + 1) / 2)
261 
plat_CalcAcceleratedMove(moveinfo_t * moveinfo)262 void plat_CalcAcceleratedMove(moveinfo_t *moveinfo)
263 {
264 	float	accel_dist;
265 	float	decel_dist;
266 
267 	moveinfo->move_speed = moveinfo->speed;
268 
269 	if (moveinfo->remaining_distance < moveinfo->accel)
270 	{
271 		moveinfo->current_speed = moveinfo->remaining_distance;
272 		return;
273 	}
274 
275 	accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel);
276 	decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel);
277 
278 	if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0)
279 	{
280 		float	f;
281 
282 		f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel);
283 		moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f);
284 		decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel);
285 	}
286 
287 	moveinfo->decel_distance = decel_dist;
288 };
289 
plat_Accelerate(moveinfo_t * moveinfo)290 void plat_Accelerate (moveinfo_t *moveinfo)
291 {
292 	// are we decelerating?
293 	if (moveinfo->remaining_distance <= moveinfo->decel_distance)
294 	{
295 		if (moveinfo->remaining_distance < moveinfo->decel_distance)
296 		{
297 			if (moveinfo->next_speed)
298 			{
299 				moveinfo->current_speed = moveinfo->next_speed;
300 				moveinfo->next_speed = 0;
301 				return;
302 			}
303 			if (moveinfo->current_speed > moveinfo->decel)
304 				moveinfo->current_speed -= moveinfo->decel;
305 		}
306 		return;
307 	}
308 
309 	// are we at full speed and need to start decelerating during this move?
310 	if (moveinfo->current_speed == moveinfo->move_speed)
311 		if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance)
312 		{
313 			float	p1_distance;
314 			float	p2_distance;
315 			float	distance;
316 
317 			p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
318 			p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed));
319 			distance = p1_distance + p2_distance;
320 			moveinfo->current_speed = moveinfo->move_speed;
321 			moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
322 			return;
323 		}
324 
325 	// are we accelerating?
326 	if (moveinfo->current_speed < moveinfo->speed)
327 	{
328 		float	old_speed;
329 		float	p1_distance;
330 		float	p1_speed;
331 		float	p2_distance;
332 		float	distance;
333 
334 		old_speed = moveinfo->current_speed;
335 
336 		// figure simple acceleration up to move_speed
337 		moveinfo->current_speed += moveinfo->accel;
338 		if (moveinfo->current_speed > moveinfo->speed)
339 			moveinfo->current_speed = moveinfo->speed;
340 
341 		// are we accelerating throughout this entire move?
342 		if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance)
343 			return;
344 
345 		// during this move we will accelrate from current_speed to move_speed
346 		// and cross over the decel_distance; figure the average speed for the
347 		// entire move
348 		p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
349 		p1_speed = (old_speed + moveinfo->move_speed) / 2.0;
350 		p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed));
351 		distance = p1_distance + p2_distance;
352 		moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance));
353 		moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
354 		return;
355 	}
356 
357 	// we are at constant velocity (move_speed)
358 	return;
359 };
360 
Think_AccelMove(edict_t * ent)361 void Think_AccelMove (edict_t *ent)
362 {
363 	ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed;
364 
365 	// PGM 04/21/98  - this should fix sthoms' sinking drop pod. Hopefully it wont break stuff.
366 //	if (ent->moveinfo.current_speed == 0)		// starting or blocked
367 		plat_CalcAcceleratedMove(&ent->moveinfo);
368 
369 	plat_Accelerate (&ent->moveinfo);
370 
371 	// will the entire move complete on next frame?
372 	if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed)
373 	{
374 		Move_Final (ent);
375 		return;
376 	}
377 
378 	VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity);
379 	ent->nextthink = level.time + FRAMETIME;
380 	ent->think = Think_AccelMove;
381 }
382 
383 
384 void plat_go_down (edict_t *ent);
385 
plat_hit_top(edict_t * ent)386 void plat_hit_top (edict_t *ent)
387 {
388 	if (!(ent->flags & FL_TEAMSLAVE))
389 	{
390 		if (ent->moveinfo.sound_end)
391 			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
392 		ent->s.sound = 0;
393 	}
394 	ent->moveinfo.state = STATE_TOP;
395 
396 	ent->think = plat_go_down;
397 	ent->nextthink = level.time + 3;
398 }
399 
plat_hit_bottom(edict_t * ent)400 void plat_hit_bottom (edict_t *ent)
401 {
402 	if (!(ent->flags & FL_TEAMSLAVE))
403 	{
404 		if (ent->moveinfo.sound_end)
405 			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
406 		ent->s.sound = 0;
407 	}
408 	ent->moveinfo.state = STATE_BOTTOM;
409 
410 	plat2_kill_danger_area (ent);		// PGM
411 }
412 
plat_go_down(edict_t * ent)413 void plat_go_down (edict_t *ent)
414 {
415 	if (!(ent->flags & FL_TEAMSLAVE))
416 	{
417 		if (ent->moveinfo.sound_start)
418 			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
419 		ent->s.sound = ent->moveinfo.sound_middle;
420 	}
421 	ent->moveinfo.state = STATE_DOWN;
422 	Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom);
423 }
424 
plat_go_up(edict_t * ent)425 void plat_go_up (edict_t *ent)
426 {
427 	if (!(ent->flags & FL_TEAMSLAVE))
428 	{
429 		if (ent->moveinfo.sound_start)
430 			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
431 		ent->s.sound = ent->moveinfo.sound_middle;
432 	}
433 	ent->moveinfo.state = STATE_UP;
434 	Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top);
435 
436 	plat2_spawn_danger_area(ent);	// PGM
437 }
438 
plat_blocked(edict_t * self,edict_t * other)439 void plat_blocked (edict_t *self, edict_t *other)
440 {
441 	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
442 	{
443 		// give it a chance to go away on it's own terms (like gibs)
444 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
445 		// if it's still there, nuke it
446 		if (other && other->inuse)		// PGM
447 			BecomeExplosion1 (other);
448 		return;
449 	}
450 
451 //PGM
452 	// gib dead things
453 	if(other->health < 1)
454 	{
455 		T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, 0, MOD_CRUSH);
456 	}
457 //PGM
458 
459 	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
460 
461 	if (self->moveinfo.state == STATE_UP)
462 		plat_go_down (self);
463 	else if (self->moveinfo.state == STATE_DOWN)
464 		plat_go_up (self);
465 }
466 
467 
Use_Plat(edict_t * ent,edict_t * other,edict_t * activator)468 void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator)
469 {
470 //======
471 //ROGUE
472 	// if a monster is using us, then allow the activity when stopped.
473 	if (other->svflags & SVF_MONSTER)
474 	{
475 		if (ent->moveinfo.state == STATE_TOP)
476 			plat_go_down (ent);
477 		else if (ent->moveinfo.state == STATE_BOTTOM)
478 			plat_go_up (ent);
479 
480 		return;
481 	}
482 //ROGUE
483 //======
484 
485 	if (ent->think)
486 		return;		// already down
487 	plat_go_down (ent);
488 }
489 
490 
Touch_Plat_Center(edict_t * ent,edict_t * other,cplane_t * plane,csurface_t * surf)491 void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
492 {
493 	if (!other->client)
494 		return;
495 
496 	if (other->health <= 0)
497 		return;
498 
499 	ent = ent->enemy;	// now point at the plat, not the trigger
500 	if (ent->moveinfo.state == STATE_BOTTOM)
501 		plat_go_up (ent);
502 	else if (ent->moveinfo.state == STATE_TOP)
503 		ent->nextthink = level.time + 1;	// the player is still on the plat, so delay going down
504 }
505 
506 // PGM - plat2's change the trigger field
507 //void plat_spawn_inside_trigger (edict_t *ent)
plat_spawn_inside_trigger(edict_t * ent)508 edict_t *plat_spawn_inside_trigger (edict_t *ent)
509 {
510 	edict_t	*trigger;
511 	vec3_t	tmin, tmax;
512 
513 //
514 // middle trigger
515 //
516 	trigger = G_Spawn();
517 	trigger->touch = Touch_Plat_Center;
518 	trigger->movetype = MOVETYPE_NONE;
519 	trigger->solid = SOLID_TRIGGER;
520 	trigger->enemy = ent;
521 
522 	tmin[0] = ent->mins[0] + 25;
523 	tmin[1] = ent->mins[1] + 25;
524 	tmin[2] = ent->mins[2];
525 
526 	tmax[0] = ent->maxs[0] - 25;
527 	tmax[1] = ent->maxs[1] - 25;
528 	tmax[2] = ent->maxs[2] + 8;
529 
530 	tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip);
531 
532 	if (ent->spawnflags & PLAT_LOW_TRIGGER)
533 		tmax[2] = tmin[2] + 8;
534 
535 	if (tmax[0] - tmin[0] <= 0)
536 	{
537 		tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5;
538 		tmax[0] = tmin[0] + 1;
539 	}
540 	if (tmax[1] - tmin[1] <= 0)
541 	{
542 		tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5;
543 		tmax[1] = tmin[1] + 1;
544 	}
545 
546 	VectorCopy (tmin, trigger->mins);
547 	VectorCopy (tmax, trigger->maxs);
548 
549 	gi.linkentity (trigger);
550 
551 	return trigger;			// PGM 11/17/97
552 }
553 
554 
555 /*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER
556 speed	default 150
557 
558 Plats are always drawn in the extended position, so they will light correctly.
559 
560 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.
561 
562 "speed"	overrides default 200.
563 "accel" overrides default 500
564 "lip"	overrides default 8 pixel lip
565 
566 If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height.
567 
568 Set "sounds" to one of the following:
569 1) base fast
570 2) chain slow
571 */
SP_func_plat(edict_t * ent)572 void SP_func_plat (edict_t *ent)
573 {
574 	VectorClear (ent->s.angles);
575 	ent->solid = SOLID_BSP;
576 	ent->movetype = MOVETYPE_PUSH;
577 
578 	gi.setmodel (ent, ent->model);
579 
580 	ent->blocked = plat_blocked;
581 
582 	if (!ent->speed)
583 		ent->speed = 20;
584 	else
585 		ent->speed *= 0.1;
586 
587 	if (!ent->accel)
588 		ent->accel = 5;
589 	else
590 		ent->accel *= 0.1;
591 
592 	if (!ent->decel)
593 		ent->decel = 5;
594 	else
595 		ent->decel *= 0.1;
596 
597 	if (!ent->dmg)
598 		ent->dmg = 2;
599 
600 	if (!st.lip)
601 		st.lip = 8;
602 
603 	// pos1 is the top position, pos2 is the bottom
604 	VectorCopy (ent->s.origin, ent->pos1);
605 	VectorCopy (ent->s.origin, ent->pos2);
606 	if (st.height)
607 		ent->pos2[2] -= st.height;
608 	else
609 		ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
610 
611 	ent->use = Use_Plat;
612 
613 	plat_spawn_inside_trigger (ent);	// the "start moving" trigger
614 
615 	if (ent->targetname)
616 	{
617 		ent->moveinfo.state = STATE_UP;
618 	}
619 	else
620 	{
621 		VectorCopy (ent->pos2, ent->s.origin);
622 		gi.linkentity (ent);
623 		ent->moveinfo.state = STATE_BOTTOM;
624 	}
625 
626 	ent->moveinfo.speed = ent->speed;
627 	ent->moveinfo.accel = ent->accel;
628 	ent->moveinfo.decel = ent->decel;
629 	ent->moveinfo.wait = ent->wait;
630 	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
631 	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
632 	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
633 	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
634 
635 	ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav");
636 	ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav");
637 	ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav");
638 }
639 
640 // ==========================================
641 // PLAT 2
642 // ==========================================
643 #define PLAT2_CALLED		1
644 #define PLAT2_MOVING		2
645 #define PLAT2_WAITING		4
646 
647 void plat2_go_down (edict_t *ent);
648 void plat2_go_up (edict_t *ent);
649 
plat2_spawn_danger_area(edict_t * ent)650 void plat2_spawn_danger_area (edict_t *ent)
651 {
652 	vec3_t	mins, maxs;
653 
654 	VectorCopy(ent->mins, mins);
655 	VectorCopy(ent->maxs, maxs);
656 	maxs[2] = ent->mins[2] + 64;
657 
658 	SpawnBadArea(mins, maxs, 0, ent);
659 }
660 
plat2_kill_danger_area(edict_t * ent)661 void plat2_kill_danger_area (edict_t *ent)
662 {
663 	edict_t *t;
664 
665 	t = NULL;
666 	while ((t = G_Find (t, FOFS(classname), "bad_area")))
667 	{
668 		if(t->owner == ent)
669 			G_FreeEdict(t);
670 	}
671 }
672 
plat2_hit_top(edict_t * ent)673 void plat2_hit_top (edict_t *ent)
674 {
675 	if (!(ent->flags & FL_TEAMSLAVE))
676 	{
677 		if (ent->moveinfo.sound_end)
678 			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
679 		ent->s.sound = 0;
680 	}
681 	ent->moveinfo.state = STATE_TOP;
682 
683 	if(ent->plat2flags & PLAT2_CALLED)
684 	{
685 		ent->plat2flags = PLAT2_WAITING;
686 		if(!(ent->spawnflags & PLAT2_TOGGLE))
687 		{
688 			ent->think = plat2_go_down;
689 			ent->nextthink = level.time + 5.0;
690 		}
691 		if(deathmatch->value)
692 			ent->last_move_time = level.time - 1.0;
693 		else
694 			ent->last_move_time = level.time - 2.0;
695 	}
696 	else if(!(ent->spawnflags & PLAT2_TOP) && !(ent->spawnflags & PLAT2_TOGGLE))
697 	{
698 		ent->plat2flags = 0;
699 		ent->think = plat2_go_down;
700 		ent->nextthink = level.time + 2.0;
701 		ent->last_move_time = level.time;
702 	}
703 	else
704 	{
705 		ent->plat2flags = 0;
706 		ent->last_move_time = level.time;
707 	}
708 
709 	G_UseTargets (ent, ent);
710 }
711 
plat2_hit_bottom(edict_t * ent)712 void plat2_hit_bottom (edict_t *ent)
713 {
714 	if (!(ent->flags & FL_TEAMSLAVE))
715 	{
716 		if (ent->moveinfo.sound_end)
717 			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
718 		ent->s.sound = 0;
719 	}
720 	ent->moveinfo.state = STATE_BOTTOM;
721 
722 	if(ent->plat2flags & PLAT2_CALLED)
723 	{
724 		ent->plat2flags = PLAT2_WAITING;
725 		if(!(ent->spawnflags & PLAT2_TOGGLE))
726 		{
727 			ent->think = plat2_go_up;
728 			ent->nextthink = level.time + 5.0;
729 		}
730 		if(deathmatch->value)
731 			ent->last_move_time = level.time - 1.0;
732 		else
733 			ent->last_move_time = level.time - 2.0;
734 	}
735 	else if ((ent->spawnflags & PLAT2_TOP) && !(ent->spawnflags & PLAT2_TOGGLE))
736 	{
737 		ent->plat2flags = 0;
738 		ent->think = plat2_go_up;
739 		ent->nextthink = level.time + 2.0;
740 		ent->last_move_time = level.time;
741 	}
742 	else
743 	{
744 		ent->plat2flags = 0;
745 		ent->last_move_time = level.time;
746 	}
747 
748 	plat2_kill_danger_area (ent);
749 	G_UseTargets (ent, ent);
750 }
751 
plat2_go_down(edict_t * ent)752 void plat2_go_down (edict_t *ent)
753 {
754 	if (!(ent->flags & FL_TEAMSLAVE))
755 	{
756 		if (ent->moveinfo.sound_start)
757 			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
758 		ent->s.sound = ent->moveinfo.sound_middle;
759 	}
760 	ent->moveinfo.state = STATE_DOWN;
761 	ent->plat2flags |= PLAT2_MOVING;
762 
763 	Move_Calc (ent, ent->moveinfo.end_origin, plat2_hit_bottom);
764 }
765 
plat2_go_up(edict_t * ent)766 void plat2_go_up (edict_t *ent)
767 {
768 	if (!(ent->flags & FL_TEAMSLAVE))
769 	{
770 		if (ent->moveinfo.sound_start)
771 			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
772 		ent->s.sound = ent->moveinfo.sound_middle;
773 	}
774 	ent->moveinfo.state = STATE_UP;
775 	ent->plat2flags |= PLAT2_MOVING;
776 
777 	plat2_spawn_danger_area(ent);
778 
779 	Move_Calc (ent, ent->moveinfo.start_origin, plat2_hit_top);
780 }
781 
plat2_operate(edict_t * ent,edict_t * other)782 void plat2_operate (edict_t *ent, edict_t *other)
783 {
784 	int		otherState;
785 	float	pauseTime;
786 	float	platCenter;
787 	edict_t *trigger;
788 
789 	trigger = ent;
790 	ent = ent->enemy;	// now point at the plat, not the trigger
791 
792 	if (ent->plat2flags & PLAT2_MOVING)
793 		return;
794 
795 	if ((ent->last_move_time + 2) > level.time)
796 		return;
797 
798 	platCenter = (trigger->absmin[2] + trigger->absmax[2]) / 2;
799 
800 	if(ent->moveinfo.state == STATE_TOP)
801 	{
802 		otherState = STATE_TOP;
803 		if(ent->spawnflags & PLAT2_BOX_LIFT)
804 		{
805 			if(platCenter > other->s.origin[2])
806 				otherState = STATE_BOTTOM;
807 		}
808 		else
809 		{
810 			if(trigger->absmax[2] > other->s.origin[2])
811 				otherState = STATE_BOTTOM;
812 		}
813 	}
814 	else
815 	{
816 		otherState = STATE_BOTTOM;
817 		if(other->s.origin[2] > platCenter)
818 			otherState = STATE_TOP;
819 	}
820 
821 	ent->plat2flags = PLAT2_MOVING;
822 
823 	if(deathmatch->value)
824 		pauseTime = 0.3;
825 	else
826 		pauseTime = 0.5;
827 
828 	if(ent->moveinfo.state != otherState)
829 	{
830 		ent->plat2flags |= PLAT2_CALLED;
831 		pauseTime = 0.1;
832 	}
833 
834 	ent->last_move_time = level.time;
835 
836 	if(ent->moveinfo.state == STATE_BOTTOM)
837 	{
838 		ent->think = plat2_go_up;
839 		ent->nextthink = level.time + pauseTime;
840 	}
841 	else
842 	{
843 		ent->think = plat2_go_down;
844 		ent->nextthink = level.time + pauseTime;
845 	}
846 }
847 
Touch_Plat_Center2(edict_t * ent,edict_t * other,cplane_t * plane,csurface_t * surf)848 void Touch_Plat_Center2 (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
849 {
850 	// this requires monsters to actively trigger plats, not just step on them.
851 
852 	//FIXME - commented out for E3
853 	//if (!other->client)
854 	//	return;
855 
856 	if (other->health <= 0)
857 		return;
858 
859 	// PMM - don't let non-monsters activate plat2s
860 	if ((!(other->svflags & SVF_MONSTER)) && (!other->client))
861 		return;
862 
863 	plat2_operate(ent, other);
864 }
865 
plat2_blocked(edict_t * self,edict_t * other)866 void plat2_blocked (edict_t *self, edict_t *other)
867 {
868 	if (!(other->svflags & SVF_MONSTER) && (!other->client))
869 	{
870 		// give it a chance to go away on it's own terms (like gibs)
871 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
872 		// if it's still there, nuke it
873 		if(other && other->inuse)
874 			BecomeExplosion1 (other);
875 		return;
876 	}
877 
878 	// gib dead things
879 	if(other->health < 1)
880 	{
881 		T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, 0, MOD_CRUSH);
882 	}
883 
884 	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
885 
886 	if (self->moveinfo.state == STATE_UP)
887 		plat2_go_down (self);
888 	else if (self->moveinfo.state == STATE_DOWN)
889 		plat2_go_up (self);
890 }
891 
Use_Plat2(edict_t * ent,edict_t * other,edict_t * activator)892 void Use_Plat2 (edict_t *ent, edict_t *other, edict_t *activator)
893 {
894 	edict_t		*trigger;
895 	int			i;
896 
897 	if(ent->moveinfo.state > STATE_BOTTOM)
898 		return;
899 	if((ent->last_move_time + 2) > level.time)
900 		return;
901 
902 	for (i = 1, trigger = g_edicts + 1; i < globals.num_edicts; i++, trigger++)
903 	{
904 		if (!trigger->inuse)
905 			continue;
906 		if (trigger->touch == Touch_Plat_Center2)
907 		{
908 			if (trigger->enemy == ent)
909 			{
910 //				Touch_Plat_Center2 (trigger, activator, NULL, NULL);
911 				plat2_operate (trigger, activator);
912 				return;
913 			}
914 		}
915 	}
916 }
917 
plat2_activate(edict_t * ent,edict_t * other,edict_t * activator)918 void plat2_activate (edict_t *ent, edict_t *other, edict_t *activator)
919 {
920 	edict_t *trigger;
921 
922 //	if(ent->targetname)
923 //		ent->targetname[0] = 0;
924 
925 	ent->use = Use_Plat2;
926 
927 	trigger = plat_spawn_inside_trigger (ent);	// the "start moving" trigger
928 
929 	trigger->maxs[0]+=10;
930 	trigger->maxs[1]+=10;
931 	trigger->mins[0]-=10;
932 	trigger->mins[1]-=10;
933 
934 	gi.linkentity (trigger);
935 
936 	trigger->touch = Touch_Plat_Center2;		// Override trigger touch function
937 
938 	plat2_go_down(ent);
939 }
940 
941 /*QUAKED func_plat2 (0 .5 .8) ? PLAT_LOW_TRIGGER PLAT2_TOGGLE PLAT2_TOP PLAT2_TRIGGER_TOP PLAT2_TRIGGER_BOTTOM BOX_LIFT
942 speed	default 150
943 
944 PLAT_LOW_TRIGGER - creates a short trigger field at the bottom
945 PLAT2_TOGGLE - plat will not return to default position.
946 PLAT2_TOP - plat's default position will the the top.
947 PLAT2_TRIGGER_TOP - plat will trigger it's targets each time it hits top
948 PLAT2_TRIGGER_BOTTOM - plat will trigger it's targets each time it hits bottom
949 BOX_LIFT - this indicates that the lift is a box, rather than just a platform
950 
951 Plats are always drawn in the extended position, so they will light correctly.
952 
953 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.
954 
955 "speed"	overrides default 200.
956 "accel" overrides default 500
957 "lip"	no default
958 
959 If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height.
960 
961 */
SP_func_plat2(edict_t * ent)962 void SP_func_plat2 (edict_t *ent)
963 {
964 	edict_t *trigger;
965 
966 	VectorClear (ent->s.angles);
967 	ent->solid = SOLID_BSP;
968 	ent->movetype = MOVETYPE_PUSH;
969 
970 	gi.setmodel (ent, ent->model);
971 
972 	ent->blocked = plat2_blocked;
973 
974 	if (!ent->speed)
975 		ent->speed = 20;
976 	else
977 		ent->speed *= 0.1;
978 
979 	if (!ent->accel)
980 		ent->accel = 5;
981 	else
982 		ent->accel *= 0.1;
983 
984 	if (!ent->decel)
985 		ent->decel = 5;
986 	else
987 		ent->decel *= 0.1;
988 
989 	if (deathmatch->value)
990 	{
991 		ent->speed *= 2;
992 		ent->accel *= 2;
993 		ent->decel *= 2;
994 	}
995 
996 
997 	//PMM Added to kill things it's being blocked by
998 	if (!ent->dmg)
999 		ent->dmg = 2;
1000 
1001 //	if (!st.lip)
1002 //		st.lip = 8;
1003 
1004 	// pos1 is the top position, pos2 is the bottom
1005 	VectorCopy (ent->s.origin, ent->pos1);
1006 	VectorCopy (ent->s.origin, ent->pos2);
1007 
1008 	if (st.height)
1009 		ent->pos2[2] -= (st.height - st.lip);
1010 	else
1011 		ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
1012 
1013 	ent->moveinfo.state = STATE_TOP;
1014 
1015 	if(ent->targetname)
1016 	{
1017 		ent->use = plat2_activate;
1018 	}
1019 	else
1020 	{
1021 		ent->use = Use_Plat2;
1022 
1023 		trigger = plat_spawn_inside_trigger (ent);	// the "start moving" trigger
1024 
1025 		// PGM - debugging??
1026 		trigger->maxs[0]+=10;
1027 		trigger->maxs[1]+=10;
1028 		trigger->mins[0]-=10;
1029 		trigger->mins[1]-=10;
1030 
1031 		gi.linkentity (trigger);
1032 
1033 		trigger->touch = Touch_Plat_Center2;		// Override trigger touch function
1034 
1035 		if(!(ent->spawnflags & PLAT2_TOP))
1036 		{
1037 			VectorCopy (ent->pos2, ent->s.origin);
1038 			ent->moveinfo.state = STATE_BOTTOM;
1039 		}
1040 	}
1041 
1042 	gi.linkentity (ent);
1043 
1044 	ent->moveinfo.speed = ent->speed;
1045 	ent->moveinfo.accel = ent->accel;
1046 	ent->moveinfo.decel = ent->decel;
1047 	ent->moveinfo.wait = ent->wait;
1048 	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
1049 	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
1050 	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
1051 	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
1052 
1053 	ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav");
1054 	ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav");
1055 	ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav");
1056 }
1057 
1058 
1059 //====================================================================
1060 
1061 /*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST EAST MED HARD DM COOP ACCEL
1062 You need to have an origin brush as part of this entity.  The center of that brush will be
1063 the point around which it is rotated. It will rotate around the Z axis by default.  You can
1064 check either the X_AXIS or Y_AXIS box to change that.
1065 
1066 func_rotating will use it's targets when it stops and starts.
1067 
1068 "speed" determines how fast it moves; default value is 100.
1069 "dmg"	damage to inflict when blocked (2 default)
1070 "accel" if specified, is how much the rotation speed will increase per .1sec.
1071 
1072 REVERSE will cause the it to rotate in the opposite direction.
1073 STOP mean it will stop moving instead of pushing entities
1074 ACCEL means it will accelerate to it's final speed and decelerate when shutting down.
1075 */
1076 
1077 //============
1078 //PGM
rotating_accel(edict_t * self)1079 void rotating_accel (edict_t *self)
1080 {
1081 	float	current_speed;
1082 
1083 	current_speed = VectorLength (self->avelocity);
1084 	if(current_speed >= (self->speed - self->accel))		// done
1085 	{
1086 		VectorScale (self->movedir, self->speed, self->avelocity);
1087 		G_UseTargets (self, self);
1088 	}
1089 	else
1090 	{
1091 		current_speed += self->accel;
1092 		VectorScale (self->movedir, current_speed, self->avelocity);
1093 		self->think = rotating_accel;
1094 		self->nextthink = level.time + FRAMETIME;
1095 	}
1096 }
1097 
rotating_decel(edict_t * self)1098 void rotating_decel (edict_t *self)
1099 {
1100 	float	current_speed;
1101 
1102 	current_speed = VectorLength (self->avelocity);
1103 	if(current_speed <= self->decel)		// done
1104 	{
1105 		VectorClear (self->avelocity);
1106 		G_UseTargets (self, self);
1107 		self->touch = NULL;
1108 	}
1109 	else
1110 	{
1111 		current_speed -= self->decel;
1112 		VectorScale (self->movedir, current_speed, self->avelocity);
1113 		self->think = rotating_decel;
1114 		self->nextthink = level.time + FRAMETIME;
1115 	}
1116 }
1117 //PGM
1118 //============
1119 
1120 
rotating_blocked(edict_t * self,edict_t * other)1121 void rotating_blocked (edict_t *self, edict_t *other)
1122 {
1123 	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
1124 }
1125 
rotating_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)1126 void rotating_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
1127 {
1128 	if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2])
1129 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
1130 }
1131 
rotating_use(edict_t * self,edict_t * other,edict_t * activator)1132 void rotating_use (edict_t *self, edict_t *other, edict_t *activator)
1133 {
1134 	if (!VectorCompare (self->avelocity, vec3_origin))
1135 	{
1136 		self->s.sound = 0;
1137 //PGM
1138 		if(self->spawnflags & 8192)	// Decelerate
1139 			rotating_decel (self);
1140 		else
1141 		{
1142 			VectorClear (self->avelocity);
1143 			G_UseTargets (self, self);
1144 			self->touch = NULL;
1145 		}
1146 //PGM
1147 	}
1148 	else
1149 	{
1150 		self->s.sound = self->moveinfo.sound_middle;
1151 //PGM
1152 		if(self->spawnflags & 8192)	// accelerate
1153 			rotating_accel (self);
1154 		else
1155 		{
1156 			VectorScale (self->movedir, self->speed, self->avelocity);
1157 			G_UseTargets (self, self);
1158 		}
1159 		if (self->spawnflags & 16)
1160 			self->touch = rotating_touch;
1161 //PGM
1162 	}
1163 }
1164 
SP_func_rotating(edict_t * ent)1165 void SP_func_rotating (edict_t *ent)
1166 {
1167 	ent->solid = SOLID_BSP;
1168 	if (ent->spawnflags & 32)
1169 		ent->movetype = MOVETYPE_STOP;
1170 	else
1171 		ent->movetype = MOVETYPE_PUSH;
1172 
1173 	// set the axis of rotation
1174 	VectorClear(ent->movedir);
1175 	if (ent->spawnflags & 4)
1176 		ent->movedir[2] = 1.0;
1177 	else if (ent->spawnflags & 8)
1178 		ent->movedir[0] = 1.0;
1179 	else // Z_AXIS
1180 		ent->movedir[1] = 1.0;
1181 
1182 	// check for reverse rotation
1183 	if (ent->spawnflags & 2)
1184 		VectorNegate (ent->movedir, ent->movedir);
1185 
1186 	if (!ent->speed)
1187 		ent->speed = 100;
1188 	if (!ent->dmg)
1189 		ent->dmg = 2;
1190 
1191 //	ent->moveinfo.sound_middle = "doors/hydro1.wav";
1192 
1193 	ent->use = rotating_use;
1194 	if (ent->dmg)
1195 		ent->blocked = rotating_blocked;
1196 
1197 	if (ent->spawnflags & 1)
1198 		ent->use (ent, NULL, NULL);
1199 
1200 	if (ent->spawnflags & 64)
1201 		ent->s.effects |= EF_ANIM_ALL;
1202 	if (ent->spawnflags & 128)
1203 		ent->s.effects |= EF_ANIM_ALLFAST;
1204 
1205 //PGM
1206 	if(ent->spawnflags & 8192)	// Accelerate / Decelerate
1207 	{
1208 		if(!ent->accel)
1209 			ent->accel = 1;
1210 		else if (ent->accel > ent->speed)
1211 			ent->accel = ent->speed;
1212 
1213 		if(!ent->decel)
1214 			ent->decel = 1;
1215 		else if (ent->decel > ent->speed)
1216 			ent->decel = ent->speed;
1217 	}
1218 //PGM
1219 
1220 	gi.setmodel (ent, ent->model);
1221 	gi.linkentity (ent);
1222 }
1223 
1224 /*
1225 ======================================================================
1226 
1227 BUTTONS
1228 
1229 ======================================================================
1230 */
1231 
1232 /*QUAKED func_button (0 .5 .8) ?
1233 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.
1234 
1235 "angle"		determines the opening direction
1236 "target"	all entities with a matching targetname will be used
1237 "speed"		override the default 40 speed
1238 "wait"		override the default 1 second wait (-1 = never return)
1239 "lip"		override the default 4 pixel lip remaining at end of move
1240 "health"	if set, the button must be killed instead of touched
1241 "sounds"
1242 1) silent
1243 2) steam metal
1244 3) wooden clunk
1245 4) metallic click
1246 5) in-out
1247 */
1248 
button_done(edict_t * self)1249 void button_done (edict_t *self)
1250 {
1251 	self->moveinfo.state = STATE_BOTTOM;
1252 	self->s.effects &= ~EF_ANIM23;
1253 	self->s.effects |= EF_ANIM01;
1254 }
1255 
button_return(edict_t * self)1256 void button_return (edict_t *self)
1257 {
1258 	self->moveinfo.state = STATE_DOWN;
1259 
1260 	Move_Calc (self, self->moveinfo.start_origin, button_done);
1261 
1262 	self->s.frame = 0;
1263 
1264 	if (self->health)
1265 		self->takedamage = DAMAGE_YES;
1266 }
1267 
button_wait(edict_t * self)1268 void button_wait (edict_t *self)
1269 {
1270 	self->moveinfo.state = STATE_TOP;
1271 	self->s.effects &= ~EF_ANIM01;
1272 	self->s.effects |= EF_ANIM23;
1273 
1274 	G_UseTargets (self, self->activator);
1275 	self->s.frame = 1;
1276 	if (self->moveinfo.wait >= 0)
1277 	{
1278 		self->nextthink = level.time + self->moveinfo.wait;
1279 		self->think = button_return;
1280 	}
1281 }
1282 
button_fire(edict_t * self)1283 void button_fire (edict_t *self)
1284 {
1285 	if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
1286 		return;
1287 
1288 	self->moveinfo.state = STATE_UP;
1289 	if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE))
1290 		gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
1291 	Move_Calc (self, self->moveinfo.end_origin, button_wait);
1292 }
1293 
button_use(edict_t * self,edict_t * other,edict_t * activator)1294 void button_use (edict_t *self, edict_t *other, edict_t *activator)
1295 {
1296 	self->activator = activator;
1297 	button_fire (self);
1298 }
1299 
button_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)1300 void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
1301 {
1302 	if (!other->client)
1303 		return;
1304 
1305 	if (other->health <= 0)
1306 		return;
1307 
1308 	self->activator = other;
1309 	button_fire (self);
1310 }
1311 
button_killed(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)1312 void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
1313 {
1314 	self->activator = attacker;
1315 	self->health = self->max_health;
1316 	self->takedamage = DAMAGE_NO;
1317 	button_fire (self);
1318 }
1319 
SP_func_button(edict_t * ent)1320 void SP_func_button (edict_t *ent)
1321 {
1322 	vec3_t	abs_movedir;
1323 	float	dist;
1324 
1325 	G_SetMovedir (ent->s.angles, ent->movedir);
1326 	ent->movetype = MOVETYPE_STOP;
1327 	ent->solid = SOLID_BSP;
1328 	gi.setmodel (ent, ent->model);
1329 
1330 	if (ent->sounds != 1)
1331 		ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav");
1332 
1333 	if (!ent->speed)
1334 		ent->speed = 40;
1335 	if (!ent->accel)
1336 		ent->accel = ent->speed;
1337 	if (!ent->decel)
1338 		ent->decel = ent->speed;
1339 
1340 	if (!ent->wait)
1341 		ent->wait = 3;
1342 	if (!st.lip)
1343 		st.lip = 4;
1344 
1345 	VectorCopy (ent->s.origin, ent->pos1);
1346 	abs_movedir[0] = fabs(ent->movedir[0]);
1347 	abs_movedir[1] = fabs(ent->movedir[1]);
1348 	abs_movedir[2] = fabs(ent->movedir[2]);
1349 	dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
1350 	VectorMA (ent->pos1, dist, ent->movedir, ent->pos2);
1351 
1352 	ent->use = button_use;
1353 	ent->s.effects |= EF_ANIM01;
1354 
1355 	if (ent->health)
1356 	{
1357 		ent->max_health = ent->health;
1358 		ent->die = button_killed;
1359 		ent->takedamage = DAMAGE_YES;
1360 	}
1361 	else if (! ent->targetname)
1362 		ent->touch = button_touch;
1363 
1364 	ent->moveinfo.state = STATE_BOTTOM;
1365 
1366 	ent->moveinfo.speed = ent->speed;
1367 	ent->moveinfo.accel = ent->accel;
1368 	ent->moveinfo.decel = ent->decel;
1369 	ent->moveinfo.wait = ent->wait;
1370 	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
1371 	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
1372 	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
1373 	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
1374 
1375 	gi.linkentity (ent);
1376 }
1377 
1378 /*
1379 ======================================================================
1380 
1381 DOORS
1382 
1383   spawn a trigger surrounding the entire team unless it is
1384   already targeted by another
1385 
1386 ======================================================================
1387 */
1388 
1389 /*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST
1390 TOGGLE		wait in both the start and end states for a trigger event.
1391 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).
1392 NOMONSTER	monsters will not trigger this door
1393 
1394 "message"	is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1395 "angle"		determines the opening direction
1396 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1397 "health"	if set, door must be shot open
1398 "speed"		movement speed (100 default)
1399 "wait"		wait before returning (3 default, -1 = never return)
1400 "lip"		lip remaining at end of move (8 default)
1401 "dmg"		damage to inflict when blocked (2 default)
1402 "sounds"
1403 1)	silent
1404 2)	light
1405 3)	medium
1406 4)	heavy
1407 */
1408 
door_use_areaportals(edict_t * self,qboolean open)1409 void door_use_areaportals (edict_t *self, qboolean open)
1410 {
1411 	edict_t	*t = NULL;
1412 
1413 	if (!self->target)
1414 		return;
1415 
1416 	while ((t = G_Find (t, FOFS(targetname), self->target)))
1417 	{
1418 		if (Q_stricmp(t->classname, "func_areaportal") == 0)
1419 		{
1420 			gi.SetAreaPortalState (t->style, open);
1421 		}
1422 	}
1423 }
1424 
1425 void door_go_down (edict_t *self);
1426 
door_hit_top(edict_t * self)1427 void door_hit_top (edict_t *self)
1428 {
1429 	if (!(self->flags & FL_TEAMSLAVE))
1430 	{
1431 		if (self->moveinfo.sound_end)
1432 			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
1433 		self->s.sound = 0;
1434 	}
1435 	self->moveinfo.state = STATE_TOP;
1436 	if (self->spawnflags & DOOR_TOGGLE)
1437 		return;
1438 	if (self->moveinfo.wait >= 0)
1439 	{
1440 		self->think = door_go_down;
1441 		self->nextthink = level.time + self->moveinfo.wait;
1442 	}
1443 }
1444 
door_hit_bottom(edict_t * self)1445 void door_hit_bottom (edict_t *self)
1446 {
1447 	if (!(self->flags & FL_TEAMSLAVE))
1448 	{
1449 		if (self->moveinfo.sound_end)
1450 			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
1451 		self->s.sound = 0;
1452 	}
1453 	self->moveinfo.state = STATE_BOTTOM;
1454 	door_use_areaportals (self, false);
1455 }
1456 
door_go_down(edict_t * self)1457 void door_go_down (edict_t *self)
1458 {
1459 	if (!(self->flags & FL_TEAMSLAVE))
1460 	{
1461 		if (self->moveinfo.sound_start)
1462 			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
1463 		self->s.sound = self->moveinfo.sound_middle;
1464 	}
1465 	if (self->max_health)
1466 	{
1467 		self->takedamage = DAMAGE_YES;
1468 		self->health = self->max_health;
1469 	}
1470 
1471 	self->moveinfo.state = STATE_DOWN;
1472 	if (strcmp(self->classname, "func_door") == 0)
1473 		Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom);
1474 	else if (strcmp(self->classname, "func_door_rotating") == 0)
1475 		AngleMove_Calc (self, door_hit_bottom);
1476 }
1477 
door_go_up(edict_t * self,edict_t * activator)1478 void door_go_up (edict_t *self, edict_t *activator)
1479 {
1480 	if (self->moveinfo.state == STATE_UP)
1481 		return;		// already going up
1482 
1483 	if (self->moveinfo.state == STATE_TOP)
1484 	{	// reset top wait time
1485 		if (self->moveinfo.wait >= 0)
1486 			self->nextthink = level.time + self->moveinfo.wait;
1487 		return;
1488 	}
1489 
1490 	if (!(self->flags & FL_TEAMSLAVE))
1491 	{
1492 		if (self->moveinfo.sound_start)
1493 			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
1494 		self->s.sound = self->moveinfo.sound_middle;
1495 	}
1496 	self->moveinfo.state = STATE_UP;
1497 	if (strcmp(self->classname, "func_door") == 0)
1498 		Move_Calc (self, self->moveinfo.end_origin, door_hit_top);
1499 	else if (strcmp(self->classname, "func_door_rotating") == 0)
1500 		AngleMove_Calc (self, door_hit_top);
1501 
1502 	G_UseTargets (self, activator);
1503 	door_use_areaportals (self, true);
1504 }
1505 
1506 //======
1507 //PGM
smart_water_go_up(edict_t * self)1508 void smart_water_go_up (edict_t *self)
1509 {
1510 	float		distance;
1511 	edict_t		*lowestPlayer;
1512 	edict_t		*ent;
1513 	float		lowestPlayerPt;
1514 	int			i;
1515 
1516 	if (self->moveinfo.state == STATE_TOP)
1517 	{	// reset top wait time
1518 		if (self->moveinfo.wait >= 0)
1519 			self->nextthink = level.time + self->moveinfo.wait;
1520 		return;
1521 	}
1522 
1523 	if (self->health)
1524 	{
1525 		if(self->absmax[2] >= self->health)
1526 		{
1527 			VectorClear (self->velocity);
1528 			self->nextthink = 0;
1529 			self->moveinfo.state = STATE_TOP;
1530 			return;
1531 		}
1532 	}
1533 
1534 	if (!(self->flags & FL_TEAMSLAVE))
1535 	{
1536 		if (self->moveinfo.sound_start)
1537 			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
1538 		self->s.sound = self->moveinfo.sound_middle;
1539 	}
1540 
1541 	// find the lowest player point.
1542 	lowestPlayerPt = 999999;
1543 	lowestPlayer = NULL;
1544 	for (i=0 ; i<game.maxclients ; i++)
1545 	{
1546 		ent = &g_edicts[1+i];
1547 
1548 		// don't count dead or unused player slots
1549 		if((ent->inuse) && (ent->health > 0))
1550 		{
1551 			if (ent->absmin[2] < lowestPlayerPt)
1552 			{
1553 				lowestPlayerPt = ent->absmin[2];
1554 				lowestPlayer = ent;
1555 			}
1556 		}
1557 	}
1558 
1559 	if(!lowestPlayer)
1560 	{
1561 		return;
1562 	}
1563 
1564 	distance = lowestPlayerPt - self->absmax[2];
1565 
1566 	// for the calculations, make sure we intend to go up at least a little.
1567 	if(distance < self->accel)
1568 	{
1569 		distance = 100;
1570 		self->moveinfo.speed = 5;
1571 	}
1572 	else
1573 		self->moveinfo.speed = distance / self->accel;
1574 
1575 	if(self->moveinfo.speed < 5)
1576 		self->moveinfo.speed = 5;
1577 	else if(self->moveinfo.speed > self->speed)
1578 		self->moveinfo.speed = self->speed;
1579 
1580 	// FIXME - should this allow any movement other than straight up?
1581 	VectorSet(self->moveinfo.dir, 0, 0, 1);
1582 	VectorScale (self->moveinfo.dir, self->moveinfo.speed, self->velocity);
1583 	self->moveinfo.remaining_distance = distance;
1584 
1585 	if(self->moveinfo.state != STATE_UP)
1586 	{
1587 		G_UseTargets (self, lowestPlayer);
1588 		door_use_areaportals (self, true);
1589 		self->moveinfo.state = STATE_UP;
1590 	}
1591 
1592 	self->think = smart_water_go_up;
1593 	self->nextthink = level.time + FRAMETIME;
1594 }
1595 //PGM
1596 //======
1597 
door_use(edict_t * self,edict_t * other,edict_t * activator)1598 void door_use (edict_t *self, edict_t *other, edict_t *activator)
1599 {
1600 	edict_t	*ent;
1601 	vec3_t	center;			//PGM
1602 
1603 	if (self->flags & FL_TEAMSLAVE)
1604 		return;
1605 
1606 	if (self->spawnflags & DOOR_TOGGLE)
1607 	{
1608 		if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
1609 		{
1610 			// trigger all paired doors
1611 			for (ent = self ; ent ; ent = ent->teamchain)
1612 			{
1613 				ent->message = NULL;
1614 				ent->touch = NULL;
1615 				door_go_down (ent);
1616 			}
1617 			return;
1618 		}
1619 	}
1620 
1621 //PGM
1622 	// smart water is different
1623 	VectorAdd(self->mins, self->maxs, center);
1624 	VectorScale(center, 0.5, center);
1625 	if ((gi.pointcontents (center) & MASK_WATER) && self->spawnflags & 2)
1626 	{
1627 		self->message = NULL;
1628 		self->touch = NULL;
1629 		self->enemy = activator;
1630 		smart_water_go_up (self);
1631 		return;
1632 	}
1633 //PGM
1634 
1635 	// trigger all paired doors
1636 	for (ent = self ; ent ; ent = ent->teamchain)
1637 	{
1638 		ent->message = NULL;
1639 		ent->touch = NULL;
1640 		door_go_up (ent, activator);
1641 	}
1642 };
1643 
Touch_DoorTrigger(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)1644 void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
1645 {
1646 	if (other->health <= 0)
1647 		return;
1648 
1649 	if (!(other->svflags & SVF_MONSTER) && (!other->client))
1650 		return;
1651 
1652 	if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER))
1653 		return;
1654 
1655 	if (level.time < self->touch_debounce_time)
1656 		return;
1657 	self->touch_debounce_time = level.time + 1.0;
1658 
1659 	door_use (self->owner, other, other);
1660 }
1661 
Think_CalcMoveSpeed(edict_t * self)1662 void Think_CalcMoveSpeed (edict_t *self)
1663 {
1664 	edict_t	*ent;
1665 	float	min;
1666 	float	time;
1667 	float	newspeed;
1668 	float	ratio;
1669 	float	dist;
1670 
1671 	if (self->flags & FL_TEAMSLAVE)
1672 		return;		// only the team master does this
1673 
1674 	// find the smallest distance any member of the team will be moving
1675 	min = fabs(self->moveinfo.distance);
1676 	for (ent = self->teamchain; ent; ent = ent->teamchain)
1677 	{
1678 		dist = fabs(ent->moveinfo.distance);
1679 		if (dist < min)
1680 			min = dist;
1681 	}
1682 
1683 	time = min / self->moveinfo.speed;
1684 
1685 	// adjust speeds so they will all complete at the same time
1686 	for (ent = self; ent; ent = ent->teamchain)
1687 	{
1688 		newspeed = fabs(ent->moveinfo.distance) / time;
1689 		ratio = newspeed / ent->moveinfo.speed;
1690 		if (ent->moveinfo.accel == ent->moveinfo.speed)
1691 			ent->moveinfo.accel = newspeed;
1692 		else
1693 			ent->moveinfo.accel *= ratio;
1694 		if (ent->moveinfo.decel == ent->moveinfo.speed)
1695 			ent->moveinfo.decel = newspeed;
1696 		else
1697 			ent->moveinfo.decel *= ratio;
1698 		ent->moveinfo.speed = newspeed;
1699 	}
1700 }
1701 
Think_SpawnDoorTrigger(edict_t * ent)1702 void Think_SpawnDoorTrigger (edict_t *ent)
1703 {
1704 	edict_t		*other;
1705 	vec3_t		mins, maxs;
1706 
1707 	if (ent->flags & FL_TEAMSLAVE)
1708 		return;		// only the team leader spawns a trigger
1709 
1710 	VectorCopy (ent->absmin, mins);
1711 	VectorCopy (ent->absmax, maxs);
1712 
1713 	for (other = ent->teamchain ; other ; other=other->teamchain)
1714 	{
1715 		AddPointToBounds (other->absmin, mins, maxs);
1716 		AddPointToBounds (other->absmax, mins, maxs);
1717 	}
1718 
1719 	// expand
1720 	mins[0] -= 60;
1721 	mins[1] -= 60;
1722 	maxs[0] += 60;
1723 	maxs[1] += 60;
1724 
1725 	other = G_Spawn ();
1726 	VectorCopy (mins, other->mins);
1727 	VectorCopy (maxs, other->maxs);
1728 	other->owner = ent;
1729 	other->solid = SOLID_TRIGGER;
1730 	other->movetype = MOVETYPE_NONE;
1731 	other->touch = Touch_DoorTrigger;
1732 	gi.linkentity (other);
1733 
1734 	if (ent->spawnflags & DOOR_START_OPEN)
1735 		door_use_areaportals (ent, true);
1736 
1737 	Think_CalcMoveSpeed (ent);
1738 }
1739 
door_blocked(edict_t * self,edict_t * other)1740 void door_blocked  (edict_t *self, edict_t *other)
1741 {
1742 	edict_t	*ent;
1743 
1744 	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
1745 	{
1746 		// give it a chance to go away on it's own terms (like gibs)
1747 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
1748 		// if it's still there, nuke it
1749 		if (other && other->inuse)
1750 			BecomeExplosion1 (other);
1751 		return;
1752 	}
1753 
1754 	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
1755 
1756 	if (self->spawnflags & DOOR_CRUSHER)
1757 		return;
1758 
1759 
1760 // if a door has a negative wait, it would never come back if blocked,
1761 // so let it just squash the object to death real fast
1762 	if (self->moveinfo.wait >= 0)
1763 	{
1764 		if (self->moveinfo.state == STATE_DOWN)
1765 		{
1766 			for (ent = self->teammaster ; ent ; ent = ent->teamchain)
1767 				door_go_up (ent, ent->activator);
1768 		}
1769 		else
1770 		{
1771 			for (ent = self->teammaster ; ent ; ent = ent->teamchain)
1772 				door_go_down (ent);
1773 		}
1774 	}
1775 }
1776 
door_killed(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)1777 void door_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
1778 {
1779 	edict_t	*ent;
1780 
1781 	for (ent = self->teammaster ; ent ; ent = ent->teamchain)
1782 	{
1783 		ent->health = ent->max_health;
1784 		ent->takedamage = DAMAGE_NO;
1785 	}
1786 	door_use (self->teammaster, attacker, attacker);
1787 }
1788 
door_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)1789 void door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
1790 {
1791 	if (!other->client)
1792 		return;
1793 
1794 	if (level.time < self->touch_debounce_time)
1795 		return;
1796 	self->touch_debounce_time = level.time + 5.0;
1797 
1798 	gi.centerprintf (other, "%s", self->message);
1799 	gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
1800 }
1801 
SP_func_door(edict_t * ent)1802 void SP_func_door (edict_t *ent)
1803 {
1804 	vec3_t	abs_movedir;
1805 
1806 	if (ent->sounds != 1)
1807 	{
1808 		ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
1809 		ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
1810 		ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
1811 	}
1812 
1813 	G_SetMovedir (ent->s.angles, ent->movedir);
1814 	ent->movetype = MOVETYPE_PUSH;
1815 	ent->solid = SOLID_BSP;
1816 	gi.setmodel (ent, ent->model);
1817 
1818 	ent->blocked = door_blocked;
1819 	ent->use = door_use;
1820 
1821 	if (!ent->speed)
1822 		ent->speed = 100;
1823 	if (deathmatch->value)
1824 		ent->speed *= 2;
1825 
1826 	if (!ent->accel)
1827 		ent->accel = ent->speed;
1828 	if (!ent->decel)
1829 		ent->decel = ent->speed;
1830 
1831 	if (!ent->wait)
1832 		ent->wait = 3;
1833 	if (!st.lip)
1834 		st.lip = 8;
1835 	if (!ent->dmg)
1836 		ent->dmg = 2;
1837 
1838 	// calculate second position
1839 	VectorCopy (ent->s.origin, ent->pos1);
1840 	abs_movedir[0] = fabs(ent->movedir[0]);
1841 	abs_movedir[1] = fabs(ent->movedir[1]);
1842 	abs_movedir[2] = fabs(ent->movedir[2]);
1843 	ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
1844 	VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2);
1845 
1846 	// if it starts open, switch the positions
1847 	if (ent->spawnflags & DOOR_START_OPEN)
1848 	{
1849 		VectorCopy (ent->pos2, ent->s.origin);
1850 		VectorCopy (ent->pos1, ent->pos2);
1851 		VectorCopy (ent->s.origin, ent->pos1);
1852 	}
1853 
1854 	ent->moveinfo.state = STATE_BOTTOM;
1855 
1856 	if (ent->health)
1857 	{
1858 		ent->takedamage = DAMAGE_YES;
1859 		ent->die = door_killed;
1860 		ent->max_health = ent->health;
1861 	}
1862 	else if (ent->targetname && ent->message)
1863 	{
1864 		gi.soundindex ("misc/talk.wav");
1865 		ent->touch = door_touch;
1866 	}
1867 
1868 	ent->moveinfo.speed = ent->speed;
1869 	ent->moveinfo.accel = ent->accel;
1870 	ent->moveinfo.decel = ent->decel;
1871 	ent->moveinfo.wait = ent->wait;
1872 	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
1873 	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
1874 	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
1875 	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
1876 
1877 	if (ent->spawnflags & 16)
1878 		ent->s.effects |= EF_ANIM_ALL;
1879 	if (ent->spawnflags & 64)
1880 		ent->s.effects |= EF_ANIM_ALLFAST;
1881 
1882 	// to simplify logic elsewhere, make non-teamed doors into a team of one
1883 	if (!ent->team)
1884 		ent->teammaster = ent;
1885 
1886 	gi.linkentity (ent);
1887 
1888 	ent->nextthink = level.time + FRAMETIME;
1889 	if (ent->health || ent->targetname)
1890 		ent->think = Think_CalcMoveSpeed;
1891 	else
1892 		ent->think = Think_SpawnDoorTrigger;
1893 }
1894 
1895 //PGM
Door_Activate(edict_t * self,edict_t * other,edict_t * activator)1896 void Door_Activate (edict_t *self, edict_t *other, edict_t *activator)
1897 {
1898 	self->use = NULL;
1899 
1900 	if (self->health)
1901 	{
1902 		self->takedamage = DAMAGE_YES;
1903 		self->die = door_killed;
1904 		self->max_health = self->health;
1905 	}
1906 
1907 	if (self->health)
1908 		self->think = Think_CalcMoveSpeed;
1909 	else
1910 		self->think = Think_SpawnDoorTrigger;
1911 	self->nextthink = level.time + FRAMETIME;
1912 
1913 }
1914 //PGM
1915 
1916 /*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS EASY MED HARD DM COOP INACTIVE
1917 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1918 
1919 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).
1920 NOMONSTER	monsters will not trigger this door
1921 
1922 You need to have an origin brush as part of this entity.  The center of that brush will be
1923 the point around which it is rotated. It will rotate around the Z axis by default.  You can
1924 check either the X_AXIS or Y_AXIS box to change that.
1925 
1926 "distance" is how many degrees the door will be rotated.
1927 "speed" determines how fast the door moves; default value is 100.
1928 "accel" if specified,is how much the rotation speed will increase each .1 sec. (default: no accel)
1929 
1930 REVERSE will cause the door to rotate in the opposite direction.
1931 INACTIVE will cause the door to be inactive until triggered.
1932 
1933 "message"	is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1934 "angle"		determines the opening direction
1935 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1936 "health"	if set, door must be shot open
1937 "speed"		movement speed (100 default)
1938 "wait"		wait before returning (3 default, -1 = never return)
1939 "dmg"		damage to inflict when blocked (2 default)
1940 "sounds"
1941 1)	silent
1942 2)	light
1943 3)	medium
1944 4)	heavy
1945 */
1946 
SP_func_door_rotating(edict_t * ent)1947 void SP_func_door_rotating (edict_t *ent)
1948 {
1949 	VectorClear (ent->s.angles);
1950 
1951 	// set the axis of rotation
1952 	VectorClear(ent->movedir);
1953 	if (ent->spawnflags & DOOR_X_AXIS)
1954 		ent->movedir[2] = 1.0;
1955 	else if (ent->spawnflags & DOOR_Y_AXIS)
1956 		ent->movedir[0] = 1.0;
1957 	else // Z_AXIS
1958 		ent->movedir[1] = 1.0;
1959 
1960 	// check for reverse rotation
1961 	if (ent->spawnflags & DOOR_REVERSE)
1962 		VectorNegate (ent->movedir, ent->movedir);
1963 
1964 	if (!st.distance)
1965 	{
1966 		gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin));
1967 		st.distance = 90;
1968 	}
1969 
1970 	VectorCopy (ent->s.angles, ent->pos1);
1971 	VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2);
1972 	ent->moveinfo.distance = st.distance;
1973 
1974 	ent->movetype = MOVETYPE_PUSH;
1975 	ent->solid = SOLID_BSP;
1976 	gi.setmodel (ent, ent->model);
1977 
1978 	ent->blocked = door_blocked;
1979 	ent->use = door_use;
1980 
1981 	if (!ent->speed)
1982 		ent->speed = 100;
1983 	if (!ent->accel)
1984 		ent->accel = ent->speed;
1985 	if (!ent->decel)
1986 		ent->decel = ent->speed;
1987 
1988 	if (!ent->wait)
1989 		ent->wait = 3;
1990 	if (!ent->dmg)
1991 		ent->dmg = 2;
1992 
1993 	if (ent->sounds != 1)
1994 	{
1995 		ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
1996 		ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
1997 		ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
1998 	}
1999 
2000 	// if it starts open, switch the positions
2001 	if (ent->spawnflags & DOOR_START_OPEN)
2002 	{
2003 		VectorCopy (ent->pos2, ent->s.angles);
2004 		VectorCopy (ent->pos1, ent->pos2);
2005 		VectorCopy (ent->s.angles, ent->pos1);
2006 		VectorNegate (ent->movedir, ent->movedir);
2007 	}
2008 
2009 	if (ent->health)
2010 	{
2011 		ent->takedamage = DAMAGE_YES;
2012 		ent->die = door_killed;
2013 		ent->max_health = ent->health;
2014 	}
2015 
2016 	if (ent->targetname && ent->message)
2017 	{
2018 		gi.soundindex ("misc/talk.wav");
2019 		ent->touch = door_touch;
2020 	}
2021 
2022 	ent->moveinfo.state = STATE_BOTTOM;
2023 	ent->moveinfo.speed = ent->speed;
2024 	ent->moveinfo.accel = ent->accel;
2025 	ent->moveinfo.decel = ent->decel;
2026 	ent->moveinfo.wait = ent->wait;
2027 	VectorCopy (ent->s.origin, ent->moveinfo.start_origin);
2028 	VectorCopy (ent->pos1, ent->moveinfo.start_angles);
2029 	VectorCopy (ent->s.origin, ent->moveinfo.end_origin);
2030 	VectorCopy (ent->pos2, ent->moveinfo.end_angles);
2031 
2032 	if (ent->spawnflags & 16)
2033 		ent->s.effects |= EF_ANIM_ALL;
2034 
2035 	// to simplify logic elsewhere, make non-teamed doors into a team of one
2036 	if (!ent->team)
2037 		ent->teammaster = ent;
2038 
2039 	gi.linkentity (ent);
2040 
2041 	ent->nextthink = level.time + FRAMETIME;
2042 	if (ent->health || ent->targetname)
2043 		ent->think = Think_CalcMoveSpeed;
2044 	else
2045 		ent->think = Think_SpawnDoorTrigger;
2046 
2047 //PGM
2048 	if (ent->spawnflags & DOOR_INACTIVE)
2049 	{
2050 		ent->takedamage = DAMAGE_NO;
2051 		ent->die = NULL;
2052 		ent->think = NULL;
2053 		ent->nextthink = 0;
2054 		ent->use = Door_Activate;
2055 	}
2056 //PGM
2057 }
2058 
smart_water_blocked(edict_t * self,edict_t * other)2059 void smart_water_blocked (edict_t *self, edict_t *other)
2060 {
2061 	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
2062 	{
2063 		// give it a chance to go away on it's own terms (like gibs)
2064 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_LAVA);
2065 		// if it's still there, nuke it
2066 		if (other && other->inuse)		// PGM
2067 			BecomeExplosion1 (other);
2068 		return;
2069 	}
2070 
2071 	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, 0, MOD_LAVA);
2072 }
2073 
2074 /*QUAKED func_water (0 .5 .8) ? START_OPEN SMART
2075 func_water is a moveable water brush.  It must be targeted to operate.  Use a non-water texture at your own risk.
2076 
2077 START_OPEN causes the water to move to its destination when spawned and operate in reverse.
2078 
2079 SMART causes the water to adjust its speed depending on distance to player.
2080 (speed = distance/accel, min 5, max self->speed)
2081 "accel"		for smart water, the divisor to determine water speed. default 20 (smaller = faster)
2082 
2083 "health"	maximum height of this water brush
2084 "angle"		determines the opening direction (up or down only)
2085 "speed"		movement speed (25 default)
2086 "wait"		wait before returning (-1 default, -1 = TOGGLE)
2087 "lip"		lip remaining at end of move (0 default)
2088 "sounds"	(yes, these need to be changed)
2089 0)	no sound
2090 1)	water
2091 2)	lava
2092 */
2093 
SP_func_water(edict_t * self)2094 void SP_func_water (edict_t *self)
2095 {
2096 	vec3_t	abs_movedir;
2097 
2098   	G_SetMovedir (self->s.angles, self->movedir);
2099 	self->movetype = MOVETYPE_PUSH;
2100 	self->solid = SOLID_BSP;
2101 	gi.setmodel (self, self->model);
2102 
2103 	switch (self->sounds)
2104 	{
2105 		default:
2106 			break;
2107 
2108 		case 1: // water
2109 			self->moveinfo.sound_start = gi.soundindex  ("world/mov_watr.wav");
2110 			self->moveinfo.sound_end = gi.soundindex  ("world/stp_watr.wav");
2111 			break;
2112 
2113 		case 2: // lava
2114 			self->moveinfo.sound_start = gi.soundindex  ("world/mov_watr.wav");
2115 			self->moveinfo.sound_end = gi.soundindex  ("world/stp_watr.wav");
2116 			break;
2117 	}
2118 
2119 	// calculate second position
2120 	VectorCopy (self->s.origin, self->pos1);
2121 	abs_movedir[0] = fabs(self->movedir[0]);
2122 	abs_movedir[1] = fabs(self->movedir[1]);
2123 	abs_movedir[2] = fabs(self->movedir[2]);
2124 	self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip;
2125 	VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2);
2126 
2127 	// if it starts open, switch the positions
2128 	if (self->spawnflags & DOOR_START_OPEN)
2129 	{
2130 		VectorCopy (self->pos2, self->s.origin);
2131 		VectorCopy (self->pos1, self->pos2);
2132 		VectorCopy (self->s.origin, self->pos1);
2133 	}
2134 
2135 	VectorCopy (self->pos1, self->moveinfo.start_origin);
2136 	VectorCopy (self->s.angles, self->moveinfo.start_angles);
2137 	VectorCopy (self->pos2, self->moveinfo.end_origin);
2138 	VectorCopy (self->s.angles, self->moveinfo.end_angles);
2139 
2140 	self->moveinfo.state = STATE_BOTTOM;
2141 
2142 	if (!self->speed)
2143 		self->speed = 25;
2144 	self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed;
2145 
2146 	if ( self->spawnflags & 2)	// smart water
2147 	{
2148 		// this is actually the divisor of the lowest player's distance to determine speed.
2149 		// self->speed then becomes the cap of the speed.
2150 		if(!self->accel)
2151 			self->accel = 20;
2152 		self->blocked = smart_water_blocked;
2153 	}
2154 
2155 	if (!self->wait)
2156 		self->wait = -1;
2157 	self->moveinfo.wait = self->wait;
2158 
2159 	self->use = door_use;
2160 
2161 	if (self->wait == -1)
2162 		self->spawnflags |= DOOR_TOGGLE;
2163 
2164 	self->classname = "func_door";
2165 
2166 	gi.linkentity (self);
2167 }
2168 
2169 
2170 #define TRAIN_START_ON		1
2171 #define TRAIN_TOGGLE		2
2172 #define TRAIN_BLOCK_STOPS	4
2173 
2174 /*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
2175 Trains are moving platforms that players can ride.
2176 The targets origin specifies the min point of the train at each corner.
2177 The train spawns at the first target it is pointing at.
2178 If the train is the target of a button or trigger, it will not begin moving until activated.
2179 speed	default 100
2180 dmg		default	2
2181 noise	looping sound to play when the train is in motion
2182 
2183 To have other entities move with the train, set all the piece's team value to the same thing. They will move in unison.
2184 */
2185 void train_next (edict_t *self);
2186 
train_blocked(edict_t * self,edict_t * other)2187 void train_blocked (edict_t *self, edict_t *other)
2188 {
2189 	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
2190 	{
2191 		// give it a chance to go away on it's own terms (like gibs)
2192 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
2193 		// if it's still there, nuke it
2194 		if (other && other->inuse)
2195 			BecomeExplosion1 (other);
2196 		return;
2197 	}
2198 
2199 	if (level.time < self->touch_debounce_time)
2200 		return;
2201 
2202 	if (!self->dmg)
2203 		return;
2204 	self->touch_debounce_time = level.time + 0.5;
2205 	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
2206 }
2207 
train_wait(edict_t * self)2208 void train_wait (edict_t *self)
2209 {
2210 	if (self->target_ent->pathtarget)
2211 	{
2212 		char	*savetarget;
2213 		edict_t	*ent;
2214 
2215 		ent = self->target_ent;
2216 		savetarget = ent->target;
2217 		ent->target = ent->pathtarget;
2218 		G_UseTargets (ent, self->activator);
2219 		ent->target = savetarget;
2220 
2221 		// make sure we didn't get killed by a killtarget
2222 		if (!self->inuse)
2223 			return;
2224 	}
2225 
2226 	if (self->moveinfo.wait)
2227 	{
2228 		if (self->moveinfo.wait > 0)
2229 		{
2230 			self->nextthink = level.time + self->moveinfo.wait;
2231 			self->think = train_next;
2232 		}
2233 		else if (self->spawnflags & TRAIN_TOGGLE)  // && wait < 0
2234 		{
2235 			// PMM - clear target_ent, let train_next get called when we get used
2236 //			train_next (self);
2237 			self->target_ent = NULL;
2238 			// pmm
2239 			self->spawnflags &= ~TRAIN_START_ON;
2240 			VectorClear (self->velocity);
2241 			self->nextthink = 0;
2242 		}
2243 
2244 		if (!(self->flags & FL_TEAMSLAVE))
2245 		{
2246 			if (self->moveinfo.sound_end)
2247 				gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
2248 			self->s.sound = 0;
2249 		}
2250 	}
2251 	else
2252 	{
2253 		train_next (self);
2254 	}
2255 
2256 }
2257 
2258 //PGM
train_piece_wait(edict_t * self)2259 void train_piece_wait (edict_t *self)
2260 {
2261 }
2262 //PGM
2263 
train_next(edict_t * self)2264 void train_next (edict_t *self)
2265 {
2266 	edict_t		*ent;
2267 	vec3_t		dest;
2268 	qboolean	first;
2269 
2270 	first = true;
2271 again:
2272 	if (!self->target)
2273 	{
2274 //		gi.dprintf ("train_next: no next target\n");
2275 		return;
2276 	}
2277 
2278 	ent = G_PickTarget (self->target);
2279 	if (!ent)
2280 	{
2281 		gi.dprintf ("train_next: bad target %s\n", self->target);
2282 		return;
2283 	}
2284 
2285 	self->target = ent->target;
2286 
2287 	// check for a teleport path_corner
2288 	if (ent->spawnflags & 1)
2289 	{
2290 		if (!first)
2291 		{
2292 			gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin));
2293 			return;
2294 		}
2295 		first = false;
2296 		VectorSubtract (ent->s.origin, self->mins, self->s.origin);
2297 		VectorCopy (self->s.origin, self->s.old_origin);
2298 		self->s.event = EV_OTHER_TELEPORT;
2299 		gi.linkentity (self);
2300 		goto again;
2301 	}
2302 
2303 //PGM
2304 	if (ent->speed)
2305 	{
2306 		self->speed = ent->speed;
2307 		self->moveinfo.speed = ent->speed;
2308 		if(ent->accel)
2309 			self->moveinfo.accel = ent->accel;
2310 		else
2311 			self->moveinfo.accel = ent->speed;
2312 		if(ent->decel)
2313 			self->moveinfo.decel = ent->decel;
2314 		else
2315 			self->moveinfo.decel = ent->speed;
2316 		self->moveinfo.current_speed = 0;
2317 	}
2318 //PGM
2319 
2320 	self->moveinfo.wait = ent->wait;
2321 	self->target_ent = ent;
2322 
2323 	if (!(self->flags & FL_TEAMSLAVE))
2324 	{
2325 		if (self->moveinfo.sound_start)
2326 			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
2327 		self->s.sound = self->moveinfo.sound_middle;
2328 	}
2329 
2330 	VectorSubtract (ent->s.origin, self->mins, dest);
2331 	self->moveinfo.state = STATE_TOP;
2332 	VectorCopy (self->s.origin, self->moveinfo.start_origin);
2333 	VectorCopy (dest, self->moveinfo.end_origin);
2334 	Move_Calc (self, dest, train_wait);
2335 	self->spawnflags |= TRAIN_START_ON;
2336 
2337 //PGM
2338 	if(self->team)
2339 	{
2340 		edict_t *e;
2341 		vec3_t	dir, dst;
2342 
2343 		VectorSubtract (dest, self->s.origin, dir);
2344 		for (e=self->teamchain; e ; e = e->teamchain)
2345 		{
2346 			VectorAdd(dir, e->s.origin, dst);
2347 			VectorCopy(e->s.origin, e->moveinfo.start_origin);
2348 			VectorCopy(dst, e->moveinfo.end_origin);
2349 
2350 			e->moveinfo.state = STATE_TOP;
2351 			e->speed = self->speed;
2352 			e->moveinfo.speed = self->moveinfo.speed;
2353 			e->moveinfo.accel = self->moveinfo.accel;
2354 			e->moveinfo.decel = self->moveinfo.decel;
2355 			e->movetype = MOVETYPE_PUSH;
2356 			Move_Calc (e, dst, train_piece_wait);
2357 		}
2358 
2359 	}
2360 //PGM
2361 }
2362 
train_resume(edict_t * self)2363 void train_resume (edict_t *self)
2364 {
2365 	edict_t	*ent;
2366 	vec3_t	dest;
2367 
2368 	ent = self->target_ent;
2369 
2370 	VectorSubtract (ent->s.origin, self->mins, dest);
2371 	self->moveinfo.state = STATE_TOP;
2372 	VectorCopy (self->s.origin, self->moveinfo.start_origin);
2373 	VectorCopy (dest, self->moveinfo.end_origin);
2374 	Move_Calc (self, dest, train_wait);
2375 	self->spawnflags |= TRAIN_START_ON;
2376 }
2377 
func_train_find(edict_t * self)2378 void func_train_find (edict_t *self)
2379 {
2380 	edict_t *ent;
2381 
2382 	if (!self->target)
2383 	{
2384 		gi.dprintf ("train_find: no target\n");
2385 		return;
2386 	}
2387 	ent = G_PickTarget (self->target);
2388 	if (!ent)
2389 	{
2390 		gi.dprintf ("train_find: target %s not found\n", self->target);
2391 		return;
2392 	}
2393 	self->target = ent->target;
2394 
2395 	VectorSubtract (ent->s.origin, self->mins, self->s.origin);
2396 	gi.linkentity (self);
2397 
2398 	// if not triggered, start immediately
2399 	if (!self->targetname)
2400 		self->spawnflags |= TRAIN_START_ON;
2401 
2402 	if (self->spawnflags & TRAIN_START_ON)
2403 	{
2404 		self->nextthink = level.time + FRAMETIME;
2405 		self->think = train_next;
2406 		self->activator = self;
2407 	}
2408 }
2409 
train_use(edict_t * self,edict_t * other,edict_t * activator)2410 void train_use (edict_t *self, edict_t *other, edict_t *activator)
2411 {
2412 	self->activator = activator;
2413 
2414 	if (self->spawnflags & TRAIN_START_ON)
2415 	{
2416 		if (!(self->spawnflags & TRAIN_TOGGLE))
2417 			return;
2418 		self->spawnflags &= ~TRAIN_START_ON;
2419 		VectorClear (self->velocity);
2420 		self->nextthink = 0;
2421 	}
2422 	else
2423 	{
2424 		if (self->target_ent)
2425 			train_resume(self);
2426 		else
2427 			train_next(self);
2428 	}
2429 }
2430 
SP_func_train(edict_t * self)2431 void SP_func_train (edict_t *self)
2432 {
2433 	self->movetype = MOVETYPE_PUSH;
2434 
2435 	VectorClear (self->s.angles);
2436 	self->blocked = train_blocked;
2437 	if (self->spawnflags & TRAIN_BLOCK_STOPS)
2438 		self->dmg = 0;
2439 	else
2440 	{
2441 		if (!self->dmg)
2442 			self->dmg = 100;
2443 	}
2444 	self->solid = SOLID_BSP;
2445 	gi.setmodel (self, self->model);
2446 
2447 	if (st.noise)
2448 		self->moveinfo.sound_middle = gi.soundindex  (st.noise);
2449 
2450 	if (!self->speed)
2451 		self->speed = 100;
2452 
2453 	self->moveinfo.speed = self->speed;
2454 	self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
2455 
2456 	self->use = train_use;
2457 
2458 	gi.linkentity (self);
2459 
2460 	if (self->target)
2461 	{
2462 		// start trains on the second frame, to make sure their targets have had
2463 		// a chance to spawn
2464 		self->nextthink = level.time + FRAMETIME;
2465 		self->think = func_train_find;
2466 	}
2467 	else
2468 	{
2469 		gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin));
2470 	}
2471 }
2472 
2473 
2474 /*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
2475 */
trigger_elevator_use(edict_t * self,edict_t * other,edict_t * activator)2476 void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator)
2477 {
2478 	edict_t *target;
2479 
2480 	if (self->movetarget->nextthink)
2481 	{
2482 //		gi.dprintf("elevator busy\n");
2483 		return;
2484 	}
2485 
2486 	if (!other->pathtarget)
2487 	{
2488 		gi.dprintf("elevator used with no pathtarget\n");
2489 		return;
2490 	}
2491 
2492 	target = G_PickTarget (other->pathtarget);
2493 	if (!target)
2494 	{
2495 		gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget);
2496 		return;
2497 	}
2498 
2499 	self->movetarget->target_ent = target;
2500 	train_resume (self->movetarget);
2501 }
2502 
trigger_elevator_init(edict_t * self)2503 void trigger_elevator_init (edict_t *self)
2504 {
2505 	if (!self->target)
2506 	{
2507 		gi.dprintf("trigger_elevator has no target\n");
2508 		return;
2509 	}
2510 	self->movetarget = G_PickTarget (self->target);
2511 	if (!self->movetarget)
2512 	{
2513 		gi.dprintf("trigger_elevator unable to find target %s\n", self->target);
2514 		return;
2515 	}
2516 	if (strcmp(self->movetarget->classname, "func_train") != 0)
2517 	{
2518 		gi.dprintf("trigger_elevator target %s is not a train\n", self->target);
2519 		return;
2520 	}
2521 
2522 	self->use = trigger_elevator_use;
2523 	self->svflags = SVF_NOCLIENT;
2524 
2525 }
2526 
SP_trigger_elevator(edict_t * self)2527 void SP_trigger_elevator (edict_t *self)
2528 {
2529 	self->think = trigger_elevator_init;
2530 	self->nextthink = level.time + FRAMETIME;
2531 }
2532 
2533 
2534 /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
2535 "wait"			base time between triggering all targets, default is 1
2536 "random"		wait variance, default is 0
2537 
2538 so, the basic time between firing is a random time between
2539 (wait - random) and (wait + random)
2540 
2541 "delay"			delay before first firing when turned on, default is 0
2542 
2543 "pausetime"		additional delay used only the very first time
2544 				and only if spawned with START_ON
2545 
2546 These can used but not touched.
2547 */
func_timer_think(edict_t * self)2548 void func_timer_think (edict_t *self)
2549 {
2550 	G_UseTargets (self, self->activator);
2551 	self->nextthink = level.time + self->wait + crandom() * self->random;
2552 }
2553 
func_timer_use(edict_t * self,edict_t * other,edict_t * activator)2554 void func_timer_use (edict_t *self, edict_t *other, edict_t *activator)
2555 {
2556 	self->activator = activator;
2557 
2558 	// if on, turn it off
2559 	if (self->nextthink)
2560 	{
2561 		self->nextthink = 0;
2562 		return;
2563 	}
2564 
2565 	// turn it on
2566 	if (self->delay)
2567 		self->nextthink = level.time + self->delay;
2568 	else
2569 		func_timer_think (self);
2570 }
2571 
SP_func_timer(edict_t * self)2572 void SP_func_timer (edict_t *self)
2573 {
2574 	if (!self->wait)
2575 		self->wait = 1.0;
2576 
2577 	self->use = func_timer_use;
2578 	self->think = func_timer_think;
2579 
2580 	if (self->random >= self->wait)
2581 	{
2582 		self->random = self->wait - FRAMETIME;
2583 		gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin));
2584 	}
2585 
2586 	if (self->spawnflags & 1)
2587 	{
2588 		self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random;
2589 		self->activator = self;
2590 	}
2591 
2592 	self->svflags = SVF_NOCLIENT;
2593 }
2594 
2595 
2596 /*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE
2597 Conveyors are stationary brushes that move what's on them.
2598 The brush should be have a surface with at least one current content enabled.
2599 speed	default 100
2600 */
2601 
func_conveyor_use(edict_t * self,edict_t * other,edict_t * activator)2602 void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator)
2603 {
2604 	if (self->spawnflags & 1)
2605 	{
2606 		self->speed = 0;
2607 		self->spawnflags &= ~1;
2608 	}
2609 	else
2610 	{
2611 		self->speed = self->count;
2612 		self->spawnflags |= 1;
2613 	}
2614 
2615 	if (!(self->spawnflags & 2))
2616 		self->count = 0;
2617 }
2618 
SP_func_conveyor(edict_t * self)2619 void SP_func_conveyor (edict_t *self)
2620 {
2621 	if (!self->speed)
2622 		self->speed = 100;
2623 
2624 	if (!(self->spawnflags & 1))
2625 	{
2626 		self->count = self->speed;
2627 		self->speed = 0;
2628 	}
2629 
2630 	self->use = func_conveyor_use;
2631 
2632 	gi.setmodel (self, self->model);
2633 	self->solid = SOLID_BSP;
2634 	gi.linkentity (self);
2635 }
2636 
2637 
2638 /*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down
2639 A secret door.  Slide back and then to the side.
2640 
2641 open_once		doors never closes
2642 1st_left		1st move is left of arrow
2643 1st_down		1st move is down from arrow
2644 always_shoot	door is shootebale even if targeted
2645 
2646 "angle"		determines the direction
2647 "dmg"		damage to inflic when blocked (default 2)
2648 "wait"		how long to hold in the open position (default 5, -1 means hold)
2649 */
2650 
2651 #define SECRET_ALWAYS_SHOOT	1
2652 #define SECRET_1ST_LEFT		2
2653 #define SECRET_1ST_DOWN		4
2654 
2655 void door_secret_move1 (edict_t *self);
2656 void door_secret_move2 (edict_t *self);
2657 void door_secret_move3 (edict_t *self);
2658 void door_secret_move4 (edict_t *self);
2659 void door_secret_move5 (edict_t *self);
2660 void door_secret_move6 (edict_t *self);
2661 void door_secret_done (edict_t *self);
2662 
door_secret_use(edict_t * self,edict_t * other,edict_t * activator)2663 void door_secret_use (edict_t *self, edict_t *other, edict_t *activator)
2664 {
2665 	// make sure we're not already moving
2666 	if (!VectorCompare(self->s.origin, vec3_origin))
2667 		return;
2668 
2669 	Move_Calc (self, self->pos1, door_secret_move1);
2670 	door_use_areaportals (self, true);
2671 }
2672 
door_secret_move1(edict_t * self)2673 void door_secret_move1 (edict_t *self)
2674 {
2675 	self->nextthink = level.time + 1.0;
2676 	self->think = door_secret_move2;
2677 }
2678 
door_secret_move2(edict_t * self)2679 void door_secret_move2 (edict_t *self)
2680 {
2681 	Move_Calc (self, self->pos2, door_secret_move3);
2682 }
2683 
door_secret_move3(edict_t * self)2684 void door_secret_move3 (edict_t *self)
2685 {
2686 	if (self->wait == -1)
2687 		return;
2688 	self->nextthink = level.time + self->wait;
2689 	self->think = door_secret_move4;
2690 }
2691 
door_secret_move4(edict_t * self)2692 void door_secret_move4 (edict_t *self)
2693 {
2694 	Move_Calc (self, self->pos1, door_secret_move5);
2695 }
2696 
door_secret_move5(edict_t * self)2697 void door_secret_move5 (edict_t *self)
2698 {
2699 	self->nextthink = level.time + 1.0;
2700 	self->think = door_secret_move6;
2701 }
2702 
door_secret_move6(edict_t * self)2703 void door_secret_move6 (edict_t *self)
2704 {
2705 	Move_Calc (self, vec3_origin, door_secret_done);
2706 }
2707 
door_secret_done(edict_t * self)2708 void door_secret_done (edict_t *self)
2709 {
2710 	if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT))
2711 	{
2712 		self->health = 0;
2713 		self->takedamage = DAMAGE_YES;
2714 	}
2715 	door_use_areaportals (self, false);
2716 }
2717 
door_secret_blocked(edict_t * self,edict_t * other)2718 void door_secret_blocked  (edict_t *self, edict_t *other)
2719 {
2720 	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
2721 	{
2722 		// give it a chance to go away on it's own terms (like gibs)
2723 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
2724 		// if it's still there, nuke it
2725 		if (other && other->inuse)
2726 			BecomeExplosion1 (other);
2727 		return;
2728 	}
2729 
2730 	if (level.time < self->touch_debounce_time)
2731 		return;
2732 	self->touch_debounce_time = level.time + 0.5;
2733 
2734 	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
2735 }
2736 
door_secret_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)2737 void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
2738 {
2739 	self->takedamage = DAMAGE_NO;
2740 	door_secret_use (self, attacker, attacker);
2741 }
2742 
SP_func_door_secret(edict_t * ent)2743 void SP_func_door_secret (edict_t *ent)
2744 {
2745 	vec3_t	forward, right, up;
2746 	float	side;
2747 	float	width;
2748 	float	length;
2749 
2750 	ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
2751 	ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
2752 	ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
2753 
2754 	ent->movetype = MOVETYPE_PUSH;
2755 	ent->solid = SOLID_BSP;
2756 	gi.setmodel (ent, ent->model);
2757 
2758 	ent->blocked = door_secret_blocked;
2759 	ent->use = door_secret_use;
2760 
2761 	if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT))
2762 	{
2763 		ent->health = 0;
2764 		ent->takedamage = DAMAGE_YES;
2765 		ent->die = door_secret_die;
2766 	}
2767 
2768 	if (!ent->dmg)
2769 		ent->dmg = 2;
2770 
2771 	if (!ent->wait)
2772 		ent->wait = 5;
2773 
2774 	ent->moveinfo.accel =
2775 	ent->moveinfo.decel =
2776 	ent->moveinfo.speed = 50;
2777 
2778 	// calculate positions
2779 	AngleVectors (ent->s.angles, forward, right, up);
2780 	VectorClear (ent->s.angles);
2781 	side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT);
2782 	if (ent->spawnflags & SECRET_1ST_DOWN)
2783 		width = fabs(DotProduct(up, ent->size));
2784 	else
2785 		width = fabs(DotProduct(right, ent->size));
2786 	length = fabs(DotProduct(forward, ent->size));
2787 	if (ent->spawnflags & SECRET_1ST_DOWN)
2788 		VectorMA (ent->s.origin, -1 * width, up, ent->pos1);
2789 	else
2790 		VectorMA (ent->s.origin, side * width, right, ent->pos1);
2791 	VectorMA (ent->pos1, length, forward, ent->pos2);
2792 
2793 	if (ent->health)
2794 	{
2795 		ent->takedamage = DAMAGE_YES;
2796 		ent->die = door_killed;
2797 		ent->max_health = ent->health;
2798 	}
2799 	else if (ent->targetname && ent->message)
2800 	{
2801 		gi.soundindex ("misc/talk.wav");
2802 		ent->touch = door_touch;
2803 	}
2804 
2805 	ent->classname = "func_door";
2806 
2807 	gi.linkentity (ent);
2808 }
2809 
2810 
2811 /*QUAKED func_killbox (1 0 0) ?
2812 Kills everything inside when fired, irrespective of protection.
2813 */
use_killbox(edict_t * self,edict_t * other,edict_t * activator)2814 void use_killbox (edict_t *self, edict_t *other, edict_t *activator)
2815 {
2816 	KillBox (self);
2817 }
2818 
SP_func_killbox(edict_t * ent)2819 void SP_func_killbox (edict_t *ent)
2820 {
2821 	gi.setmodel (ent, ent->model);
2822 	ent->use = use_killbox;
2823 	ent->svflags = SVF_NOCLIENT;
2824 }
2825 
2826