1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 
5 This file is part of Quake III Arena source code.
6 
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11 
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 ===========================================================================
21 */
22 //
23 
24 #include "g_local.h"
25 
26 
27 
28 /*
29 ===============================================================================
30 
31 PUSHMOVE
32 
33 ===============================================================================
34 */
35 
36 void MatchTeam( gentity_t *teamLeader, int moverState, int time );
37 
38 typedef struct {
39 	gentity_t	*ent;
40 	vec3_t	origin;
41 	vec3_t	angles;
42 	float	deltayaw;
43 } pushed_t;
44 pushed_t	pushed[MAX_GENTITIES], *pushed_p;
45 
46 
47 /*
48 ============
49 G_TestEntityPosition
50 
51 ============
52 */
G_TestEntityPosition(gentity_t * ent)53 gentity_t	*G_TestEntityPosition( gentity_t *ent ) {
54 	trace_t	tr;
55 	int		mask;
56 
57 	if ( ent->clipmask ) {
58 		mask = ent->clipmask;
59 	} else {
60 		mask = MASK_SOLID;
61 	}
62 	if ( ent->client ) {
63 		trap_Trace( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, ent->client->ps.origin, ent->s.number, mask );
64 	} else {
65 		trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, mask );
66 	}
67 
68 	if (tr.startsolid)
69 		return &g_entities[ tr.entityNum ];
70 
71 	return NULL;
72 }
73 
74 /*
75 ================
76 G_CreateRotationMatrix
77 ================
78 */
G_CreateRotationMatrix(vec3_t angles,vec3_t matrix[3])79 void G_CreateRotationMatrix(vec3_t angles, vec3_t matrix[3]) {
80 	AngleVectors(angles, matrix[0], matrix[1], matrix[2]);
81 	VectorInverse(matrix[1]);
82 }
83 
84 /*
85 ================
86 G_TransposeMatrix
87 ================
88 */
G_TransposeMatrix(vec3_t matrix[3],vec3_t transpose[3])89 void G_TransposeMatrix(vec3_t matrix[3], vec3_t transpose[3]) {
90 	int i, j;
91 	for (i = 0; i < 3; i++) {
92 		for (j = 0; j < 3; j++) {
93 			transpose[i][j] = matrix[j][i];
94 		}
95 	}
96 }
97 
98 /*
99 ================
100 G_RotatePoint
101 ================
102 */
G_RotatePoint(vec3_t point,vec3_t matrix[3])103 void G_RotatePoint(vec3_t point, vec3_t matrix[3]) {
104 	vec3_t tvec;
105 
106 	VectorCopy(point, tvec);
107 	point[0] = DotProduct(matrix[0], tvec);
108 	point[1] = DotProduct(matrix[1], tvec);
109 	point[2] = DotProduct(matrix[2], tvec);
110 }
111 
112 /*
113 ==================
114 G_TryPushingEntity
115 
116 Returns qfalse if the move is blocked
117 ==================
118 */
G_TryPushingEntity(gentity_t * check,gentity_t * pusher,vec3_t move,vec3_t amove)119 qboolean	G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) {
120 	vec3_t		matrix[3], transpose[3];
121 	vec3_t		org, org2, move2;
122 	gentity_t	*block;
123 
124 	// EF_MOVER_STOP will just stop when contacting another entity
125 	// instead of pushing it, but entities can still ride on top of it
126 	if ( ( pusher->s.eFlags & EF_MOVER_STOP ) &&
127 		check->s.groundEntityNum != pusher->s.number ) {
128 		return qfalse;
129 	}
130 
131 	// save off the old position
132 	if (pushed_p > &pushed[MAX_GENTITIES]) {
133 		G_Error( "pushed_p > &pushed[MAX_GENTITIES]" );
134 	}
135 	pushed_p->ent = check;
136 	VectorCopy (check->s.pos.trBase, pushed_p->origin);
137 	VectorCopy (check->s.apos.trBase, pushed_p->angles);
138 	if ( check->client ) {
139 		pushed_p->deltayaw = check->client->ps.delta_angles[YAW];
140 		VectorCopy (check->client->ps.origin, pushed_p->origin);
141 	}
142 	pushed_p++;
143 
144 	// try moving the contacted entity
145 	// figure movement due to the pusher's amove
146 	G_CreateRotationMatrix( amove, transpose );
147 	G_TransposeMatrix( transpose, matrix );
148 	if ( check->client ) {
149 		VectorSubtract (check->client->ps.origin, pusher->r.currentOrigin, org);
150 	}
151 	else {
152 		VectorSubtract (check->s.pos.trBase, pusher->r.currentOrigin, org);
153 	}
154 	VectorCopy( org, org2 );
155 	G_RotatePoint( org2, matrix );
156 	VectorSubtract (org2, org, move2);
157 	// add movement
158 	VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase);
159 	VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase);
160 	if ( check->client ) {
161 		VectorAdd (check->client->ps.origin, move, check->client->ps.origin);
162 		VectorAdd (check->client->ps.origin, move2, check->client->ps.origin);
163 		// make sure the client's view rotates when on a rotating mover
164 		check->client->ps.delta_angles[YAW] += ANGLE2SHORT(amove[YAW]);
165 	}
166 
167 	// may have pushed them off an edge
168 	if ( check->s.groundEntityNum != pusher->s.number ) {
169 		check->s.groundEntityNum = -1;
170 	}
171 
172 	block = G_TestEntityPosition( check );
173 	if (!block) {
174 		// pushed ok
175 		if ( check->client ) {
176 			VectorCopy( check->client->ps.origin, check->r.currentOrigin );
177 		} else {
178 			VectorCopy( check->s.pos.trBase, check->r.currentOrigin );
179 		}
180 		trap_LinkEntity (check);
181 		return qtrue;
182 	}
183 
184 	// if it is ok to leave in the old position, do it
185 	// this is only relevent for riding entities, not pushed
186 	// Sliding trapdoors can cause this.
187 	VectorCopy( (pushed_p-1)->origin, check->s.pos.trBase);
188 	if ( check->client ) {
189 		VectorCopy( (pushed_p-1)->origin, check->client->ps.origin);
190 	}
191 	VectorCopy( (pushed_p-1)->angles, check->s.apos.trBase );
192 	block = G_TestEntityPosition (check);
193 	if ( !block ) {
194 		check->s.groundEntityNum = -1;
195 		pushed_p--;
196 		return qtrue;
197 	}
198 
199 	// blocked
200 	return qfalse;
201 }
202 
203 /*
204 ==================
205 G_CheckProxMinePosition
206 ==================
207 */
G_CheckProxMinePosition(gentity_t * check)208 qboolean G_CheckProxMinePosition( gentity_t *check ) {
209 	vec3_t		start, end;
210 	trace_t	tr;
211 
212 	VectorMA(check->s.pos.trBase, 0.125, check->movedir, start);
213 	VectorMA(check->s.pos.trBase, 2, check->movedir, end);
214 	trap_Trace( &tr, start, NULL, NULL, end, check->s.number, MASK_SOLID );
215 
216 	if (tr.startsolid || tr.fraction < 1)
217 		return qfalse;
218 
219 	return qtrue;
220 }
221 
222 /*
223 ==================
224 G_TryPushingProxMine
225 ==================
226 */
G_TryPushingProxMine(gentity_t * check,gentity_t * pusher,vec3_t move,vec3_t amove)227 qboolean G_TryPushingProxMine( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) {
228 	vec3_t		forward, right, up;
229 	vec3_t		org, org2, move2;
230 	int ret;
231 
232 	// we need this for pushing things later
233 	VectorSubtract (vec3_origin, amove, org);
234 	AngleVectors (org, forward, right, up);
235 
236 	// try moving the contacted entity
237 	VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase);
238 
239 	// figure movement due to the pusher's amove
240 	VectorSubtract (check->s.pos.trBase, pusher->r.currentOrigin, org);
241 	org2[0] = DotProduct (org, forward);
242 	org2[1] = -DotProduct (org, right);
243 	org2[2] = DotProduct (org, up);
244 	VectorSubtract (org2, org, move2);
245 	VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase);
246 
247 	ret = G_CheckProxMinePosition( check );
248 	if (ret) {
249 		VectorCopy( check->s.pos.trBase, check->r.currentOrigin );
250 		trap_LinkEntity (check);
251 	}
252 	return ret;
253 }
254 
255 void G_ExplodeMissile( gentity_t *ent );
256 
257 /*
258 ============
259 G_MoverPush
260 
261 Objects need to be moved back on a failed push,
262 otherwise riders would continue to slide.
263 If qfalse is returned, *obstacle will be the blocking entity
264 ============
265 */
G_MoverPush(gentity_t * pusher,vec3_t move,vec3_t amove,gentity_t ** obstacle)266 qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **obstacle ) {
267 	int			i, e;
268 	gentity_t	*check;
269 	vec3_t		mins, maxs;
270 	pushed_t	*p;
271 	int			entityList[MAX_GENTITIES];
272 	int			listedEntities;
273 	vec3_t		totalMins, totalMaxs;
274 
275 	*obstacle = NULL;
276 
277 
278 	// mins/maxs are the bounds at the destination
279 	// totalMins / totalMaxs are the bounds for the entire move
280 	if ( pusher->r.currentAngles[0] || pusher->r.currentAngles[1] || pusher->r.currentAngles[2]
281 		|| amove[0] || amove[1] || amove[2] ) {
282 		float		radius;
283 
284 		radius = RadiusFromBounds( pusher->r.mins, pusher->r.maxs );
285 		for ( i = 0 ; i < 3 ; i++ ) {
286 			mins[i] = pusher->r.currentOrigin[i] + move[i] - radius;
287 			maxs[i] = pusher->r.currentOrigin[i] + move[i] + radius;
288 			totalMins[i] = mins[i] - move[i];
289 			totalMaxs[i] = maxs[i] - move[i];
290 		}
291 	} else {
292 		for (i=0 ; i<3 ; i++) {
293 			mins[i] = pusher->r.absmin[i] + move[i];
294 			maxs[i] = pusher->r.absmax[i] + move[i];
295 		}
296 
297 		VectorCopy( pusher->r.absmin, totalMins );
298 		VectorCopy( pusher->r.absmax, totalMaxs );
299 		for (i=0 ; i<3 ; i++) {
300 			if ( move[i] > 0 ) {
301 				totalMaxs[i] += move[i];
302 			} else {
303 				totalMins[i] += move[i];
304 			}
305 		}
306 	}
307 
308 	// unlink the pusher so we don't get it in the entityList
309 	trap_UnlinkEntity( pusher );
310 
311 	listedEntities = trap_EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES );
312 
313 	// move the pusher to it's final position
314 	VectorAdd( pusher->r.currentOrigin, move, pusher->r.currentOrigin );
315 	VectorAdd( pusher->r.currentAngles, amove, pusher->r.currentAngles );
316 	trap_LinkEntity( pusher );
317 
318 	// see if any solid entities are inside the final position
319 	for ( e = 0 ; e < listedEntities ; e++ ) {
320 		check = &g_entities[ entityList[ e ] ];
321 
322 #ifdef MISSIONPACK
323 		if ( check->s.eType == ET_MISSILE ) {
324 			// if it is a prox mine
325 			if ( !strcmp(check->classname, "prox mine") ) {
326 				// if this prox mine is attached to this mover try to move it with the pusher
327 				if ( check->enemy == pusher ) {
328 					if (!G_TryPushingProxMine( check, pusher, move, amove )) {
329 						//explode
330 						check->s.loopSound = 0;
331 						G_AddEvent( check, EV_PROXIMITY_MINE_TRIGGER, 0 );
332 						G_ExplodeMissile(check);
333 						if (check->activator) {
334 							G_FreeEntity(check->activator);
335 							check->activator = NULL;
336 						}
337 						//G_Printf("prox mine explodes\n");
338 					}
339 				}
340 				else {
341 					//check if the prox mine is crushed by the mover
342 					if (!G_CheckProxMinePosition( check )) {
343 						//explode
344 						check->s.loopSound = 0;
345 						G_AddEvent( check, EV_PROXIMITY_MINE_TRIGGER, 0 );
346 						G_ExplodeMissile(check);
347 						if (check->activator) {
348 							G_FreeEntity(check->activator);
349 							check->activator = NULL;
350 						}
351 						//G_Printf("prox mine explodes\n");
352 					}
353 				}
354 				continue;
355 			}
356 		}
357 #endif
358 		// only push items and players
359 		if ( check->s.eType != ET_ITEM && check->s.eType != ET_PLAYER && !check->physicsObject ) {
360 			continue;
361 		}
362 
363 		// if the entity is standing on the pusher, it will definitely be moved
364 		if ( check->s.groundEntityNum != pusher->s.number ) {
365 			// see if the ent needs to be tested
366 			if ( check->r.absmin[0] >= maxs[0]
367 			|| check->r.absmin[1] >= maxs[1]
368 			|| check->r.absmin[2] >= maxs[2]
369 			|| check->r.absmax[0] <= mins[0]
370 			|| check->r.absmax[1] <= mins[1]
371 			|| check->r.absmax[2] <= mins[2] ) {
372 				continue;
373 			}
374 			// see if the ent's bbox is inside the pusher's final position
375 			// this does allow a fast moving object to pass through a thin entity...
376 			if (!G_TestEntityPosition (check)) {
377 				continue;
378 			}
379 		}
380 
381 		// the entity needs to be pushed
382 		if ( G_TryPushingEntity( check, pusher, move, amove ) ) {
383 			continue;
384 		}
385 
386 		// the move was blocked an entity
387 
388 		// bobbing entities are instant-kill and never get blocked
389 		if ( pusher->s.pos.trType == TR_SINE || pusher->s.apos.trType == TR_SINE ) {
390 			G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH );
391 			continue;
392 		}
393 
394 
395 		// save off the obstacle so we can call the block function (crush, etc)
396 		*obstacle = check;
397 
398 		// move back any entities we already moved
399 		// go backwards, so if the same entity was pushed
400 		// twice, it goes back to the original position
401 		for ( p=pushed_p-1 ; p>=pushed ; p-- ) {
402 			VectorCopy (p->origin, p->ent->s.pos.trBase);
403 			VectorCopy (p->angles, p->ent->s.apos.trBase);
404 			if ( p->ent->client ) {
405 				p->ent->client->ps.delta_angles[YAW] = p->deltayaw;
406 				VectorCopy (p->origin, p->ent->client->ps.origin);
407 			}
408 			trap_LinkEntity (p->ent);
409 		}
410 		return qfalse;
411 	}
412 
413 	return qtrue;
414 }
415 
416 
417 /*
418 =================
419 G_MoverTeam
420 =================
421 */
G_MoverTeam(gentity_t * ent)422 void G_MoverTeam( gentity_t *ent ) {
423 	vec3_t		move, amove;
424 	gentity_t	*part, *obstacle;
425 	vec3_t		origin, angles;
426 
427 	obstacle = NULL;
428 
429 	// make sure all team slaves can move before commiting
430 	// any moves or calling any think functions
431 	// if the move is blocked, all moved objects will be backed out
432 	pushed_p = pushed;
433 	for (part = ent ; part ; part=part->teamchain) {
434 		// get current position
435 		BG_EvaluateTrajectory( &part->s.pos, level.time, origin );
436 		BG_EvaluateTrajectory( &part->s.apos, level.time, angles );
437 		VectorSubtract( origin, part->r.currentOrigin, move );
438 		VectorSubtract( angles, part->r.currentAngles, amove );
439 		if ( !G_MoverPush( part, move, amove, &obstacle ) ) {
440 			break;	// move was blocked
441 		}
442 	}
443 
444 	if (part) {
445 		// go back to the previous position
446 		for ( part = ent ; part ; part = part->teamchain ) {
447 			part->s.pos.trTime += level.time - level.previousTime;
448 			part->s.apos.trTime += level.time - level.previousTime;
449 			BG_EvaluateTrajectory( &part->s.pos, level.time, part->r.currentOrigin );
450 			BG_EvaluateTrajectory( &part->s.apos, level.time, part->r.currentAngles );
451 			trap_LinkEntity( part );
452 		}
453 
454 		// if the pusher has a "blocked" function, call it
455 		if (ent->blocked) {
456 			ent->blocked( ent, obstacle );
457 		}
458 		return;
459 	}
460 
461 	// the move succeeded
462 	for ( part = ent ; part ; part = part->teamchain ) {
463 		// call the reached function if time is at or past end point
464 		if ( part->s.pos.trType == TR_LINEAR_STOP ) {
465 			if ( level.time >= part->s.pos.trTime + part->s.pos.trDuration ) {
466 				if ( part->reached ) {
467 					part->reached( part );
468 				}
469 			}
470 		}
471 	}
472 }
473 
474 /*
475 ================
476 G_RunMover
477 
478 ================
479 */
G_RunMover(gentity_t * ent)480 void G_RunMover( gentity_t *ent ) {
481 	// if not a team captain, don't do anything, because
482 	// the captain will handle everything
483 	if ( ent->flags & FL_TEAMSLAVE ) {
484 		return;
485 	}
486 
487 	// if stationary at one of the positions, don't move anything
488 	if ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) {
489 		G_MoverTeam( ent );
490 	}
491 
492 	// check think function
493 	G_RunThink( ent );
494 }
495 
496 /*
497 ============================================================================
498 
499 GENERAL MOVERS
500 
501 Doors, plats, and buttons are all binary (two position) movers
502 Pos1 is "at rest", pos2 is "activated"
503 ============================================================================
504 */
505 
506 /*
507 ===============
508 SetMoverState
509 ===============
510 */
SetMoverState(gentity_t * ent,moverState_t moverState,int time)511 void SetMoverState( gentity_t *ent, moverState_t moverState, int time ) {
512 	vec3_t			delta;
513 	float			f;
514 
515 	ent->moverState = moverState;
516 
517 	ent->s.pos.trTime = time;
518 	switch( moverState ) {
519 	case MOVER_POS1:
520 		VectorCopy( ent->pos1, ent->s.pos.trBase );
521 		ent->s.pos.trType = TR_STATIONARY;
522 		break;
523 	case MOVER_POS2:
524 		VectorCopy( ent->pos2, ent->s.pos.trBase );
525 		ent->s.pos.trType = TR_STATIONARY;
526 		break;
527 	case MOVER_1TO2:
528 		VectorCopy( ent->pos1, ent->s.pos.trBase );
529 		VectorSubtract( ent->pos2, ent->pos1, delta );
530 		f = 1000.0 / ent->s.pos.trDuration;
531 		VectorScale( delta, f, ent->s.pos.trDelta );
532 		ent->s.pos.trType = TR_LINEAR_STOP;
533 		break;
534 	case MOVER_2TO1:
535 		VectorCopy( ent->pos2, ent->s.pos.trBase );
536 		VectorSubtract( ent->pos1, ent->pos2, delta );
537 		f = 1000.0 / ent->s.pos.trDuration;
538 		VectorScale( delta, f, ent->s.pos.trDelta );
539 		ent->s.pos.trType = TR_LINEAR_STOP;
540 		break;
541 	}
542 	BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin );
543 	trap_LinkEntity( ent );
544 }
545 
546 /*
547 ================
548 MatchTeam
549 
550 All entities in a mover team will move from pos1 to pos2
551 in the same amount of time
552 ================
553 */
MatchTeam(gentity_t * teamLeader,int moverState,int time)554 void MatchTeam( gentity_t *teamLeader, int moverState, int time ) {
555 	gentity_t		*slave;
556 
557 	for ( slave = teamLeader ; slave ; slave = slave->teamchain ) {
558 		SetMoverState( slave, moverState, time );
559 	}
560 }
561 
562 
563 
564 /*
565 ================
566 ReturnToPos1
567 ================
568 */
ReturnToPos1(gentity_t * ent)569 void ReturnToPos1( gentity_t *ent ) {
570 	MatchTeam( ent, MOVER_2TO1, level.time );
571 
572 	// looping sound
573 	ent->s.loopSound = ent->soundLoop;
574 
575 	// starting sound
576 	if ( ent->sound2to1 ) {
577 		G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
578 	}
579 }
580 
581 
582 /*
583 ================
584 Reached_BinaryMover
585 ================
586 */
Reached_BinaryMover(gentity_t * ent)587 void Reached_BinaryMover( gentity_t *ent ) {
588 
589 	// stop the looping sound
590 	ent->s.loopSound = ent->soundLoop;
591 
592 	if ( ent->moverState == MOVER_1TO2 ) {
593 		// reached pos2
594 		SetMoverState( ent, MOVER_POS2, level.time );
595 
596 		// play sound
597 		if ( ent->soundPos2 ) {
598 			G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 );
599 		}
600 
601 		// return to pos1 after a delay
602 		ent->think = ReturnToPos1;
603 		ent->nextthink = level.time + ent->wait;
604 
605 		// fire targets
606 		if ( !ent->activator ) {
607 			ent->activator = ent;
608 		}
609 		G_UseTargets( ent, ent->activator );
610 	} else if ( ent->moverState == MOVER_2TO1 ) {
611 		// reached pos1
612 		SetMoverState( ent, MOVER_POS1, level.time );
613 
614 		// play sound
615 		if ( ent->soundPos1 ) {
616 			G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 );
617 		}
618 
619 		// close areaportals
620 		if ( ent->teammaster == ent || !ent->teammaster ) {
621 			trap_AdjustAreaPortalState( ent, qfalse );
622 		}
623 	} else {
624 		G_Error( "Reached_BinaryMover: bad moverState" );
625 	}
626 }
627 
628 
629 /*
630 ================
631 Use_BinaryMover
632 ================
633 */
Use_BinaryMover(gentity_t * ent,gentity_t * other,gentity_t * activator)634 void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
635 	int		total;
636 	int		partial;
637 
638 	// only the master should be used
639 	if ( ent->flags & FL_TEAMSLAVE ) {
640 		Use_BinaryMover( ent->teammaster, other, activator );
641 		return;
642 	}
643 
644 	ent->activator = activator;
645 
646 	if ( ent->moverState == MOVER_POS1 ) {
647 		// start moving 50 msec later, becase if this was player
648 		// triggered, level.time hasn't been advanced yet
649 		MatchTeam( ent, MOVER_1TO2, level.time + 50 );
650 
651 		// starting sound
652 		if ( ent->sound1to2 ) {
653 			G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
654 		}
655 
656 		// looping sound
657 		ent->s.loopSound = ent->soundLoop;
658 
659 		// open areaportal
660 		if ( ent->teammaster == ent || !ent->teammaster ) {
661 			trap_AdjustAreaPortalState( ent, qtrue );
662 		}
663 		return;
664 	}
665 
666 	// if all the way up, just delay before coming down
667 	if ( ent->moverState == MOVER_POS2 ) {
668 		ent->nextthink = level.time + ent->wait;
669 		return;
670 	}
671 
672 	// only partway down before reversing
673 	if ( ent->moverState == MOVER_2TO1 ) {
674 		total = ent->s.pos.trDuration;
675 		partial = level.time - ent->s.pos.trTime;
676 		if ( partial > total ) {
677 			partial = total;
678 		}
679 
680 		MatchTeam( ent, MOVER_1TO2, level.time - ( total - partial ) );
681 
682 		if ( ent->sound1to2 ) {
683 			G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
684 		}
685 		return;
686 	}
687 
688 	// only partway up before reversing
689 	if ( ent->moverState == MOVER_1TO2 ) {
690 		total = ent->s.pos.trDuration;
691 		partial = level.time - ent->s.pos.trTime;
692 		if ( partial > total ) {
693 			partial = total;
694 		}
695 
696 		MatchTeam( ent, MOVER_2TO1, level.time - ( total - partial ) );
697 
698 		if ( ent->sound2to1 ) {
699 			G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
700 		}
701 		return;
702 	}
703 }
704 
705 
706 
707 /*
708 ================
709 InitMover
710 
711 "pos1", "pos2", and "speed" should be set before calling,
712 so the movement delta can be calculated
713 ================
714 */
InitMover(gentity_t * ent)715 void InitMover( gentity_t *ent ) {
716 	vec3_t		move;
717 	float		distance;
718 	float		light;
719 	vec3_t		color;
720 	qboolean	lightSet, colorSet;
721 	char		*sound;
722 
723 	// if the "model2" key is set, use a seperate model
724 	// for drawing, but clip against the brushes
725 	if ( ent->model2 ) {
726 		ent->s.modelindex2 = G_ModelIndex( ent->model2 );
727 	}
728 
729 	// if the "loopsound" key is set, use a constant looping sound when moving
730 	if ( G_SpawnString( "noise", "100", &sound ) ) {
731 		ent->s.loopSound = G_SoundIndex( sound );
732 	}
733 
734 	// if the "color" or "light" keys are set, setup constantLight
735 	lightSet = G_SpawnFloat( "light", "100", &light );
736 	colorSet = G_SpawnVector( "color", "1 1 1", color );
737 	if ( lightSet || colorSet ) {
738 		int		r, g, b, i;
739 
740 		r = color[0] * 255;
741 		if ( r > 255 ) {
742 			r = 255;
743 		}
744 		g = color[1] * 255;
745 		if ( g > 255 ) {
746 			g = 255;
747 		}
748 		b = color[2] * 255;
749 		if ( b > 255 ) {
750 			b = 255;
751 		}
752 		i = light / 4;
753 		if ( i > 255 ) {
754 			i = 255;
755 		}
756 		ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
757 	}
758 
759 
760 	ent->use = Use_BinaryMover;
761 	ent->reached = Reached_BinaryMover;
762 
763 	ent->moverState = MOVER_POS1;
764 	ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
765 	ent->s.eType = ET_MOVER;
766 	VectorCopy (ent->pos1, ent->r.currentOrigin);
767 	trap_LinkEntity (ent);
768 
769 	ent->s.pos.trType = TR_STATIONARY;
770 	VectorCopy( ent->pos1, ent->s.pos.trBase );
771 
772 	// calculate time to reach second position from speed
773 	VectorSubtract( ent->pos2, ent->pos1, move );
774 	distance = VectorLength( move );
775 	if ( ! ent->speed ) {
776 		ent->speed = 100;
777 	}
778 	VectorScale( move, ent->speed, ent->s.pos.trDelta );
779 	ent->s.pos.trDuration = distance * 1000 / ent->speed;
780 	if ( ent->s.pos.trDuration <= 0 ) {
781 		ent->s.pos.trDuration = 1;
782 	}
783 }
784 
785 
786 /*
787 ===============================================================================
788 
789 DOOR
790 
791 A use can be triggered either by a touch function, by being shot, or by being
792 targeted by another entity.
793 
794 ===============================================================================
795 */
796 
797 /*
798 ================
799 Blocked_Door
800 ================
801 */
Blocked_Door(gentity_t * ent,gentity_t * other)802 void Blocked_Door( gentity_t *ent, gentity_t *other ) {
803 	// remove anything other than a client
804 	if ( !other->client ) {
805 		// except CTF flags!!!!
806 		if( other->s.eType == ET_ITEM && other->item->giType == IT_TEAM ) {
807 			Team_DroppedFlagThink( other );
808 			return;
809 		}
810 		G_TempEntity( other->s.origin, EV_ITEM_POP );
811 		G_FreeEntity( other );
812 		return;
813 	}
814 
815 	if ( ent->damage ) {
816 		G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH );
817 	}
818 	if ( ent->spawnflags & 4 ) {
819 		return;		// crushers don't reverse
820 	}
821 
822 	// reverse direction
823 	Use_BinaryMover( ent, ent, other );
824 }
825 
826 /*
827 ================
828 Touch_DoorTriggerSpectator
829 ================
830 */
Touch_DoorTriggerSpectator(gentity_t * ent,gentity_t * other,trace_t * trace)831 static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_t *trace ) {
832 	int i, axis;
833 	vec3_t origin, dir, angles;
834 
835 	axis = ent->count;
836 	VectorClear(dir);
837 	if (fabs(other->s.origin[axis] - ent->r.absmax[axis]) <
838 		fabs(other->s.origin[axis] - ent->r.absmin[axis])) {
839 		origin[axis] = ent->r.absmin[axis] - 10;
840 		dir[axis] = -1;
841 	}
842 	else {
843 		origin[axis] = ent->r.absmax[axis] + 10;
844 		dir[axis] = 1;
845 	}
846 	for (i = 0; i < 3; i++) {
847 		if (i == axis) continue;
848 		origin[i] = (ent->r.absmin[i] + ent->r.absmax[i]) * 0.5;
849 	}
850 	vectoangles(dir, angles);
851 	TeleportPlayer(other, origin, angles );
852 }
853 
854 /*
855 ================
856 Touch_DoorTrigger
857 ================
858 */
Touch_DoorTrigger(gentity_t * ent,gentity_t * other,trace_t * trace)859 void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ) {
860 	if ( other->client && other->client->sess.sessionTeam == TEAM_SPECTATOR ) {
861 		// if the door is not open and not opening
862 		if ( ent->parent->moverState != MOVER_1TO2 &&
863 			ent->parent->moverState != MOVER_POS2) {
864 			Touch_DoorTriggerSpectator( ent, other, trace );
865 		}
866 	}
867 	else if ( ent->parent->moverState != MOVER_1TO2 ) {
868 		Use_BinaryMover( ent->parent, ent, other );
869 	}
870 }
871 
872 
873 /*
874 ======================
875 Think_SpawnNewDoorTrigger
876 
877 All of the parts of a door have been spawned, so create
878 a trigger that encloses all of them
879 ======================
880 */
Think_SpawnNewDoorTrigger(gentity_t * ent)881 void Think_SpawnNewDoorTrigger( gentity_t *ent ) {
882 	gentity_t		*other;
883 	vec3_t		mins, maxs;
884 	int			i, best;
885 
886 	// set all of the slaves as shootable
887 	for ( other = ent ; other ; other = other->teamchain ) {
888 		other->takedamage = qtrue;
889 	}
890 
891 	// find the bounds of everything on the team
892 	VectorCopy (ent->r.absmin, mins);
893 	VectorCopy (ent->r.absmax, maxs);
894 
895 	for (other = ent->teamchain ; other ; other=other->teamchain) {
896 		AddPointToBounds (other->r.absmin, mins, maxs);
897 		AddPointToBounds (other->r.absmax, mins, maxs);
898 	}
899 
900 	// find the thinnest axis, which will be the one we expand
901 	best = 0;
902 	for ( i = 1 ; i < 3 ; i++ ) {
903 		if ( maxs[i] - mins[i] < maxs[best] - mins[best] ) {
904 			best = i;
905 		}
906 	}
907 	maxs[best] += 120;
908 	mins[best] -= 120;
909 
910 	// create a trigger with this size
911 	other = G_Spawn ();
912 	other->classname = "door_trigger";
913 	VectorCopy (mins, other->r.mins);
914 	VectorCopy (maxs, other->r.maxs);
915 	other->parent = ent;
916 	other->r.contents = CONTENTS_TRIGGER;
917 	other->touch = Touch_DoorTrigger;
918 	// remember the thinnest axis
919 	other->count = best;
920 	trap_LinkEntity (other);
921 
922 	MatchTeam( ent, ent->moverState, level.time );
923 }
924 
Think_MatchTeam(gentity_t * ent)925 void Think_MatchTeam( gentity_t *ent ) {
926 	MatchTeam( ent, ent->moverState, level.time );
927 }
928 
929 
930 /*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER
931 TOGGLE		wait in both the start and end states for a trigger event.
932 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).
933 NOMONSTER	monsters will not trigger this door
934 
935 "model2"	.md3 model to also draw
936 "angle"		determines the opening direction
937 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
938 "speed"		movement speed (100 default)
939 "wait"		wait before returning (3 default, -1 = never return)
940 "lip"		lip remaining at end of move (8 default)
941 "dmg"		damage to inflict when blocked (2 default)
942 "color"		constantLight color
943 "light"		constantLight radius
944 "health"	if set, the door must be shot open
945 */
SP_func_door(gentity_t * ent)946 void SP_func_door (gentity_t *ent) {
947 	vec3_t	abs_movedir;
948 	float	distance;
949 	vec3_t	size;
950 	float	lip;
951 
952 	ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/doors/dr1_strt.wav");
953 	ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/doors/dr1_end.wav");
954 
955 	ent->blocked = Blocked_Door;
956 
957 	// default speed of 400
958 	if (!ent->speed)
959 		ent->speed = 400;
960 
961 	// default wait of 2 seconds
962 	if (!ent->wait)
963 		ent->wait = 2;
964 	ent->wait *= 1000;
965 
966 	// default lip of 8 units
967 	G_SpawnFloat( "lip", "8", &lip );
968 
969 	// default damage of 2 points
970 	G_SpawnInt( "dmg", "2", &ent->damage );
971 
972 	// first position at start
973 	VectorCopy( ent->s.origin, ent->pos1 );
974 
975 	// calculate second position
976 	trap_SetBrushModel( ent, ent->model );
977 	G_SetMovedir (ent->s.angles, ent->movedir);
978 	abs_movedir[0] = fabs(ent->movedir[0]);
979 	abs_movedir[1] = fabs(ent->movedir[1]);
980 	abs_movedir[2] = fabs(ent->movedir[2]);
981 	VectorSubtract( ent->r.maxs, ent->r.mins, size );
982 	distance = DotProduct( abs_movedir, size ) - lip;
983 	VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 );
984 
985 	// if "start_open", reverse position 1 and 2
986 	if ( ent->spawnflags & 1 ) {
987 		vec3_t	temp;
988 
989 		VectorCopy( ent->pos2, temp );
990 		VectorCopy( ent->s.origin, ent->pos2 );
991 		VectorCopy( temp, ent->pos1 );
992 	}
993 
994 	InitMover( ent );
995 
996 	ent->nextthink = level.time + FRAMETIME;
997 
998 	if ( ! (ent->flags & FL_TEAMSLAVE ) ) {
999 		int health;
1000 
1001 		G_SpawnInt( "health", "0", &health );
1002 		if ( health ) {
1003 			ent->takedamage = qtrue;
1004 		}
1005 		if ( ent->targetname || health ) {
1006 			// non touch/shoot doors
1007 			ent->think = Think_MatchTeam;
1008 		} else {
1009 			ent->think = Think_SpawnNewDoorTrigger;
1010 		}
1011 	}
1012 
1013 
1014 }
1015 
1016 /*
1017 ===============================================================================
1018 
1019 PLAT
1020 
1021 ===============================================================================
1022 */
1023 
1024 /*
1025 ==============
1026 Touch_Plat
1027 
1028 Don't allow decent if a living player is on it
1029 ===============
1030 */
Touch_Plat(gentity_t * ent,gentity_t * other,trace_t * trace)1031 void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) {
1032 	if ( !other->client || other->client->ps.stats[STAT_HEALTH] <= 0 ) {
1033 		return;
1034 	}
1035 
1036 	// delay return-to-pos1 by one second
1037 	if ( ent->moverState == MOVER_POS2 ) {
1038 		ent->nextthink = level.time + 1000;
1039 	}
1040 }
1041 
1042 /*
1043 ==============
1044 Touch_PlatCenterTrigger
1045 
1046 If the plat is at the bottom position, start it going up
1047 ===============
1048 */
Touch_PlatCenterTrigger(gentity_t * ent,gentity_t * other,trace_t * trace)1049 void Touch_PlatCenterTrigger(gentity_t *ent, gentity_t *other, trace_t *trace ) {
1050 	if ( !other->client ) {
1051 		return;
1052 	}
1053 
1054 	if ( ent->parent->moverState == MOVER_POS1 ) {
1055 		Use_BinaryMover( ent->parent, ent, other );
1056 	}
1057 }
1058 
1059 
1060 /*
1061 ================
1062 SpawnPlatTrigger
1063 
1064 Spawn a trigger in the middle of the plat's low position
1065 Elevator cars require that the trigger extend through the entire low position,
1066 not just sit on top of it.
1067 ================
1068 */
SpawnPlatTrigger(gentity_t * ent)1069 void SpawnPlatTrigger( gentity_t *ent ) {
1070 	gentity_t	*trigger;
1071 	vec3_t	tmin, tmax;
1072 
1073 	// the middle trigger will be a thin trigger just
1074 	// above the starting position
1075 	trigger = G_Spawn();
1076 	trigger->classname = "plat_trigger";
1077 	trigger->touch = Touch_PlatCenterTrigger;
1078 	trigger->r.contents = CONTENTS_TRIGGER;
1079 	trigger->parent = ent;
1080 
1081 	tmin[0] = ent->pos1[0] + ent->r.mins[0] + 33;
1082 	tmin[1] = ent->pos1[1] + ent->r.mins[1] + 33;
1083 	tmin[2] = ent->pos1[2] + ent->r.mins[2];
1084 
1085 	tmax[0] = ent->pos1[0] + ent->r.maxs[0] - 33;
1086 	tmax[1] = ent->pos1[1] + ent->r.maxs[1] - 33;
1087 	tmax[2] = ent->pos1[2] + ent->r.maxs[2] + 8;
1088 
1089 	if ( tmax[0] <= tmin[0] ) {
1090 		tmin[0] = ent->pos1[0] + (ent->r.mins[0] + ent->r.maxs[0]) *0.5;
1091 		tmax[0] = tmin[0] + 1;
1092 	}
1093 	if ( tmax[1] <= tmin[1] ) {
1094 		tmin[1] = ent->pos1[1] + (ent->r.mins[1] + ent->r.maxs[1]) *0.5;
1095 		tmax[1] = tmin[1] + 1;
1096 	}
1097 
1098 	VectorCopy (tmin, trigger->r.mins);
1099 	VectorCopy (tmax, trigger->r.maxs);
1100 
1101 	trap_LinkEntity (trigger);
1102 }
1103 
1104 
1105 /*QUAKED func_plat (0 .5 .8) ?
1106 Plats are always drawn in the extended position so they will light correctly.
1107 
1108 "lip"		default 8, protrusion above rest position
1109 "height"	total height of movement, defaults to model height
1110 "speed"		overrides default 200.
1111 "dmg"		overrides default 2
1112 "model2"	.md3 model to also draw
1113 "color"		constantLight color
1114 "light"		constantLight radius
1115 */
SP_func_plat(gentity_t * ent)1116 void SP_func_plat (gentity_t *ent) {
1117 	float		lip, height;
1118 
1119 	ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/plats/pt1_strt.wav");
1120 	ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/plats/pt1_end.wav");
1121 
1122 	VectorClear (ent->s.angles);
1123 
1124 	G_SpawnFloat( "speed", "200", &ent->speed );
1125 	G_SpawnInt( "dmg", "2", &ent->damage );
1126 	G_SpawnFloat( "wait", "1", &ent->wait );
1127 	G_SpawnFloat( "lip", "8", &lip );
1128 
1129 	ent->wait = 1000;
1130 
1131 	// create second position
1132 	trap_SetBrushModel( ent, ent->model );
1133 
1134 	if ( !G_SpawnFloat( "height", "0", &height ) ) {
1135 		height = (ent->r.maxs[2] - ent->r.mins[2]) - lip;
1136 	}
1137 
1138 	// pos1 is the rest (bottom) position, pos2 is the top
1139 	VectorCopy( ent->s.origin, ent->pos2 );
1140 	VectorCopy( ent->pos2, ent->pos1 );
1141 	ent->pos1[2] -= height;
1142 
1143 	InitMover( ent );
1144 
1145 	// touch function keeps the plat from returning while
1146 	// a live player is standing on it
1147 	ent->touch = Touch_Plat;
1148 
1149 	ent->blocked = Blocked_Door;
1150 
1151 	ent->parent = ent;	// so it can be treated as a door
1152 
1153 	// spawn the trigger if one hasn't been custom made
1154 	if ( !ent->targetname ) {
1155 		SpawnPlatTrigger(ent);
1156 	}
1157 }
1158 
1159 
1160 /*
1161 ===============================================================================
1162 
1163 BUTTON
1164 
1165 ===============================================================================
1166 */
1167 
1168 /*
1169 ==============
1170 Touch_Button
1171 
1172 ===============
1173 */
Touch_Button(gentity_t * ent,gentity_t * other,trace_t * trace)1174 void Touch_Button(gentity_t *ent, gentity_t *other, trace_t *trace ) {
1175 	if ( !other->client ) {
1176 		return;
1177 	}
1178 
1179 	if ( ent->moverState == MOVER_POS1 ) {
1180 		Use_BinaryMover( ent, other, other );
1181 	}
1182 }
1183 
1184 
1185 /*QUAKED func_button (0 .5 .8) ?
1186 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.
1187 
1188 "model2"	.md3 model to also draw
1189 "angle"		determines the opening direction
1190 "target"	all entities with a matching targetname will be used
1191 "speed"		override the default 40 speed
1192 "wait"		override the default 1 second wait (-1 = never return)
1193 "lip"		override the default 4 pixel lip remaining at end of move
1194 "health"	if set, the button must be killed instead of touched
1195 "color"		constantLight color
1196 "light"		constantLight radius
1197 */
SP_func_button(gentity_t * ent)1198 void SP_func_button( gentity_t *ent ) {
1199 	vec3_t		abs_movedir;
1200 	float		distance;
1201 	vec3_t		size;
1202 	float		lip;
1203 
1204 	ent->sound1to2 = G_SoundIndex("sound/movers/switches/butn2.wav");
1205 
1206 	if ( !ent->speed ) {
1207 		ent->speed = 40;
1208 	}
1209 
1210 	if ( !ent->wait ) {
1211 		ent->wait = 1;
1212 	}
1213 	ent->wait *= 1000;
1214 
1215 	// first position
1216 	VectorCopy( ent->s.origin, ent->pos1 );
1217 
1218 	// calculate second position
1219 	trap_SetBrushModel( ent, ent->model );
1220 
1221 	G_SpawnFloat( "lip", "4", &lip );
1222 
1223 	G_SetMovedir( ent->s.angles, ent->movedir );
1224 	abs_movedir[0] = fabs(ent->movedir[0]);
1225 	abs_movedir[1] = fabs(ent->movedir[1]);
1226 	abs_movedir[2] = fabs(ent->movedir[2]);
1227 	VectorSubtract( ent->r.maxs, ent->r.mins, size );
1228 	distance = abs_movedir[0] * size[0] + abs_movedir[1] * size[1] + abs_movedir[2] * size[2] - lip;
1229 	VectorMA (ent->pos1, distance, ent->movedir, ent->pos2);
1230 
1231 	if (ent->health) {
1232 		// shootable button
1233 		ent->takedamage = qtrue;
1234 	} else {
1235 		// touchable button
1236 		ent->touch = Touch_Button;
1237 	}
1238 
1239 	InitMover( ent );
1240 }
1241 
1242 
1243 
1244 /*
1245 ===============================================================================
1246 
1247 TRAIN
1248 
1249 ===============================================================================
1250 */
1251 
1252 
1253 #define TRAIN_START_ON		1
1254 #define TRAIN_TOGGLE		2
1255 #define TRAIN_BLOCK_STOPS	4
1256 
1257 /*
1258 ===============
1259 Think_BeginMoving
1260 
1261 The wait time at a corner has completed, so start moving again
1262 ===============
1263 */
Think_BeginMoving(gentity_t * ent)1264 void Think_BeginMoving( gentity_t *ent ) {
1265 	ent->s.pos.trTime = level.time;
1266 	ent->s.pos.trType = TR_LINEAR_STOP;
1267 }
1268 
1269 /*
1270 ===============
1271 Reached_Train
1272 ===============
1273 */
Reached_Train(gentity_t * ent)1274 void Reached_Train( gentity_t *ent ) {
1275 	gentity_t		*next;
1276 	float			speed;
1277 	vec3_t			move;
1278 	float			length;
1279 
1280 	// copy the apropriate values
1281 	next = ent->nextTrain;
1282 	if ( !next || !next->nextTrain ) {
1283 		return;		// just stop
1284 	}
1285 
1286 	// fire all other targets
1287 	G_UseTargets( next, NULL );
1288 
1289 	// set the new trajectory
1290 	ent->nextTrain = next->nextTrain;
1291 	VectorCopy( next->s.origin, ent->pos1 );
1292 	VectorCopy( next->nextTrain->s.origin, ent->pos2 );
1293 
1294 	// if the path_corner has a speed, use that
1295 	if ( next->speed ) {
1296 		speed = next->speed;
1297 	} else {
1298 		// otherwise use the train's speed
1299 		speed = ent->speed;
1300 	}
1301 	if ( speed < 1 ) {
1302 		speed = 1;
1303 	}
1304 
1305 	// calculate duration
1306 	VectorSubtract( ent->pos2, ent->pos1, move );
1307 	length = VectorLength( move );
1308 
1309 	ent->s.pos.trDuration = length * 1000 / speed;
1310 
1311 	// looping sound
1312 	ent->s.loopSound = next->soundLoop;
1313 
1314 	// start it going
1315 	SetMoverState( ent, MOVER_1TO2, level.time );
1316 
1317 	// if there is a "wait" value on the target, don't start moving yet
1318 	if ( next->wait ) {
1319 		ent->nextthink = level.time + next->wait * 1000;
1320 		ent->think = Think_BeginMoving;
1321 		ent->s.pos.trType = TR_STATIONARY;
1322 	}
1323 }
1324 
1325 
1326 /*
1327 ===============
1328 Think_SetupTrainTargets
1329 
1330 Link all the corners together
1331 ===============
1332 */
Think_SetupTrainTargets(gentity_t * ent)1333 void Think_SetupTrainTargets( gentity_t *ent ) {
1334 	gentity_t		*path, *next, *start;
1335 
1336 	ent->nextTrain = G_Find( NULL, FOFS(targetname), ent->target );
1337 	if ( !ent->nextTrain ) {
1338 		G_Printf( "func_train at %s with an unfound target\n",
1339 			vtos(ent->r.absmin) );
1340 		return;
1341 	}
1342 
1343 	start = NULL;
1344 	for ( path = ent->nextTrain ; path != start ; path = next ) {
1345 		if ( !start ) {
1346 			start = path;
1347 		}
1348 
1349 		if ( !path->target ) {
1350 			G_Printf( "Train corner at %s without a target\n",
1351 				vtos(path->s.origin) );
1352 			return;
1353 		}
1354 
1355 		// find a path_corner among the targets
1356 		// there may also be other targets that get fired when the corner
1357 		// is reached
1358 		next = NULL;
1359 		do {
1360 			next = G_Find( next, FOFS(targetname), path->target );
1361 			if ( !next ) {
1362 				G_Printf( "Train corner at %s without a target path_corner\n",
1363 					vtos(path->s.origin) );
1364 				return;
1365 			}
1366 		} while ( strcmp( next->classname, "path_corner" ) );
1367 
1368 		path->nextTrain = next;
1369 	}
1370 
1371 	// start the train moving from the first corner
1372 	Reached_Train( ent );
1373 }
1374 
1375 
1376 
1377 /*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8)
1378 Train path corners.
1379 Target: next path corner and other targets to fire
1380 "speed" speed to move to the next corner
1381 "wait" seconds to wait before behining move to next corner
1382 */
SP_path_corner(gentity_t * self)1383 void SP_path_corner( gentity_t *self ) {
1384 	if ( !self->targetname ) {
1385 		G_Printf ("path_corner with no targetname at %s\n", vtos(self->s.origin));
1386 		G_FreeEntity( self );
1387 		return;
1388 	}
1389 	// path corners don't need to be linked in
1390 }
1391 
1392 
1393 
1394 /*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
1395 A train is a mover that moves between path_corner target points.
1396 Trains MUST HAVE AN ORIGIN BRUSH.
1397 The train spawns at the first target it is pointing at.
1398 "model2"	.md3 model to also draw
1399 "speed"		default 100
1400 "dmg"		default	2
1401 "noise"		looping sound to play when the train is in motion
1402 "target"	next path corner
1403 "color"		constantLight color
1404 "light"		constantLight radius
1405 */
SP_func_train(gentity_t * self)1406 void SP_func_train (gentity_t *self) {
1407 	VectorClear (self->s.angles);
1408 
1409 	if (self->spawnflags & TRAIN_BLOCK_STOPS) {
1410 		self->damage = 0;
1411 	} else {
1412 		if (!self->damage) {
1413 			self->damage = 2;
1414 		}
1415 	}
1416 
1417 	if ( !self->speed ) {
1418 		self->speed = 100;
1419 	}
1420 
1421 	if ( !self->target ) {
1422 		G_Printf ("func_train without a target at %s\n", vtos(self->r.absmin));
1423 		G_FreeEntity( self );
1424 		return;
1425 	}
1426 
1427 	trap_SetBrushModel( self, self->model );
1428 	InitMover( self );
1429 
1430 	self->reached = Reached_Train;
1431 
1432 	// start trains on the second frame, to make sure their targets have had
1433 	// a chance to spawn
1434 	self->nextthink = level.time + FRAMETIME;
1435 	self->think = Think_SetupTrainTargets;
1436 }
1437 
1438 /*
1439 ===============================================================================
1440 
1441 STATIC
1442 
1443 ===============================================================================
1444 */
1445 
1446 
1447 /*QUAKED func_static (0 .5 .8) ?
1448 A bmodel that just sits there, doing nothing.  Can be used for conditional walls and models.
1449 "model2"	.md3 model to also draw
1450 "color"		constantLight color
1451 "light"		constantLight radius
1452 */
SP_func_static(gentity_t * ent)1453 void SP_func_static( gentity_t *ent ) {
1454 	trap_SetBrushModel( ent, ent->model );
1455 	InitMover( ent );
1456 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
1457 	VectorCopy( ent->s.origin, ent->r.currentOrigin );
1458 }
1459 
1460 
1461 /*
1462 ===============================================================================
1463 
1464 ROTATING
1465 
1466 ===============================================================================
1467 */
1468 
1469 
1470 /*QUAKED func_rotating (0 .5 .8) ? START_ON - X_AXIS Y_AXIS
1471 You need to have an origin brush as part of this entity.  The center of that brush will be
1472 the point around which it is rotated. It will rotate around the Z axis by default.  You can
1473 check either the X_AXIS or Y_AXIS box to change that.
1474 
1475 "model2"	.md3 model to also draw
1476 "speed"		determines how fast it moves; default value is 100.
1477 "dmg"		damage to inflict when blocked (2 default)
1478 "color"		constantLight color
1479 "light"		constantLight radius
1480 */
SP_func_rotating(gentity_t * ent)1481 void SP_func_rotating (gentity_t *ent) {
1482 	if ( !ent->speed ) {
1483 		ent->speed = 100;
1484 	}
1485 
1486 	// set the axis of rotation
1487 	ent->s.apos.trType = TR_LINEAR;
1488 	if ( ent->spawnflags & 4 ) {
1489 		ent->s.apos.trDelta[2] = ent->speed;
1490 	} else if ( ent->spawnflags & 8 ) {
1491 		ent->s.apos.trDelta[0] = ent->speed;
1492 	} else {
1493 		ent->s.apos.trDelta[1] = ent->speed;
1494 	}
1495 
1496 	if (!ent->damage) {
1497 		ent->damage = 2;
1498 	}
1499 
1500 	trap_SetBrushModel( ent, ent->model );
1501 	InitMover( ent );
1502 
1503 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
1504 	VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
1505 	VectorCopy( ent->s.apos.trBase, ent->r.currentAngles );
1506 
1507 	trap_LinkEntity( ent );
1508 }
1509 
1510 
1511 /*
1512 ===============================================================================
1513 
1514 BOBBING
1515 
1516 ===============================================================================
1517 */
1518 
1519 
1520 /*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
1521 Normally bobs on the Z axis
1522 "model2"	.md3 model to also draw
1523 "height"	amplitude of bob (32 default)
1524 "speed"		seconds to complete a bob cycle (4 default)
1525 "phase"		the 0.0 to 1.0 offset in the cycle to start at
1526 "dmg"		damage to inflict when blocked (2 default)
1527 "color"		constantLight color
1528 "light"		constantLight radius
1529 */
SP_func_bobbing(gentity_t * ent)1530 void SP_func_bobbing (gentity_t *ent) {
1531 	float		height;
1532 	float		phase;
1533 
1534 	G_SpawnFloat( "speed", "4", &ent->speed );
1535 	G_SpawnFloat( "height", "32", &height );
1536 	G_SpawnInt( "dmg", "2", &ent->damage );
1537 	G_SpawnFloat( "phase", "0", &phase );
1538 
1539 	trap_SetBrushModel( ent, ent->model );
1540 	InitMover( ent );
1541 
1542 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
1543 	VectorCopy( ent->s.origin, ent->r.currentOrigin );
1544 
1545 	ent->s.pos.trDuration = ent->speed * 1000;
1546 	ent->s.pos.trTime = ent->s.pos.trDuration * phase;
1547 	ent->s.pos.trType = TR_SINE;
1548 
1549 	// set the axis of bobbing
1550 	if ( ent->spawnflags & 1 ) {
1551 		ent->s.pos.trDelta[0] = height;
1552 	} else if ( ent->spawnflags & 2 ) {
1553 		ent->s.pos.trDelta[1] = height;
1554 	} else {
1555 		ent->s.pos.trDelta[2] = height;
1556 	}
1557 }
1558 
1559 /*
1560 ===============================================================================
1561 
1562 PENDULUM
1563 
1564 ===============================================================================
1565 */
1566 
1567 
1568 /*QUAKED func_pendulum (0 .5 .8) ?
1569 You need to have an origin brush as part of this entity.
1570 Pendulums always swing north / south on unrotated models.  Add an angles field to the model to allow rotation in other directions.
1571 Pendulum frequency is a physical constant based on the length of the beam and gravity.
1572 "model2"	.md3 model to also draw
1573 "speed"		the number of degrees each way the pendulum swings, (30 default)
1574 "phase"		the 0.0 to 1.0 offset in the cycle to start at
1575 "dmg"		damage to inflict when blocked (2 default)
1576 "color"		constantLight color
1577 "light"		constantLight radius
1578 */
SP_func_pendulum(gentity_t * ent)1579 void SP_func_pendulum(gentity_t *ent) {
1580 	float		freq;
1581 	float		length;
1582 	float		phase;
1583 	float		speed;
1584 
1585 	G_SpawnFloat( "speed", "30", &speed );
1586 	G_SpawnInt( "dmg", "2", &ent->damage );
1587 	G_SpawnFloat( "phase", "0", &phase );
1588 
1589 	trap_SetBrushModel( ent, ent->model );
1590 
1591 	// find pendulum length
1592 	length = fabs( ent->r.mins[2] );
1593 	if ( length < 8 ) {
1594 		length = 8;
1595 	}
1596 
1597 	freq = 1 / ( M_PI * 2 ) * sqrt( g_gravity.value / ( 3 * length ) );
1598 
1599 	ent->s.pos.trDuration = ( 1000 / freq );
1600 
1601 	InitMover( ent );
1602 
1603 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
1604 	VectorCopy( ent->s.origin, ent->r.currentOrigin );
1605 
1606 	VectorCopy( ent->s.angles, ent->s.apos.trBase );
1607 
1608 	ent->s.apos.trDuration = 1000 / freq;
1609 	ent->s.apos.trTime = ent->s.apos.trDuration * phase;
1610 	ent->s.apos.trType = TR_SINE;
1611 	ent->s.apos.trDelta[2] = speed;
1612 }
1613