1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7 
8 This file is part of the OpenJK source code.
9 
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23 
24 #include "bg_public.h"
25 #include "../cgame/cg_local.h"
26 #include "g_functions.h"
27 #include "objectives.h"
28 #include "g_local.h"
29 
30 #include "../icarus/IcarusInterface.h"
31 
32 
33 int	BMS_START = 0;
34 int	BMS_MID = 1;
35 int	BMS_END = 2;
36 
37 extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
38 void CalcTeamDoorCenter ( gentity_t *ent, vec3_t center );
39 void InitMover( gentity_t *ent ) ;
40 
41 /*
42 ===============================================================================
43 
44 PUSHMOVE
45 
46 ===============================================================================
47 */
48 
49 void MatchTeam( gentity_t *teamLeader, int moverState, int time );
50 extern qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2);
51 
52 typedef struct {
53 	gentity_t	*ent;
54 	vec3_t	origin;
55 	vec3_t	angles;
56 	float	deltayaw;
57 } pushed_t;
58 pushed_t	pushed[MAX_GENTITIES], *pushed_p;
59 
60 
61 /*
62 -------------------------
63 G_SetLoopSound
64 -------------------------
65 */
66 
G_PlayDoorLoopSound(gentity_t * ent)67 void G_PlayDoorLoopSound( gentity_t *ent )
68 {
69 	if ( VALIDSTRING( ent->soundSet ) == false )
70 		return;
71 
72 	sfxHandle_t	sfx = CAS_GetBModelSound( ent->soundSet, BMS_MID );
73 
74 	if ( sfx == -1 )
75 	{
76 		ent->s.loopSound = 0;
77 		return;
78 	}
79 
80 	ent->s.loopSound = sfx;
81 }
82 
83 /*
84 -------------------------
85 G_PlayDoorSound
86 -------------------------
87 */
88 
G_PlayDoorSound(gentity_t * ent,int type)89 void G_PlayDoorSound( gentity_t *ent, int type )
90 {
91 	if ( VALIDSTRING( ent->soundSet ) == false )
92 		return;
93 
94 	sfxHandle_t	sfx = CAS_GetBModelSound( ent->soundSet, type );
95 
96 	if ( sfx == -1 )
97 		return;
98 
99 	vec3_t	doorcenter;
100 	CalcTeamDoorCenter( ent, doorcenter );
101 	if ( ent->activator && ent->activator->client && ent->activator->client->playerTeam == TEAM_PLAYER )
102 	{
103 		AddSoundEvent( ent->activator, doorcenter, 128, AEL_MINOR, qfalse, qtrue );
104 	}
105 
106 	G_AddEvent( ent, EV_BMODEL_SOUND, sfx );
107 }
108 
109 /*
110 ============
111 G_TestEntityPosition
112 
113 ============
114 */
G_TestEntityPosition(gentity_t * ent)115 gentity_t	*G_TestEntityPosition( gentity_t *ent ) {
116 	trace_t	tr;
117 	int		mask;
118 
119 	if ( (ent->client && ent->health <= 0) || !ent->clipmask )
120 	{//corpse or something with no clipmask
121 		mask = MASK_SOLID;
122 	}
123 	else
124 	{
125 		mask = ent->clipmask;
126 	}
127 	if ( ent->client )
128 	{
129 		gi.trace( &tr, ent->client->ps.origin, ent->mins, ent->maxs, ent->client->ps.origin, ent->s.number, mask, (EG2_Collision)0, 0 );
130 	}
131 	else
132 	{
133 		if ( ent->s.eFlags & EF_MISSILE_STICK )//Arggh, this is dumb...but when it used the bbox, it was pretty much always in solid when it is riding something..which is wrong..so I changed it to basically be a point contents check
134 		{
135 			gi.trace( &tr, ent->s.pos.trBase, vec3_origin, vec3_origin, ent->s.pos.trBase, ent->s.number, mask, (EG2_Collision)0, 0 );
136 		}
137 		else
138 		{
139 			gi.trace( &tr, ent->s.pos.trBase, ent->mins, ent->maxs, ent->s.pos.trBase, ent->s.number, mask, (EG2_Collision)0, 0 );
140 		}
141 	}
142 
143 	if (tr.startsolid)
144 		return &g_entities[ tr.entityNum ];
145 
146 	return NULL;
147 }
148 
149 
150 /*
151 ==================
152 G_TryPushingEntity
153 
154 Returns qfalse if the move is blocked
155 ==================
156 */
157 extern qboolean G_OkayToRemoveCorpse( gentity_t *self );
158 
G_TryPushingEntity(gentity_t * check,gentity_t * pusher,vec3_t move,vec3_t amove)159 qboolean	G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) {
160 	vec3_t		forward, right, up;
161 	vec3_t		org, org2, move2;
162 	gentity_t	*block;
163 
164 	/*
165 	// EF_MOVER_STOP will just stop when contacting another entity
166 	// instead of pushing it, but entities can still ride on top of it
167 	if ( ( pusher->s.eFlags & EF_MOVER_STOP ) &&
168 		check->s.groundEntityNum != pusher->s.number ) {
169 		return qfalse;
170 	}
171 	*/
172 
173 	// save off the old position
174 	if (pushed_p > &pushed[MAX_GENTITIES]) {
175 		G_Error( "pushed_p > &pushed[MAX_GENTITIES]" );
176 	}
177 	pushed_p->ent = check;
178 	VectorCopy (check->s.pos.trBase, pushed_p->origin);
179 	VectorCopy (check->s.apos.trBase, pushed_p->angles);
180 	if ( check->client ) {
181 		pushed_p->deltayaw = check->client->ps.delta_angles[YAW];
182 		VectorCopy (check->client->ps.origin, pushed_p->origin);
183 	}
184 	pushed_p++;
185 
186 	// we need this for pushing things later
187 	VectorSubtract (vec3_origin, amove, org);
188 	AngleVectors (org, forward, right, up);
189 
190 	// try moving the contacted entity
191 	VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase);
192 	if (check->client) {
193 		// make sure the client's view rotates when on a rotating mover
194 		check->client->ps.delta_angles[YAW] += ANGLE2SHORT(amove[YAW]);
195 	}
196 
197 	// figure movement due to the pusher's amove
198 	VectorSubtract (check->s.pos.trBase, pusher->currentOrigin, org);
199 	org2[0] = DotProduct (org, forward);
200 	org2[1] = -DotProduct (org, right);
201 	org2[2] = DotProduct (org, up);
202 	VectorSubtract (org2, org, move2);
203 	VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase);
204 	if ( check->client ) {
205 		VectorAdd (check->client->ps.origin, move, check->client->ps.origin);
206 		VectorAdd (check->client->ps.origin, move2, check->client->ps.origin);
207 	}
208 
209 	// may have pushed them off an edge
210 	if ( check->s.groundEntityNum != pusher->s.number ) {
211 		check->s.groundEntityNum = ENTITYNUM_NONE;
212 	}
213 
214 	/*
215 	if ( check->client && check->health <= 0 && check->contents == CONTENTS_CORPSE )
216 	{//sigh... allow pushing corpses into walls... problem is, they'll be stuck in there... maybe just remove them?
217 		return qtrue;
218 	}
219 	*/
220 	block = G_TestEntityPosition( check );
221 	if (!block) {
222 		// pushed ok
223 		if ( check->client ) {
224 			VectorCopy( check->client->ps.origin, check->currentOrigin );
225 		} else {
226 			VectorCopy( check->s.pos.trBase, check->currentOrigin );
227 		}
228 		gi.linkentity (check);
229 		return qtrue;
230 	}
231 
232 	// if it is ok to leave in the old position, do it
233 	// this is only relevent for riding entities, not pushed
234 	// Sliding trapdoors can cause this.
235 	VectorCopy( (pushed_p-1)->origin, check->s.pos.trBase);
236 	if ( check->client ) {
237 		VectorCopy( (pushed_p-1)->origin, check->client->ps.origin);
238 	}
239 	VectorCopy( (pushed_p-1)->angles, check->s.apos.trBase );
240 	block = G_TestEntityPosition (check);
241 	if ( !block ) {
242 		check->s.groundEntityNum = ENTITYNUM_NONE;
243 		pushed_p--;
244 		return qtrue;
245 	}
246 
247 	// blocked
248 	if ( pusher->damage )
249 	{//Do damage
250 		if ( (pusher->spawnflags&MOVER_CRUSHER)//a crusher
251  			&& check->s.clientNum >= MAX_CLIENTS//not the player
252 			&& check->client //NPC
253 			&& check->health <= 0 //dead
254 			&& G_OkayToRemoveCorpse( check ) )//okay to remove him
255 		{//crusher stuck on a non->player corpse that does not have a key and is not running a script
256 			G_FreeEntity( check );
257 		}
258 		else
259 		{
260 			G_Damage(check, pusher, pusher->activator, move, check->currentOrigin, pusher->damage, 0, MOD_CRUSH );
261 		}
262 	}
263 
264 	return qfalse;
265 }
266 
267 
268 /*
269 ============
270 G_MoverPush
271 
272 Objects need to be moved back on a failed push,
273 otherwise riders would continue to slide.
274 If qfalse is returned, *obstacle will be the blocking entity
275 ============
276 */
G_MoverPush(gentity_t * pusher,vec3_t move,vec3_t amove,gentity_t ** obstacle)277 qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **obstacle ) {
278 	qboolean	notMoving;
279 	int			i, e;
280 	int			listedEntities;
281 	vec3_t		mins, maxs;
282 	vec3_t		pusherMins, pusherMaxs, totalMins, totalMaxs;
283 	pushed_t	*p;
284 	gentity_t	*entityList[MAX_GENTITIES];
285 	gentity_t	*check;
286 
287 	*obstacle = NULL;
288 
289 
290 	if ( !pusher->bmodel )
291 	{//misc_model_breakable
292 		VectorAdd( pusher->currentOrigin, pusher->mins, pusherMins );
293 		VectorAdd( pusher->currentOrigin, pusher->maxs, pusherMaxs );
294 	}
295 
296 	// mins/maxs are the bounds at the destination
297 	// totalMins / totalMaxs are the bounds for the entire move
298 	if ( pusher->currentAngles[0] || pusher->currentAngles[1] || pusher->currentAngles[2]
299 		|| amove[0] || amove[1] || amove[2] )
300 	{
301 		float		radius;
302 
303 		radius = RadiusFromBounds( pusher->mins, pusher->maxs );
304 		for ( i = 0 ; i < 3 ; i++ )
305 		{
306 			mins[i] = pusher->currentOrigin[i] + move[i] - radius;
307 			maxs[i] = pusher->currentOrigin[i] + move[i] + radius;
308 			totalMins[i] = mins[i] - move[i];
309 			totalMaxs[i] = maxs[i] - move[i];
310 		}
311 	}
312 	else
313 	{
314 		for (i=0 ; i<3 ; i++)
315 		{
316 			mins[i] = pusher->absmin[i] + move[i];
317 			maxs[i] = pusher->absmax[i] + move[i];
318 		}
319 
320 		VectorCopy( pusher->absmin, totalMins );
321 		VectorCopy( pusher->absmax, totalMaxs );
322 		for (i=0 ; i<3 ; i++)
323 		{
324 			if ( move[i] > 0 )
325 			{
326 				totalMaxs[i] += move[i];
327 			}
328 			else
329 			{
330 				totalMins[i] += move[i];
331 			}
332 		}
333 	}
334 
335 	// unlink the pusher so we don't get it in the entityList
336 	gi.unlinkentity( pusher );
337 
338 	listedEntities = gi.EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES );
339 
340 	// move the pusher to it's final position
341 	VectorAdd( pusher->currentOrigin, move, pusher->currentOrigin );
342 	VectorAdd( pusher->currentAngles, amove, pusher->currentAngles );
343 	gi.linkentity( pusher );
344 
345 	notMoving = (qboolean)(VectorCompare( vec3_origin, move )&&VectorCompare( vec3_origin, amove ));
346 
347 	// see if any solid entities are inside the final position
348 	for ( e = 0 ; e < listedEntities ; e++ ) {
349 		check = entityList[ e ];
350 
351 		if (( check->s.eFlags & EF_MISSILE_STICK ) && (notMoving || check->s.groundEntityNum < 0 || check->s.groundEntityNum >= ENTITYNUM_NONE ))
352 		{
353 			// special case hack for sticky things, destroy it if we aren't attached to the thing that is moving, but the moving thing is pushing us
354 			G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH );
355 			continue;
356 		}
357 
358 		// only push items and players
359 		if ( check->s.eType != ET_ITEM )
360 		{
361 			//FIXME: however it allows items to be pushed through stuff, do same for corpses?
362 			if ( check->s.eType != ET_PLAYER )
363 			{
364 				if ( !( check->s.eFlags & EF_MISSILE_STICK ))
365 				{
366 					// cannot be pushed by this mover
367 					continue;
368 				}
369 			}
370 			/*
371 			else if ( check->health <= 0 )
372 			{//For now, don't push on dead players
373 				continue;
374 			}
375 			*/
376 			else if ( !pusher->bmodel )
377 			{
378 				vec3_t	checkMins, checkMaxs;
379 
380 				VectorAdd( check->currentOrigin, check->mins, checkMins );
381 				VectorAdd( check->currentOrigin, check->maxs, checkMaxs );
382 
383 				if ( G_BoundsOverlap( checkMins, checkMaxs, pusherMins, pusherMaxs ) )
384 				{//They're inside me already, no push - FIXME: we're testing a moves spot, aren't we, so we could have just moved inside them?
385 					continue;
386 				}
387 			}
388 		}
389 
390 
391 		if ( check->maxs[0] - check->mins[0] <= 0 &&
392 				check->maxs[1] - check->mins[1] <= 0 &&
393 				check->maxs[2] - check->mins[2] <= 0 )
394 		{//no size, don't push
395 			continue;
396 		}
397 
398 		// if the entity is standing on the pusher, it will definitely be moved
399 		if ( check->s.groundEntityNum != pusher->s.number ) {
400 			// see if the ent needs to be tested
401 			if ( check->absmin[0] >= maxs[0]
402 			|| check->absmin[1] >= maxs[1]
403 			|| check->absmin[2] >= maxs[2]
404 			|| check->absmax[0] <= mins[0]
405 			|| check->absmax[1] <= mins[1]
406 			|| check->absmax[2] <= mins[2] ) {
407 				continue;
408 			}
409 			// see if the ent's bbox is inside the pusher's final position
410 			// this does allow a fast moving object to pass through a thin entity...
411 			if ( G_TestEntityPosition( check ) != pusher )
412 			{
413 				continue;
414 			}
415 		}
416 
417 		if ( ((pusher->spawnflags&2)&&!Q_stricmp("func_breakable",pusher->classname))
418 			||((pusher->spawnflags&16)&&!Q_stricmp("func_static",pusher->classname)) )
419 		{//ugh, avoid stricmp with a unique flag
420 			//Damage on impact
421 			if ( pusher->damage )
422 			{//Do damage
423 				G_Damage( check, pusher, pusher->activator, move, check->currentOrigin, pusher->damage, 0, MOD_CRUSH );
424 				if ( pusher->health >= 0 && pusher->takedamage && !(pusher->spawnflags&1) )
425 				{//do some damage to me, too
426 					G_Damage( pusher, check, pusher->activator, move, pusher->s.pos.trBase, floor(pusher->damage/4.0f), 0, MOD_CRUSH );
427 				}
428 			}
429 		}
430 		// really need a flag like MOVER_TOUCH that calls the ent's touch function here, instead of this stricmp crap
431 		else if ( (pusher->spawnflags&2) && !Q_stricmp( "func_rotating", pusher->classname ) )
432 		{
433 			GEntity_TouchFunc( pusher, check, NULL );
434 			continue;	// don't want it blocking so skip past it
435 		}
436 
437 		vec3_t oldOrg;
438 
439 		VectorCopy( check->s.pos.trBase, oldOrg );
440 
441 		// the entity needs to be pushed
442 		if ( G_TryPushingEntity( check, pusher, move, amove ) )
443 		{
444 			// the mover wasn't blocked
445 			if ( check->s.eFlags & EF_MISSILE_STICK )
446 			{
447 				if ( !VectorCompare( oldOrg, check->s.pos.trBase ))
448 				{
449 					// and the rider was actually pushed, so interpolate position change to smooth out the ride
450 					check->s.pos.trType = TR_INTERPOLATE;
451 					continue;
452 				}
453 				//else..the mover wasn't blocked & we are riding the mover & but the rider did not move even though the mover itself did...so
454 				//	drop through and let it blow up the rider up
455 			}
456 			else
457 			{
458 				continue;
459 			}
460 		}
461 
462 		// the move was blocked even after pushing this rider
463 		if ( check->s.eFlags & EF_MISSILE_STICK )
464 		{
465 			// so nuke 'em so they don't block us anymore
466 			G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH );
467 			continue;
468 		}
469 
470 		// save off the obstacle so we can call the block function (crush, etc)
471 		*obstacle = check;
472 
473 		// move back any entities we already moved
474 		// go backwards, so if the same entity was pushed
475 		// twice, it goes back to the original position
476 		for ( p=pushed_p-1 ; p>=pushed ; p-- ) {
477 			VectorCopy (p->origin, p->ent->s.pos.trBase);
478 			VectorCopy (p->angles, p->ent->s.apos.trBase);
479 			if ( p->ent->client ) {
480 				p->ent->client->ps.delta_angles[YAW] = p->deltayaw;
481 				VectorCopy (p->origin, p->ent->client->ps.origin);
482 			}
483 			gi.linkentity (p->ent);
484 		}
485 		return qfalse;
486 	}
487 
488 	return qtrue;
489 }
490 
491 
492 /*
493 =================
494 G_MoverTeam
495 =================
496 */
G_MoverTeam(gentity_t * ent)497 void G_MoverTeam( gentity_t *ent ) {
498 	vec3_t		move, amove;
499 	gentity_t	*part, *obstacle;
500 	vec3_t		origin, angles;
501 
502 	obstacle = NULL;
503 
504 	// make sure all team slaves can move before commiting
505 	// any moves or calling any think functions
506 	// if the move is blocked, all moved objects will be backed out
507 	pushed_p = pushed;
508 	for (part = ent ; part ; part=part->teamchain)
509 	{
510 		// get current position
511 		part->s.eFlags &= ~EF_BLOCKED_MOVER;
512 		EvaluateTrajectory( &part->s.pos, level.time, origin );
513 		EvaluateTrajectory( &part->s.apos, level.time, angles );
514 		VectorSubtract( origin, part->currentOrigin, move );
515 		VectorSubtract( angles, part->currentAngles, amove );
516 		if ( !G_MoverPush( part, move, amove, &obstacle ) )
517 		{
518 			break;	// move was blocked
519 		}
520 	}
521 
522 	if (part)
523 	{
524 		// if the pusher has a "blocked" function, call it
525 		// go back to the previous position
526 		for ( part = ent ; part ; part = part->teamchain )
527 		{
528 			//Push up time so it doesn't wiggle when blocked
529 			part->s.pos.trTime += level.time - level.previousTime;
530 			part->s.apos.trTime += level.time - level.previousTime;
531 			EvaluateTrajectory( &part->s.pos, level.time, part->currentOrigin );
532 			EvaluateTrajectory( &part->s.apos, level.time, part->currentAngles );
533 			gi.linkentity( part );
534 			part->s.eFlags |= EF_BLOCKED_MOVER;
535 		}
536 
537 		if ( ent->e_BlockedFunc != blockedF_NULL )
538 		{// this check no longer necessary, done internally below, but it's here for reference
539 			GEntity_BlockedFunc( ent, obstacle );
540 		}
541 		return;
542 	}
543 
544 	// the move succeeded
545 	for ( part = ent ; part ; part = part->teamchain )
546 	{
547 		// call the reached function if time is at or past end point
548 		if ( part->s.pos.trType == TR_LINEAR_STOP ||
549 			part->s.pos.trType == TR_NONLINEAR_STOP )
550 		{
551 			if ( level.time >= part->s.pos.trTime + part->s.pos.trDuration )
552 			{
553 				GEntity_ReachedFunc( part );
554 			}
555 		}
556 	}
557 }
558 
559 /*
560 ================
561 G_RunMover
562 
563 ================
564 */
G_RunMover(gentity_t * ent)565 void G_RunMover( gentity_t *ent ) {
566 	// if not a team captain, don't do anything, because
567 	// the captain will handle everything
568 	if ( ent->flags & FL_TEAMSLAVE ) {
569 		return;
570 	}
571 
572 	// if stationary at one of the positions, don't move anything
573 	if ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) {
574 		G_MoverTeam( ent );
575 	}
576 
577 /*	if ( ent->classname && Q_stricmp("misc_turret", ent->classname ) == 0 )
578 	{
579 		rebolt_turret( ent );
580 	}
581 */
582 	// check think function
583 	G_RunThink( ent );
584 }
585 
586 /*
587 ============================================================================
588 
589 GENERAL MOVERS
590 
591 Doors, plats, and buttons are all binary (two position) movers
592 Pos1 is "at rest", pos2 is "activated"
593 ============================================================================
594 */
595 
596 /*
597 CalcTeamDoorCenter
598 
599 Finds all the doors of a team and returns their center position
600 */
601 
CalcTeamDoorCenter(gentity_t * ent,vec3_t center)602 void CalcTeamDoorCenter ( gentity_t *ent, vec3_t center )
603 {
604 	vec3_t		slavecenter;
605 	gentity_t	*slave;
606 
607 	//Start with our center
608 	VectorAdd(ent->mins, ent->maxs, center);
609 	VectorScale(center, 0.5, center);
610 	for ( slave = ent->teamchain ; slave ; slave = slave->teamchain )
611 	{
612 		//Find slave's center
613 		VectorAdd(slave->mins, slave->maxs, slavecenter);
614 		VectorScale(slavecenter, 0.5, slavecenter);
615 		//Add that to our own, find middle
616 		VectorAdd(center, slavecenter, center);
617 		VectorScale(center, 0.5, center);
618 	}
619 }
620 
621 /*
622 ===============
623 SetMoverState
624 ===============
625 */
SetMoverState(gentity_t * ent,moverState_t moverState,int time)626 void SetMoverState( gentity_t *ent, moverState_t moverState, int time ) {
627 	vec3_t			delta;
628 	float			f;
629 
630 	ent->moverState = moverState;
631 
632 	ent->s.pos.trTime = time;
633 
634 	if ( ent->s.pos.trDuration <= 0 )
635 	{//Don't allow divide by zero!
636 		ent->s.pos.trDuration = 1;
637 	}
638 
639 	switch( moverState ) {
640 	case MOVER_POS1:
641 		VectorCopy( ent->pos1, ent->s.pos.trBase );
642 		ent->s.pos.trType = TR_STATIONARY;
643 		break;
644 	case MOVER_POS2:
645 		VectorCopy( ent->pos2, ent->s.pos.trBase );
646 		ent->s.pos.trType = TR_STATIONARY;
647 		break;
648 	case MOVER_1TO2:
649 		VectorCopy( ent->pos1, ent->s.pos.trBase );
650 		VectorSubtract( ent->pos2, ent->pos1, delta );
651 		f = 1000.0 / ent->s.pos.trDuration;
652 		VectorScale( delta, f, ent->s.pos.trDelta );
653 		if ( ent->alt_fire )
654 		{
655 			ent->s.pos.trType = TR_LINEAR_STOP;
656 		}
657 		else
658 		{
659 			ent->s.pos.trType = TR_NONLINEAR_STOP;
660 		}
661 		ent->s.eFlags &= ~EF_BLOCKED_MOVER;
662 		break;
663 	case MOVER_2TO1:
664 		VectorCopy( ent->pos2, ent->s.pos.trBase );
665 		VectorSubtract( ent->pos1, ent->pos2, delta );
666 		f = 1000.0 / ent->s.pos.trDuration;
667 		VectorScale( delta, f, ent->s.pos.trDelta );
668 		if ( ent->alt_fire )
669 		{
670 			ent->s.pos.trType = TR_LINEAR_STOP;
671 		}
672 		else
673 		{
674 			ent->s.pos.trType = TR_NONLINEAR_STOP;
675 		}
676 		ent->s.eFlags &= ~EF_BLOCKED_MOVER;
677 		break;
678 	}
679 	EvaluateTrajectory( &ent->s.pos, level.time, ent->currentOrigin );
680 	gi.linkentity( ent );
681 }
682 
683 /*
684 ================
685 MatchTeam
686 
687 All entities in a mover team will move from pos1 to pos2
688 in the same amount of time
689 ================
690 */
MatchTeam(gentity_t * teamLeader,int moverState,int time)691 void MatchTeam( gentity_t *teamLeader, int moverState, int time ) {
692 	gentity_t		*slave;
693 
694 	for ( slave = teamLeader ; slave ; slave = slave->teamchain ) {
695 		SetMoverState( slave, (moverState_t) moverState, time );
696 	}
697 }
698 
699 
700 
701 /*
702 ================
703 ReturnToPos1
704 ================
705 */
ReturnToPos1(gentity_t * ent)706 void ReturnToPos1( gentity_t *ent ) {
707 	ent->e_ThinkFunc = thinkF_NULL;
708 	ent->nextthink = 0;
709 	ent->s.time = level.time;
710 
711 	MatchTeam( ent, MOVER_2TO1, level.time );
712 
713 	// starting sound
714 	G_PlayDoorLoopSound( ent );
715 	G_PlayDoorSound( ent, BMS_START );	//??
716 }
717 
718 
719 /*
720 ================
721 Reached_BinaryMover
722 ================
723 */
724 
Reached_BinaryMover(gentity_t * ent)725 void Reached_BinaryMover( gentity_t *ent )
726 {
727 	// stop the looping sound
728 	ent->s.loopSound = 0;
729 
730 	if ( ent->moverState == MOVER_1TO2 )
731 	{//reached open
732 		// reached pos2
733 		SetMoverState( ent, MOVER_POS2, level.time );
734 
735 		vec3_t	doorcenter;
736 		CalcTeamDoorCenter( ent, doorcenter );
737 		if ( ent->activator && ent->activator->client && ent->activator->client->playerTeam == TEAM_PLAYER )
738 		{
739 			AddSightEvent( ent->activator, doorcenter, 256, AEL_MINOR, 1 );
740 		}
741 
742 		// play sound
743 		G_PlayDoorSound( ent, BMS_END );
744 
745 		if ( ent->wait < 0 )
746 		{//Done for good
747 			ent->e_ThinkFunc = thinkF_NULL;
748 			ent->nextthink = -1;
749 			ent->e_UseFunc = useF_NULL;
750 		}
751 		else
752 		{
753 			// return to pos1 after a delay
754 			ent->e_ThinkFunc = thinkF_ReturnToPos1;
755 			if(ent->spawnflags & 8)
756 			{//Toggle, keep think, wait for next use?
757 				ent->nextthink = -1;
758 			}
759 			else
760 			{
761 				ent->nextthink = level.time + ent->wait;
762 			}
763 		}
764 
765 		// fire targets
766 		if ( !ent->activator )
767 		{
768 			ent->activator = ent;
769 		}
770 		G_UseTargets2( ent, ent->activator, ent->opentarget );
771 	}
772 	else if ( ent->moverState == MOVER_2TO1 )
773 	{//closed
774 		// reached pos1
775 		SetMoverState( ent, MOVER_POS1, level.time );
776 
777 		vec3_t	doorcenter;
778 		CalcTeamDoorCenter( ent, doorcenter );
779 		if ( ent->activator && ent->activator->client && ent->activator->client->playerTeam == TEAM_PLAYER )
780 		{
781 			AddSightEvent( ent->activator, doorcenter, 256, AEL_MINOR, 1 );
782 		}
783 		// play sound
784 		G_PlayDoorSound( ent, BMS_END );
785 
786 		// close areaportals
787 		if ( ent->teammaster == ent || !ent->teammaster )
788 		{
789 			gi.AdjustAreaPortalState( ent, qfalse );
790 		}
791 		G_UseTargets2( ent, ent->activator, ent->closetarget );
792 	}
793 	else
794 	{
795 		G_Error( "Reached_BinaryMover: bad moverState" );
796 	}
797 }
798 
799 
800 /*
801 ================
802 Use_BinaryMover_Go
803 ================
804 */
Use_BinaryMover_Go(gentity_t * ent)805 void Use_BinaryMover_Go( gentity_t *ent )
806 {
807 	int		total;
808 	int		partial;
809 //	gentity_t	*other = ent->enemy;
810 	gentity_t	*activator = ent->activator;
811 
812 	ent->activator = activator;
813 
814 	if ( ent->moverState == MOVER_POS1 )
815 	{
816 		// start moving 50 msec later, becase if this was player
817 		// triggered, level.time hasn't been advanced yet
818 		MatchTeam( ent, MOVER_1TO2, level.time + 50 );
819 
820 		vec3_t	doorcenter;
821 		CalcTeamDoorCenter( ent, doorcenter );
822 		if ( ent->activator && ent->activator->client && ent->activator->client->playerTeam == TEAM_PLAYER )
823 		{
824 			AddSightEvent( ent->activator, doorcenter, 256, AEL_MINOR, 1 );
825 		}
826 
827 		// starting sound
828 		G_PlayDoorLoopSound( ent );
829 		G_PlayDoorSound( ent, BMS_START );
830 		ent->s.time = level.time;
831 
832 		// open areaportal
833 		if ( ent->teammaster == ent || !ent->teammaster ) {
834 			gi.AdjustAreaPortalState( ent, qtrue );
835 		}
836 		G_UseTargets( ent, ent->activator );
837 		return;
838 	}
839 
840 	// if all the way up, just delay before coming down
841 	if ( ent->moverState == MOVER_POS2 ) {
842 		//have to do this because the delay sets our think to Use_BinaryMover_Go
843 		ent->e_ThinkFunc = thinkF_ReturnToPos1;
844 		if ( ent->spawnflags & 8 )
845 		{//TOGGLE doors don't use wait!
846 			ent->nextthink = level.time + FRAMETIME;
847 		}
848 		else
849 		{
850 			ent->nextthink = level.time + ent->wait;
851 		}
852 		G_UseTargets2( ent, ent->activator, ent->target2 );
853 		return;
854 	}
855 
856 	// only partway down before reversing
857 	if ( ent->moverState == MOVER_2TO1 )
858 	{
859 		total = ent->s.pos.trDuration-50;
860 		if ( ent->s.pos.trType == TR_NONLINEAR_STOP )
861 		{
862 			vec3_t curDelta;
863 			VectorSubtract( ent->currentOrigin, ent->pos1, curDelta );
864 			float fPartial = VectorLength( curDelta )/VectorLength( ent->s.pos.trDelta );
865 			VectorScale( ent->s.pos.trDelta, fPartial, curDelta );
866 			fPartial /= ent->s.pos.trDuration;
867 			fPartial /= 0.001f;
868 			fPartial = acos( fPartial );
869 			fPartial = RAD2DEG( fPartial );
870 			fPartial = (90.0f - fPartial)/90.0f*ent->s.pos.trDuration;
871 			partial = total - floor( fPartial );
872 		}
873 		else
874 		{
875 			partial = level.time - ent->s.pos.trTime;//ent->s.time;
876 		}
877 
878 		if ( partial > total ) {
879 			partial = total;
880 		}
881 		ent->s.pos.trTime = level.time - ( total - partial );//ent->s.time;
882 
883 		MatchTeam( ent, MOVER_1TO2, ent->s.pos.trTime );
884 
885 		G_PlayDoorSound( ent, BMS_START );
886 
887 		return;
888 	}
889 
890 	// only partway up before reversing
891 	if ( ent->moverState == MOVER_1TO2 )
892 	{
893 		total = ent->s.pos.trDuration-50;
894 		if ( ent->s.pos.trType == TR_NONLINEAR_STOP )
895 		{
896 			vec3_t curDelta;
897 			VectorSubtract( ent->currentOrigin, ent->pos2, curDelta );
898 			float fPartial = VectorLength( curDelta )/VectorLength( ent->s.pos.trDelta );
899 			VectorScale( ent->s.pos.trDelta, fPartial, curDelta );
900 			fPartial /= ent->s.pos.trDuration;
901 			fPartial /= 0.001f;
902 			fPartial = acos( fPartial );
903 			fPartial = RAD2DEG( fPartial );
904 			fPartial = (90.0f - fPartial)/90.0f*ent->s.pos.trDuration;
905 			partial = total - floor( fPartial );
906 		}
907 		else
908 		{
909 			partial = level.time - ent->s.pos.trTime;//ent->s.time;
910 		}
911 		if ( partial > total ) {
912 			partial = total;
913 		}
914 
915 		ent->s.pos.trTime = level.time - ( total - partial );//ent->s.time;
916 		MatchTeam( ent, MOVER_2TO1, ent->s.pos.trTime );
917 
918 		G_PlayDoorSound( ent, BMS_START );
919 
920 		return;
921 	}
922 }
923 
UnLockDoors(gentity_t * const ent)924 void UnLockDoors(gentity_t *const ent)
925 {
926 	//noise?
927 	//go through and unlock the door and all the slaves
928 	gentity_t	*slave = ent;
929 	do
930 	{	// want to allow locked toggle doors, so keep the targetname
931 		if( !(slave->spawnflags & MOVER_TOGGLE) )
932 		{
933 			slave->targetname = NULL;//not usable ever again
934 		}
935 		slave->spawnflags &= ~MOVER_LOCKED;
936 		slave->s.frame = 1;//second stage of anim
937 		slave = slave->teamchain;
938 	} while  ( slave );
939 }
LockDoors(gentity_t * const ent)940 void LockDoors(gentity_t *const ent)
941 {
942 	//noise?
943 	//go through and lock the door and all the slaves
944 	gentity_t	*slave = ent;
945 	do
946 	{
947 		slave->spawnflags |= MOVER_LOCKED;
948 		slave->s.frame = 0;//first stage of anim
949 		slave = slave->teamchain;
950 	} while  ( slave );
951 }
952 /*
953 ================
954 Use_BinaryMover
955 ================
956 */
Use_BinaryMover(gentity_t * ent,gentity_t * other,gentity_t * activator)957 void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator )
958 {
959 	int	key;
960 	const char *text;
961 
962 	if ( ent->e_UseFunc == useF_NULL )
963 	{//I cannot be used anymore, must be a door with a wait of -1 that's opened.
964 		return;
965 	}
966 
967 	// only the master should be used
968 	if ( ent->flags & FL_TEAMSLAVE )
969 	{
970 		Use_BinaryMover( ent->teammaster, other, activator );
971 		return;
972 	}
973 
974 	if ( ent->svFlags & SVF_INACTIVE )
975 	{
976 		return;
977 	}
978 
979 	if ( ent->spawnflags & MOVER_LOCKED )
980 	{//a locked door, unlock it
981 		UnLockDoors(ent);
982 		return;
983 	}
984 
985 	if ( ent->spawnflags & MOVER_GOODIE )
986 	{
987 		if ( ent->fly_sound_debounce_time > level.time )
988 		{
989 			return;
990 		}
991 		else
992 		{
993 			key = INV_GoodieKeyCheck( activator );
994 			if (key)
995 			{//activator has a goodie key, remove it
996 				activator->client->ps.inventory[key]--;
997 				G_Sound( activator, G_SoundIndex( "sound/movers/goodie_pass.wav" ) );
998 				// once the goodie mover has been used, it no longer requires a goodie key
999 				ent->spawnflags &= ~MOVER_GOODIE;
1000 			}
1001 			else
1002 			{	//don't have a goodie key
1003 				G_Sound( activator, G_SoundIndex( "sound/movers/goodie_fail.wav" ) );
1004 				ent->fly_sound_debounce_time = level.time + 5000;
1005 				text = "cp @SP_INGAME_NEED_KEY_TO_OPEN";
1006 				//FIXME: temp message, only on certain difficulties?, graphic instead of text?
1007 				gi.SendServerCommand( 0, text );
1008 				return;
1009 			}
1010 		}
1011 	}
1012 
1013 	G_ActivateBehavior(ent,BSET_USE);
1014 
1015 	G_SetEnemy( ent, other );
1016 	ent->activator = activator;
1017 	if(ent->delay)
1018 	{
1019 		ent->e_ThinkFunc = thinkF_Use_BinaryMover_Go;
1020 		ent->nextthink = level.time + ent->delay;
1021 	}
1022 	else
1023 	{
1024 		Use_BinaryMover_Go(ent);
1025 	}
1026 }
1027 
1028 
1029 
1030 /*
1031 ================
1032 InitMover
1033 
1034 "pos1", "pos2", and "speed" should be set before calling,
1035 so the movement delta can be calculated
1036 ================
1037 */
InitMoverTrData(gentity_t * ent)1038 void InitMoverTrData( gentity_t *ent )
1039 {
1040 	vec3_t		move;
1041 	float		distance;
1042 
1043 	ent->s.pos.trType = TR_STATIONARY;
1044 	VectorCopy( ent->pos1, ent->s.pos.trBase );
1045 
1046 	// calculate time to reach second position from speed
1047 	VectorSubtract( ent->pos2, ent->pos1, move );
1048 	distance = VectorLength( move );
1049 	if ( ! ent->speed )
1050 	{
1051 		ent->speed = 100;
1052 	}
1053 	VectorScale( move, ent->speed, ent->s.pos.trDelta );
1054 	ent->s.pos.trDuration = distance * 1000 / ent->speed;
1055 	if ( ent->s.pos.trDuration <= 0 )
1056 	{
1057 		ent->s.pos.trDuration = 1;
1058 	}
1059 }
1060 
InitMover(gentity_t * ent)1061 void InitMover( gentity_t *ent )
1062 {
1063 	float		light;
1064 	vec3_t		color;
1065 	qboolean	lightSet, colorSet;
1066 
1067 	// if the "model2" key is set, use a seperate model
1068 	// for drawing, but clip against the brushes
1069 	if ( ent->model2 )
1070 	{
1071 		if ( strstr( ent->model2, ".glm" ))
1072 		{
1073 			ent->s.modelindex2 = G_ModelIndex( ent->model2 );
1074 			ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, ent->model2, ent->s.modelindex2, NULL_HANDLE, NULL_HANDLE, 0, 0 );
1075 			if ( ent->playerModel >= 0 )
1076 			{
1077 				ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue );
1078 			}
1079 
1080 			ent->s.radius = 120;
1081 		}
1082 		else
1083 		{
1084 			ent->s.modelindex2 = G_ModelIndex( ent->model2 );
1085 		}
1086 	}
1087 
1088 	// if the "color" or "light" keys are set, setup constantLight
1089 	lightSet = G_SpawnFloat( "light", "100", &light );
1090 	colorSet = G_SpawnVector( "color", "1 1 1", color );
1091 	if ( lightSet || colorSet ) {
1092 		int		r, g, b, i;
1093 
1094 		r = color[0] * 255;
1095 		if ( r > 255 ) {
1096 			r = 255;
1097 		}
1098 		g = color[1] * 255;
1099 		if ( g > 255 ) {
1100 			g = 255;
1101 		}
1102 		b = color[2] * 255;
1103 		if ( b > 255 ) {
1104 			b = 255;
1105 		}
1106 		i = light / 4;
1107 		if ( i > 255 ) {
1108 			i = 255;
1109 		}
1110 		ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
1111 	}
1112 
1113 	ent->e_UseFunc	   = useF_Use_BinaryMover;
1114 	ent->e_ReachedFunc = reachedF_Reached_BinaryMover;
1115 
1116 	ent->moverState = MOVER_POS1;
1117 	ent->svFlags = SVF_USE_CURRENT_ORIGIN;
1118 	if ( ent->spawnflags & MOVER_INACTIVE )
1119 	{// Make it inactive
1120 		ent->svFlags |= SVF_INACTIVE;
1121 	}
1122 	if(ent->spawnflags & MOVER_PLAYER_USE)
1123 	{//Can be used by the player's BUTTON_USE
1124 		ent->svFlags |= SVF_PLAYER_USABLE;
1125 	}
1126 	ent->s.eType = ET_MOVER;
1127 	VectorCopy( ent->pos1, ent->currentOrigin );
1128 	gi.linkentity( ent );
1129 
1130 	InitMoverTrData( ent );
1131 }
1132 
1133 
1134 /*
1135 ===============================================================================
1136 
1137 DOOR
1138 
1139 A use can be triggered either by a touch function, by being shot, or by being
1140 targeted by another entity.
1141 
1142 ===============================================================================
1143 */
1144 
1145 /*
1146 ================
1147 Blocked_Door
1148 ================
1149 */
Blocked_Door(gentity_t * ent,gentity_t * other)1150 void Blocked_Door( gentity_t *ent, gentity_t *other ) {
1151 
1152 	// remove anything other than a client -- no longer the case
1153 
1154 	// don't remove security keys or goodie keys
1155 	if ( (other->s.eType == ET_ITEM) && (other->item->giTag >= INV_GOODIE_KEY && other->item->giTag <= INV_SECURITY_KEY) )
1156 	{
1157 		// should we be doing anything special if a key blocks it... move it somehow..?
1158 	}
1159 	// if your not a client, or your a dead client remove yourself...
1160 	else if ( other->s.number && (!other->client || (other->client && other->health <= 0 && other->contents == CONTENTS_CORPSE && !other->message)) )
1161 	{
1162 		if ( !IIcarusInterface::GetIcarus()->IsRunning( other->m_iIcarusID ) /*!other->taskManager || !other->taskManager->IsRunning()*/ )
1163 		{
1164 			// if an item or weapon can we do a little explosion..?
1165 			G_FreeEntity( other );
1166 			return;
1167 		}
1168 	}
1169 
1170 	if ( ent->damage )
1171 	{
1172 		if ( (ent->spawnflags&MOVER_CRUSHER)//a crusher
1173  			&& other->s.clientNum >= MAX_CLIENTS//not the player
1174 			&& other->client //NPC
1175 			&& other->health <= 0 //dead
1176 			&& G_OkayToRemoveCorpse( other ) )//okay to remove him
1177 		{//crusher stuck on a non->player corpse that does not have a key and is not running a script
1178 			G_FreeEntity( other );
1179 		}
1180 		else
1181 		{
1182 			G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH );
1183 		}
1184 	}
1185 	if ( ent->spawnflags & MOVER_CRUSHER ) {
1186 		return;		// crushers don't reverse
1187 	}
1188 
1189 	// reverse direction
1190 	Use_BinaryMover( ent, ent, other );
1191 }
1192 
1193 
1194 /*
1195 ================
1196 Touch_DoorTrigger
1197 ================
1198 */
1199 
Touch_DoorTrigger(gentity_t * ent,gentity_t * other,trace_t * trace)1200 void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace )
1201 {
1202 	if ( ent->svFlags & SVF_INACTIVE )
1203 	{
1204 		return;
1205 	}
1206 
1207 	if ( ent->owner->spawnflags & MOVER_LOCKED )
1208 	{//don't even try to use the door if it's locked
1209 		return;
1210 	}
1211 
1212 	if ( ent->owner->moverState != MOVER_1TO2 )
1213 	{//Door is not already opening
1214 		//if ( ent->owner->moverState == MOVER_POS1 || ent->owner->moverState == MOVER_2TO1 )
1215 		//{//only check these if closed or closing
1216 
1217 		//If door is closed, opening or open, check this
1218 		Use_BinaryMover( ent->owner, ent, other );
1219 	}
1220 
1221 	/*
1222 	//Old style
1223 	if ( ent->owner->moverState != MOVER_1TO2 ) {
1224 		Use_BinaryMover( ent->owner, ent, other );
1225 	}
1226 	*/
1227 }
1228 
1229 /*
1230 ======================
1231 Think_SpawnNewDoorTrigger
1232 
1233 All of the parts of a door have been spawned, so create
1234 a trigger that encloses all of them
1235 ======================
1236 */
Think_SpawnNewDoorTrigger(gentity_t * ent)1237 void Think_SpawnNewDoorTrigger( gentity_t *ent )
1238 {
1239 	gentity_t		*other;
1240 	vec3_t		mins, maxs;
1241 	int			i, best;
1242 
1243 	// set all of the slaves as shootable
1244 	if ( ent->takedamage )
1245 	{
1246 		for ( other = ent ; other ; other = other->teamchain )
1247 		{
1248 			other->takedamage = qtrue;
1249 		}
1250 	}
1251 
1252 	// find the bounds of everything on the team
1253 	VectorCopy (ent->absmin, mins);
1254 	VectorCopy (ent->absmax, maxs);
1255 
1256 	for (other = ent->teamchain ; other ; other=other->teamchain) {
1257 		AddPointToBounds (other->absmin, mins, maxs);
1258 		AddPointToBounds (other->absmax, mins, maxs);
1259 	}
1260 
1261 	// find the thinnest axis, which will be the one we expand
1262 	best = 0;
1263 	for ( i = 1 ; i < 3 ; i++ ) {
1264 		if ( maxs[i] - mins[i] < maxs[best] - mins[best] ) {
1265 			best = i;
1266 		}
1267 	}
1268 	maxs[best] += 120;
1269 	mins[best] -= 120;
1270 
1271 	// create a trigger with this size
1272 	other = G_Spawn ();
1273 	VectorCopy (mins, other->mins);
1274 	VectorCopy (maxs, other->maxs);
1275 	other->owner = ent;
1276 	other->contents = CONTENTS_TRIGGER;
1277 	other->e_TouchFunc = touchF_Touch_DoorTrigger;
1278 	gi.linkentity (other);
1279 	other->classname = "trigger_door";
1280 
1281 	MatchTeam( ent, ent->moverState, level.time );
1282 }
1283 
Think_MatchTeam(gentity_t * ent)1284 void Think_MatchTeam( gentity_t *ent )
1285 {
1286 	MatchTeam( ent, ent->moverState, level.time );
1287 }
1288 
G_EntIsDoor(int entityNum)1289 qboolean G_EntIsDoor( int entityNum )
1290 {
1291 	if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD )
1292 	{
1293 		return qfalse;
1294 	}
1295 
1296 	gentity_t *ent = &g_entities[entityNum];
1297 	if ( ent && !Q_stricmp( "func_door", ent->classname ) )
1298 	{//blocked by a door
1299 		return qtrue;
1300 	}
1301 	return qfalse;
1302 }
1303 
G_FindDoorTrigger(gentity_t * ent)1304 gentity_t *G_FindDoorTrigger( gentity_t *ent )
1305 {
1306 	gentity_t *owner = NULL;
1307 	gentity_t *door = ent;
1308 	if ( door->flags & FL_TEAMSLAVE )
1309 	{//not the master door, get the master door
1310 		while ( door->teammaster && (door->flags&FL_TEAMSLAVE))
1311 		{
1312 			door = door->teammaster;
1313 		}
1314 	}
1315 	if ( door->targetname )
1316 	{//find out what is targeting it
1317 		//FIXME: if ent->targetname, check what kind of trigger/ent is targetting it?  If a normal trigger (active, etc), then it's okay?
1318 		while ( (owner = G_Find( owner, FOFS( target ), door->targetname )) != NULL )
1319 		{
1320 			if ( owner && (owner->contents&CONTENTS_TRIGGER) )
1321 			{
1322 				return owner;
1323 			}
1324 		}
1325 		owner = NULL;
1326 		while ( (owner = G_Find( owner, FOFS( target2 ), door->targetname )) != NULL )
1327 		{
1328 			if ( owner && (owner->contents&CONTENTS_TRIGGER) )
1329 			{
1330 				return owner;
1331 			}
1332 		}
1333 	}
1334 
1335 	owner = NULL;
1336 	while ( (owner = G_Find( owner, FOFS( classname ), "trigger_door" )) != NULL )
1337 	{
1338 		if ( owner->owner == door )
1339 		{
1340 			return owner;
1341 		}
1342 	}
1343 
1344 	return NULL;
1345 }
1346 
1347 qboolean G_TriggerActive( gentity_t *self );
G_EntIsUnlockedDoor(int entityNum)1348 qboolean G_EntIsUnlockedDoor( int entityNum )
1349 {
1350 	if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD )
1351 	{
1352 		return qfalse;
1353 	}
1354 
1355 	if ( G_EntIsDoor( entityNum ) )
1356 	{
1357 		gentity_t *ent = &g_entities[entityNum];
1358 		gentity_t *owner = NULL;
1359 		if ( ent->flags & FL_TEAMSLAVE )
1360 		{//not the master door, get the master door
1361 			while ( ent->teammaster && (ent->flags&FL_TEAMSLAVE))
1362 			{
1363 				ent = ent->teammaster;
1364 			}
1365 		}
1366 		if ( ent->targetname )
1367 		{//find out what is targetting it
1368 			owner = NULL;
1369 			//FIXME: if ent->targetname, check what kind of trigger/ent is targetting it?  If a normal trigger (active, etc), then it's okay?
1370 			while ( (owner = G_Find( owner, FOFS( target ), ent->targetname )) != NULL )
1371 			{
1372 				if ( !Q_stricmp( "trigger_multiple", owner->classname )
1373 				  || !Q_stricmp( "trigger_once", owner->classname ) )//FIXME: other triggers okay too?
1374 				{
1375 					if ( G_TriggerActive( owner ) )
1376 					{
1377 						return qtrue;
1378 					}
1379 				}
1380 			}
1381 			owner = NULL;
1382 			while ( (owner = G_Find( owner, FOFS( target2 ), ent->targetname )) != NULL )
1383 			{
1384 				if ( !Q_stricmp( "trigger_multiple", owner->classname ) )//FIXME: other triggers okay too?
1385 				{
1386 					if ( G_TriggerActive( owner ) )
1387 					{
1388 						return qtrue;
1389 					}
1390 				}
1391 			}
1392 			return qfalse;
1393 		}
1394 		else
1395 		{//check the door's auto-created trigger instead
1396 			owner = G_FindDoorTrigger( ent );
1397 			if ( owner && (owner->svFlags&SVF_INACTIVE) )
1398 			{//owning auto-created trigger is inactive
1399 				return qfalse;
1400 			}
1401 		}
1402 		if ( !(ent->svFlags & SVF_INACTIVE) && //assumes that the reactivate trigger isn't right next to the door!
1403 			!ent->health &&
1404 			!(ent->spawnflags & MOVER_PLAYER_USE) &&
1405 			!(ent->spawnflags & MOVER_FORCE_ACTIVATE) &&
1406 			!(ent->spawnflags & MOVER_LOCKED))
1407 			//FIXME: what about MOVER_GOODIE?
1408 		{
1409 			return qtrue;
1410 		}
1411 	}
1412 	return qfalse;
1413 }
1414 
1415 
1416 /*QUAKED func_door (0 .5 .8) ? START_OPEN FORCE_ACTIVATE CRUSHER TOGGLE LOCKED GOODIE PLAYER_USE INACTIVE
1417 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).
1418 FORCE_ACTIVATE	Can only be activated by a force push or pull
1419 CRUSHER		?
1420 TOGGLE		wait in both the start and end states for a trigger event - does NOT work on Trek doors.
1421 LOCKED		Starts locked, with the shader animmap at the first frame and inactive.  Once used, the animmap changes to the second frame and the door operates normally.  Note that you cannot use the door again after this.
1422 GOODIE		Door will not work unless activator has a "goodie key" in his inventory
1423 PLAYER_USE	Player can use it with the use button
1424 INACTIVE	must be used by a target_activate before it can be used
1425 
1426 "target"     Door fires this when it starts moving from it's closed position to its open position.
1427 "opentarget" Door fires this after reaching its "open" position
1428 "target2"    Door fires this when it starts moving from it's open position to its closed position.
1429 "closetarget" Door fires this after reaching its "closed" position
1430 "model2"	.md3 model to also draw
1431 "modelAngles" md3 model's angles <pitch yaw roll> (in addition to any rotation on the part of the brush entity itself)
1432 "angle"		determines the opening direction
1433 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1434 "speed"		movement speed (100 default)
1435 "wait"		wait before returning (3 default, -1 = never return)
1436 "delay"		when used, how many seconds to wait before moving - default is none
1437 "lip"		lip remaining at end of move (8 default)
1438 "dmg"		damage to inflict when blocked (2 default, set to negative for no damage)
1439 "color"		constantLight color
1440 "light"		constantLight radius
1441 "health"	if set, the door must be shot open
1442 "sounds" - sound door makes when opening/closing
1443 "linear"	set to 1 and it will move linearly rather than with acceleration (default is 0)
1444 0 - no sound (default)
1445 */
SP_func_door(gentity_t * ent)1446 void SP_func_door (gentity_t *ent)
1447 {
1448 	vec3_t	abs_movedir;
1449 	float	distance;
1450 	vec3_t	size;
1451 	float	lip;
1452 
1453 	ent->e_BlockedFunc = blockedF_Blocked_Door;
1454 
1455 	if ( ent->spawnflags & MOVER_GOODIE )
1456 	{
1457 		G_SoundIndex( "sound/movers/goodie_fail.wav" );
1458 		G_SoundIndex( "sound/movers/goodie_pass.wav" );
1459 	}
1460 
1461 	// default speed of 400
1462 	if (!ent->speed)
1463 		ent->speed = 400;
1464 
1465 	// default wait of 2 seconds
1466 	if (!ent->wait)
1467 		ent->wait = 2;
1468 	ent->wait *= 1000;
1469 
1470 	ent->delay *= 1000;
1471 
1472 	// default lip of 8 units
1473 	G_SpawnFloat( "lip", "8", &lip );
1474 
1475 	// default damage of 2 points
1476 	G_SpawnInt( "dmg", "2", &ent->damage );
1477 	if ( ent->damage < 0 )
1478 	{
1479 		ent->damage = 0;
1480 	}
1481 
1482 	// first position at start
1483 	VectorCopy( ent->s.origin, ent->pos1 );
1484 
1485 	// calculate second position
1486 	gi.SetBrushModel( ent, ent->model );
1487 	G_SetMovedir( ent->s.angles, ent->movedir );
1488 	abs_movedir[0] = fabs( ent->movedir[0] );
1489 	abs_movedir[1] = fabs( ent->movedir[1] );
1490 	abs_movedir[2] = fabs( ent->movedir[2] );
1491 	VectorSubtract( ent->maxs, ent->mins, size );
1492 	distance = DotProduct( abs_movedir, size ) - lip;
1493 	VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 );
1494 
1495 	// if "start_open", reverse position 1 and 2
1496 	if ( ent->spawnflags & 1 )
1497 	{
1498 		vec3_t	temp;
1499 
1500 		VectorCopy( ent->pos2, temp );
1501 		VectorCopy( ent->s.origin, ent->pos2 );
1502 		VectorCopy( temp, ent->pos1 );
1503 	}
1504 
1505 	if ( ent->spawnflags & MOVER_LOCKED )
1506 	{//a locked door, set up as locked until used directly
1507 		ent->s.eFlags |= EF_SHADER_ANIM;//use frame-controlled shader anim
1508 		ent->s.frame = 0;//first stage of anim
1509 	}
1510 	InitMover( ent );
1511 
1512 	ent->nextthink = level.time + FRAMETIME;
1513 
1514 	if ( !(ent->flags&FL_TEAMSLAVE) )
1515 	{
1516 		int health;
1517 
1518 		G_SpawnInt( "health", "0", &health );
1519 
1520 		if ( health )
1521 		{
1522 			ent->takedamage = qtrue;
1523 		}
1524 
1525 		if ( !(ent->spawnflags&MOVER_LOCKED) && (ent->targetname || health || ent->spawnflags & MOVER_PLAYER_USE || ent->spawnflags & MOVER_FORCE_ACTIVATE) )
1526 		{
1527 			// non touch/shoot doors
1528 			ent->e_ThinkFunc = thinkF_Think_MatchTeam;
1529 		}
1530 		else
1531 		{//locked doors still spawn a trigger
1532 			ent->e_ThinkFunc = thinkF_Think_SpawnNewDoorTrigger;
1533 		}
1534 	}
1535 }
1536 
1537 /*
1538 ===============================================================================
1539 
1540 PLAT
1541 
1542 ===============================================================================
1543 */
1544 
1545 /*
1546 ==============
1547 Touch_Plat
1548 
1549 Don't allow decent if a living player is on it
1550 ===============
1551 */
Touch_Plat(gentity_t * ent,gentity_t * other,trace_t * trace)1552 void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) {
1553 	if ( !other->client || other->client->ps.stats[STAT_HEALTH] <= 0 ) {
1554 		return;
1555 	}
1556 
1557 	// delay return-to-pos1 by one second
1558 	if ( ent->moverState == MOVER_POS2 ) {
1559 		ent->nextthink = level.time + 1000;
1560 	}
1561 }
1562 
1563 /*
1564 ==============
1565 Touch_PlatCenterTrigger
1566 
1567 If the plat is at the bottom position, start it going up
1568 ===============
1569 */
Touch_PlatCenterTrigger(gentity_t * ent,gentity_t * other,trace_t * trace)1570 void Touch_PlatCenterTrigger(gentity_t *ent, gentity_t *other, trace_t *trace ) {
1571 	if ( !other->client ) {
1572 		return;
1573 	}
1574 
1575 	if ( ent->owner->moverState == MOVER_POS1 ) {
1576 		Use_BinaryMover( ent->owner, ent, other );
1577 	}
1578 }
1579 
1580 
1581 /*
1582 ================
1583 SpawnPlatTrigger
1584 
1585 Spawn a trigger in the middle of the plat's low position
1586 Elevator cars require that the trigger extend through the entire low position,
1587 not just sit on top of it.
1588 ================
1589 */
SpawnPlatTrigger(gentity_t * ent)1590 void SpawnPlatTrigger( gentity_t *ent ) {
1591 	gentity_t	*trigger;
1592 	vec3_t	tmin, tmax;
1593 
1594 	// the middle trigger will be a thin trigger just
1595 	// above the starting position
1596 	trigger = G_Spawn();
1597 	trigger->e_TouchFunc = touchF_Touch_PlatCenterTrigger;
1598 	trigger->contents = CONTENTS_TRIGGER;
1599 	trigger->owner = ent;
1600 
1601 	tmin[0] = ent->pos1[0] + ent->mins[0] + 33;
1602 	tmin[1] = ent->pos1[1] + ent->mins[1] + 33;
1603 	tmin[2] = ent->pos1[2] + ent->mins[2];
1604 
1605 	tmax[0] = ent->pos1[0] + ent->maxs[0] - 33;
1606 	tmax[1] = ent->pos1[1] + ent->maxs[1] - 33;
1607 	tmax[2] = ent->pos1[2] + ent->maxs[2] + 8;
1608 
1609 	if ( tmax[0] <= tmin[0] ) {
1610 		tmin[0] = ent->pos1[0] + (ent->mins[0] + ent->maxs[0]) *0.5;
1611 		tmax[0] = tmin[0] + 1;
1612 	}
1613 	if ( tmax[1] <= tmin[1] ) {
1614 		tmin[1] = ent->pos1[1] + (ent->mins[1] + ent->maxs[1]) *0.5;
1615 		tmax[1] = tmin[1] + 1;
1616 	}
1617 
1618 	VectorCopy (tmin, trigger->mins);
1619 	VectorCopy (tmax, trigger->maxs);
1620 
1621 	gi.linkentity (trigger);
1622 }
1623 
1624 
1625 /*QUAKED func_plat (0 .5 .8) ? x x x x x x PLAYER_USE INACTIVE
1626 PLAYER_USE	Player can use it with the use button
1627 INACTIVE	must be used by a target_activate before it can be used
1628 
1629 Plats are always drawn in the extended position so they will light correctly.
1630 
1631 "lip"		default 8, protrusion above rest position
1632 "height"	total height of movement, defaults to model height
1633 "speed"		overrides default 200.
1634 "dmg"		overrides default 2
1635 "model2"	.md3 model to also draw
1636 "modelAngles" md3 model's angles <pitch yaw roll> (in addition to any rotation on the part of the brush entity itself)
1637 "color"		constantLight color
1638 "light"		constantLight radius
1639 */
SP_func_plat(gentity_t * ent)1640 void SP_func_plat (gentity_t *ent) {
1641 	float		lip, height;
1642 
1643 //	ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/plats/pt1_strt.wav");
1644 //	ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/plats/pt1_end.wav");
1645 
1646 	VectorClear (ent->s.angles);
1647 
1648 	G_SpawnFloat( "speed", "200", &ent->speed );
1649 	G_SpawnInt( "dmg", "2", &ent->damage );
1650 	G_SpawnFloat( "wait", "1", &ent->wait );
1651 	G_SpawnFloat( "lip", "8", &lip );
1652 
1653 	ent->wait = 1000;
1654 
1655 	// create second position
1656 	gi.SetBrushModel( ent, ent->model );
1657 
1658 	if ( !G_SpawnFloat( "height", "0", &height ) ) {
1659 		height = (ent->maxs[2] - ent->mins[2]) - lip;
1660 	}
1661 
1662 	// pos1 is the rest (bottom) position, pos2 is the top
1663 	VectorCopy( ent->s.origin, ent->pos2 );
1664 	VectorCopy( ent->pos2, ent->pos1 );
1665 	ent->pos1[2] -= height;
1666 
1667 	InitMover( ent );
1668 
1669 	// touch function keeps the plat from returning while
1670 	// a live player is standing on it
1671 	ent->e_TouchFunc = touchF_Touch_Plat;
1672 
1673 	ent->e_BlockedFunc = blockedF_Blocked_Door;
1674 
1675 	ent->owner = ent;	// so it can be treated as a door
1676 
1677 	// spawn the trigger if one hasn't been custom made
1678 	if ( !ent->targetname ) {
1679 		SpawnPlatTrigger(ent);
1680 	}
1681 }
1682 
1683 
1684 /*
1685 ===============================================================================
1686 
1687 BUTTON
1688 
1689 ===============================================================================
1690 */
1691 
1692 /*
1693 ==============
1694 Touch_Button
1695 
1696 ===============
1697 */
Touch_Button(gentity_t * ent,gentity_t * other,trace_t * trace)1698 void Touch_Button(gentity_t *ent, gentity_t *other, trace_t *trace ) {
1699 	if ( !other->client ) {
1700 		return;
1701 	}
1702 
1703 	if ( ent->moverState == MOVER_POS1 ) {
1704 		Use_BinaryMover( ent, other, other );
1705 	}
1706 }
1707 
1708 
1709 /*QUAKED func_button (0 .5 .8) ? x x x x x x PLAYER_USE INACTIVE
1710 PLAYER_USE	Player can use it with the use button
1711 INACTIVE	must be used by a target_activate before it can be used
1712 
1713 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.
1714 
1715 "model2"	.md3 model to also draw
1716 "modelAngles" md3 model's angles <pitch yaw roll> (in addition to any rotation on the part of the brush entity itself)
1717 "angle"		determines the opening direction
1718 "target"	all entities with a matching targetname will be used
1719 "speed"		override the default 40 speed
1720 "wait"		override the default 1 second wait (-1 = never return)
1721 "lip"		override the default 4 pixel lip remaining at end of move
1722 "health"	if set, the button must be killed instead of touched
1723 "color"		constantLight color
1724 "light"		constantLight radius
1725 */
SP_func_button(gentity_t * ent)1726 void SP_func_button( gentity_t *ent ) {
1727 	vec3_t		abs_movedir;
1728 	float		distance;
1729 	vec3_t		size;
1730 	float		lip;
1731 
1732 //	ent->sound1to2 = G_SoundIndex("sound/movers/switches/butn2.wav");
1733 
1734 	if ( !ent->speed ) {
1735 		ent->speed = 40;
1736 	}
1737 
1738 	if ( !ent->wait ) {
1739 		ent->wait = 1;
1740 	}
1741 	ent->wait *= 1000;
1742 
1743 	// first position
1744 	VectorCopy( ent->s.origin, ent->pos1 );
1745 
1746 	// calculate second position
1747 	gi.SetBrushModel( ent, ent->model );
1748 
1749 	G_SpawnFloat( "lip", "4", &lip );
1750 
1751 	G_SetMovedir( ent->s.angles, ent->movedir );
1752 	abs_movedir[0] = fabs(ent->movedir[0]);
1753 	abs_movedir[1] = fabs(ent->movedir[1]);
1754 	abs_movedir[2] = fabs(ent->movedir[2]);
1755 	VectorSubtract( ent->maxs, ent->mins, size );
1756 	distance = abs_movedir[0] * size[0] + abs_movedir[1] * size[1] + abs_movedir[2] * size[2] - lip;
1757 	VectorMA (ent->pos1, distance, ent->movedir, ent->pos2);
1758 
1759 	if (ent->health) {
1760 		// shootable button
1761 		ent->takedamage = qtrue;
1762 	} else {
1763 		// touchable button
1764 		ent->e_TouchFunc = touchF_Touch_Button;
1765 	}
1766 
1767 	InitMover( ent );
1768 }
1769 
1770 
1771 
1772 /*
1773 ===============================================================================
1774 
1775 TRAIN
1776 
1777 ===============================================================================
1778 */
1779 
1780 
1781 #define TRAIN_START_ON		1
1782 #define TRAIN_TOGGLE		2
1783 #define TRAIN_BLOCK_STOPS	4
1784 
1785 /*
1786 ===============
1787 Think_BeginMoving
1788 
1789 The wait time at a corner has completed, so start moving again
1790 ===============
1791 */
Think_BeginMoving(gentity_t * ent)1792 void Think_BeginMoving( gentity_t *ent ) {
1793 
1794 	if ( ent->spawnflags & 2048 )
1795 	{
1796 		// this tie fighter hack is done for doom_detention, where the shooting gallery takes place. let them draw again when they start moving
1797 		ent->s.eFlags &= ~EF_NODRAW;
1798 	}
1799 
1800 	ent->s.pos.trTime = level.time;
1801 	if ( ent->alt_fire )
1802 	{
1803 		ent->s.pos.trType = TR_LINEAR_STOP;
1804 	}
1805 	else
1806 	{
1807 		ent->s.pos.trType = TR_NONLINEAR_STOP;
1808 	}
1809 }
1810 
1811 /*
1812 ===============
1813 Reached_Train
1814 ===============
1815 */
Reached_Train(gentity_t * ent)1816 void Reached_Train( gentity_t *ent ) {
1817 	gentity_t		*next;
1818 	float			speed;
1819 	vec3_t			move;
1820 	float			length;
1821 
1822 	// copy the apropriate values
1823 	next = ent->nextTrain;
1824 	if ( !next || !next->nextTrain ) {
1825 		//FIXME: can we go backwards- say if we are toggle-able?
1826 		return;		// just stop
1827 	}
1828 
1829 	// fire all other targets
1830 	G_UseTargets( next, ent );
1831 
1832 	// set the new trajectory
1833 	ent->nextTrain = next->nextTrain;
1834 	VectorCopy( next->s.origin, ent->pos1 );
1835 	VectorCopy( next->nextTrain->s.origin, ent->pos2 );
1836 
1837 	// if the path_corner has a speed, use that
1838 	if ( next->speed ) {
1839 		speed = next->speed;
1840 	} else {
1841 		// otherwise use the train's speed
1842 		speed = ent->speed;
1843 	}
1844 	if ( speed < 1 ) {
1845 		speed = 1;
1846 	}
1847 
1848 	// calculate duration
1849 	VectorSubtract( ent->pos2, ent->pos1, move );
1850 	length = VectorLength( move );
1851 
1852 	ent->s.pos.trDuration = length * 1000 / speed;
1853 
1854 	// looping sound
1855 /*
1856 	if ( VALIDSTRING( next->soundSet ) )
1857 	{
1858 		ent->s.loopSound = CAS_GetBModelSound( next->soundSet, BMS_MID );//ent->soundLoop;
1859 	}
1860 */
1861 	G_PlayDoorLoopSound( ent );
1862 
1863 	// start it going
1864 	SetMoverState( ent, MOVER_1TO2, level.time );
1865 
1866 	if (( next->spawnflags & 1 ))
1867 	{
1868 		vec3_t angs;
1869 
1870 		vectoangles( move, angs );
1871 		AnglesSubtract( angs, ent->currentAngles, angs );
1872 
1873 		for ( int i = 0; i < 3; i++ )
1874 		{
1875 			AngleNormalize360( angs[i] );
1876 		}
1877 		VectorCopy( ent->currentAngles, ent->s.apos.trBase );
1878 		VectorScale( angs, 0.5f, ent->s.apos.trDelta );
1879 		ent->s.apos.trTime = level.time;
1880 		ent->s.apos.trDuration = 2000;
1881 		if ( ent->alt_fire )
1882 		{
1883 			ent->s.apos.trType = TR_LINEAR_STOP;
1884 		}
1885 		else
1886 		{
1887 			ent->s.apos.trType = TR_NONLINEAR_STOP;
1888 		}
1889 	}
1890 	else
1891 	{
1892 		if (( next->spawnflags & 4 ))
1893 		{//yaw
1894 			vec3_t angs;
1895 
1896 			vectoangles( move, angs );
1897 			AnglesSubtract( angs, ent->currentAngles, angs );
1898 
1899 			for ( int i = 0; i < 3; i++ )
1900 			{
1901 				AngleNormalize360( angs[i] );
1902 			}
1903 			VectorCopy( ent->currentAngles, ent->s.apos.trBase );
1904 			ent->s.apos.trDelta[YAW] = angs[YAW] * 0.5f;
1905 			if (( next->spawnflags & 8 ))
1906 			{//roll, too
1907 				ent->s.apos.trDelta[ROLL] = angs[YAW] * -0.1f;
1908 			}
1909 			ent->s.apos.trTime = level.time;
1910 			ent->s.apos.trDuration = 2000;
1911 			if ( ent->alt_fire )
1912 			{
1913 				ent->s.apos.trType = TR_LINEAR_STOP;
1914 			}
1915 			else
1916 			{
1917 				ent->s.apos.trType = TR_NONLINEAR_STOP;
1918 			}
1919 		}
1920 	}
1921 
1922 	// This is for the tie fighter shooting gallery on doom detention, you could see them waiting under the bay, but the architecture couldn't easily be changed..
1923 	if (( next->spawnflags & 2 ))
1924 	{
1925 		ent->s.eFlags |= EF_NODRAW; // make us invisible until we start moving again
1926 	}
1927 
1928 	// if there is a "wait" value on the target, don't start moving yet
1929 	if ( next->wait )
1930 	{
1931 		ent->nextthink = level.time + next->wait * 1000;
1932 		ent->e_ThinkFunc = thinkF_Think_BeginMoving;
1933 		ent->s.pos.trType = TR_STATIONARY;
1934 	}
1935 	else if (!( next->spawnflags & 2 ))
1936 	{
1937 		// we aren't waiting to start, so let us draw right away
1938 		ent->s.eFlags &= ~EF_NODRAW;
1939 	}
1940 }
1941 
TrainUse(gentity_t * ent,gentity_t * other,gentity_t * activator)1942 void TrainUse( gentity_t *ent, gentity_t *other, gentity_t *activator )
1943 {
1944 	Reached_Train( ent );
1945 }
1946 
1947 /*
1948 ===============
1949 Think_SetupTrainTargets
1950 
1951 Link all the corners together
1952 ===============
1953 */
Think_SetupTrainTargets(gentity_t * ent)1954 void Think_SetupTrainTargets( gentity_t *ent ) {
1955 	gentity_t		*path, *next, *start;
1956 
1957 	ent->nextTrain = G_Find( NULL, FOFS(targetname), ent->target );
1958 	if ( !ent->nextTrain ) {
1959 		gi.Printf( "func_train at %s with an unfound target\n", vtos(ent->absmin) );
1960 		//Free me?`
1961 		return;
1962 	}
1963 
1964 	//FIXME: this can go into an infinite loop if last path_corner doesn't link to first
1965 	//path_corner, like so:
1966 	// t1---->t2---->t3
1967 	//         ^      |
1968 	//          \_____|
1969 	start = NULL;
1970 	next = NULL;
1971 	int iterations = 2000;	//max attempts to find our path start
1972 	for ( path = ent->nextTrain ; path != start ; path = next ) {
1973 		if (!iterations--)
1974 		{
1975 			G_Error( "Think_SetupTrainTargets:  last path_corner doesn't link back to first on func_train(%s)", vtos(ent->absmin) );
1976 		}
1977 		if (!start)
1978 		{
1979 			start = path;
1980 		}
1981 		if ( !path->target ) {
1982 //			gi.Printf( "Train corner at %s without a target\n", vtos(path->s.origin) );
1983 			//end of path
1984 			break;
1985 		}
1986 
1987 		// find a path_corner among the targets
1988 		// there may also be other targets that get fired when the corner
1989 		// is reached
1990 		next = NULL;
1991 		do {
1992 			next = G_Find( next, FOFS(targetname), path->target );
1993 			if ( !next ) {
1994 //				gi.Printf( "Train corner at %s without a target path_corner\n", vtos(path->s.origin) );
1995 				//end of path
1996 				break;
1997 			}
1998 		} while ( strcmp( next->classname, "path_corner" ) );
1999 
2000 		if ( next )
2001 		{
2002 			path->nextTrain = next;
2003 		}
2004 		else
2005 		{
2006 			break;
2007 		}
2008 	}
2009 
2010 	if ( !ent->targetname || (ent->spawnflags&1) /*start on*/)
2011 	{
2012 		// start the train moving from the first corner
2013 		Reached_Train( ent );
2014 	}
2015 	else
2016 	{
2017 		G_SetOrigin( ent, ent->s.origin );
2018 	}
2019 }
2020 
2021 
2022 
2023 /*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TURN_TRAIN INVISIBLE YAW_TRAIN ROLL_TRAIN
2024 
2025 TURN_TRAIN	func_train moving on this path will turn to face the next path_corner within 2 seconds
2026 INVISIBLE - train will become invisible ( but still solid ) when it reaches this path_corner.
2027 		It will become visible again at the next path_corner that does not have this option checked
2028 
2029 Train path corners.
2030 Target: next path corner and other targets to fire
2031 "speed" speed to move to the next corner
2032 "wait" seconds to wait before behining move to next corner
2033 */
SP_path_corner(gentity_t * self)2034 void SP_path_corner( gentity_t *self ) {
2035 	if ( !self->targetname ) {
2036 		gi.Printf ("path_corner with no targetname at %s\n", vtos(self->s.origin));
2037 		G_FreeEntity( self );
2038 		return;
2039 	}
2040 	// path corners don't need to be linked in
2041 	VectorCopy(self->s.origin, self->currentOrigin);
2042 }
2043 
2044 
func_train_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)2045 void func_train_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc )
2046 {
2047 	if ( self->target3 )
2048 	{
2049 		G_UseTargets2( self, self, self->target3 );
2050 	}
2051 
2052 	//HACKHACKHACKHACK
2053 	G_PlayEffect( "explosions/fighter_explosion2", self->currentOrigin );
2054 	G_FreeEntity( self );
2055 	//HACKHACKHACKHACK
2056 }
2057 
2058 /*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS x x LOOP PLAYER_USE INACTIVE TIE
2059 
2060 LOOP - if it's a ghoul2 model, it will just loop the animation
2061 PLAYER_USE	Player can use it with the use button
2062 INACTIVE	must be used by a target_activate before it can be used
2063 TIE  flying Tie-fighter hack, should be made more flexible so other things can use this if needed
2064 
2065 A train is a mover that moves between path_corner target points.
2066 Trains MUST HAVE AN ORIGIN BRUSH.
2067 The train spawns at the first target it is pointing at.
2068 "model2"	.md3 model to also draw
2069 "modelAngles" md3 model's angles <pitch yaw roll> (in addition to any rotation on the part of the brush entity itself)
2070 "speed"		default 100
2071 "dmg"		default	2
2072 "health"	default 0
2073 "noise"		looping sound to play when the train is in motion
2074 "targetname" will not start until used
2075 "target"	next path corner
2076 "target3" what to use when it breaks
2077 "color"		constantLight color
2078 "light"		constantLight radius
2079 "linear" set to 1 and it will move linearly rather than with acceleration (default is 0)
2080 "startframe" default 0...ghoul2 animation start frame
2081 "endframe" default 0...ghoul2 animation end frame
2082 */
SP_func_train(gentity_t * self)2083 void SP_func_train (gentity_t *self) {
2084 	VectorClear (self->s.angles);
2085 
2086 	if (self->spawnflags & TRAIN_BLOCK_STOPS) {
2087 		self->damage = 0;
2088 	} else {
2089 		if (!self->damage) {
2090 			self->damage = 2;
2091 		}
2092 	}
2093 
2094 	if ( !self->speed ) {
2095 		self->speed = 100;
2096 	}
2097 
2098 	if ( !self->target ) {
2099 		gi.Printf ("func_train without a target at %s\n", vtos(self->absmin));
2100 		G_FreeEntity( self );
2101 		return;
2102 	}
2103 
2104 	char *noise;
2105 
2106 	G_SpawnInt( "startframe", "0", &self->startFrame );
2107 	G_SpawnInt( "endframe", "0", &self->endFrame );
2108 
2109 	if ( G_SpawnString( "noise", "", &noise ) )
2110 	{
2111 		if ( VALIDSTRING( noise ) )
2112 		{
2113 			self->s.loopSound = cgi_S_RegisterSound( noise );//G_SoundIndex( noise );
2114 		}
2115 	}
2116 
2117 	gi.SetBrushModel( self, self->model );
2118 	InitMover( self );
2119 
2120 	if ( self->spawnflags & 2048 ) // TIE HACK
2121 	{
2122 		//HACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACK
2123 		self->s.modelindex2 = G_ModelIndex( "models/map_objects/ships/tie_fighter.md3" );
2124 		G_EffectIndex( "explosions/fighter_explosion2" );
2125 
2126 		self->contents = CONTENTS_SHOTCLIP;
2127 		self->takedamage = qtrue;
2128 		VectorSet( self->maxs, 112, 112, 112 );
2129 		VectorSet( self->mins, -112, -112, -112 );
2130 		self->e_DieFunc  = dieF_func_train_die;
2131 		gi.linkentity( self );
2132 		//HACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACK
2133 	}
2134 
2135 	if ( self->targetname )
2136 	{
2137 		self->e_UseFunc = useF_TrainUse;
2138 	}
2139 
2140 	self->e_ReachedFunc = reachedF_Reached_Train;
2141 
2142 	// start trains on the second frame, to make sure their targets have had
2143 	// a chance to spawn
2144 	self->nextthink = level.time + START_TIME_LINK_ENTS;
2145 	self->e_ThinkFunc = thinkF_Think_SetupTrainTargets;
2146 
2147 
2148 	if ( self->playerModel >= 0 && self->spawnflags & 32 ) // loop...dunno, could support it on other things, but for now I need it for the glider...so...kill the flag
2149 	{
2150 		self->spawnflags &= ~32; // once only
2151 
2152 		gi.G2API_SetBoneAnim( &self->ghoul2[self->playerModel], "model_root", self->startFrame, self->endFrame, BONE_ANIM_OVERRIDE_LOOP, 1.0f + Q_flrand(-1.0f, 1.0f) * 0.1f, 0, -1, -1 );
2153 		self->endFrame = 0; // don't allow it to do anything with the animation function in G_main
2154 	}
2155 }
2156 
2157 /*
2158 ===============================================================================
2159 
2160 STATIC
2161 
2162 ===============================================================================
2163 */
2164 
2165 /*QUAKED func_static (0 .5 .8) ? F_PUSH F_PULL SWITCH_SHADER CRUSHER IMPACT SOLITARY PLAYER_USE INACTIVE BROADCAST
2166 F_PUSH		Will be used when you Force-Push it
2167 F_PULL		Will be used when you Force-Pull it
2168 SWITCH_SHADER	Toggle the shader anim frame between 1 and 2 when used
2169 CRUSHER		Make it do damage when it's blocked
2170 IMPACT		Make it do damage when it hits any entity
2171 SOLITARY	Can only be pushed when directly under crosshair, when pushed you shall push nothing else BUT me.
2172 PLAYER_USE	Player can use it with the use button
2173 INACTIVE	must be used by a target_activate before it can be used
2174 BROADCAST   don't ever use this, it's evil
2175 
2176 A bmodel that just sits there, doing nothing.  Can be used for conditional walls and models.
2177 "model2"	.md3 model to also draw
2178 "modelAngles" md3 model's angles <pitch yaw roll> (in addition to any rotation on the part of the brush entity itself)
2179 "color"		constantLight color
2180 "light"		constantLight radius
2181 "dmg"		how much damage to do when it crushes (use with CRUSHER spawnflag default is 2)
2182 "linear" set to 1 and it will move linearly rather than with acceleration (default is 0)
2183 "NPC_targetname" if set up to be push/pullable, only this NPC can push/pull it (for the player, use "player")
2184 */
SP_func_static(gentity_t * ent)2185 void SP_func_static( gentity_t *ent )
2186 {
2187 	gi.SetBrushModel( ent, ent->model );
2188 
2189 	VectorCopy( ent->s.origin, ent->pos1 );
2190 	VectorCopy( ent->s.origin, ent->pos2 );
2191 
2192 	InitMover( ent );
2193 
2194 	G_SetOrigin( ent, ent->s.origin );
2195 	G_SetAngles( ent, ent->s.angles );
2196 
2197 	ent->e_UseFunc = useF_func_static_use;
2198 	ent->e_ReachedFunc = reachedF_NULL;
2199 
2200 
2201 	if( ent->spawnflags & 2048 )
2202 	{								   // yes this is very very evil, but for now (pre-alpha) it's a solution
2203 		ent->svFlags |= SVF_BROADCAST; // I need to rotate something that is huge and it's touching too many area portals...
2204 	}
2205 
2206 	if ( ent->spawnflags & 4/*SWITCH_SHADER*/ )
2207 	{
2208 		ent->s.eFlags |= EF_SHADER_ANIM;//use frame-controlled shader anim
2209 		ent->s.frame = 0;//first stage of anim
2210 		ent->spawnflags &= ~4;//this is the CRUSHER spawnflag!  remove it!
2211 	}
2212 	if ( ent->spawnflags & 8 )
2213 	{//!!! 8 is NOT the crusher spawnflag, 4 is!!!
2214 		ent->spawnflags &= ~8;
2215 		ent->spawnflags |= MOVER_CRUSHER;
2216 		if ( !ent->damage )
2217 		{
2218 			ent->damage = 2;
2219 		}
2220 	}
2221 	gi.linkentity( ent );
2222 
2223 	if (level.mBSPInstanceDepth)
2224 	{	// this means that this guy will never be updated, moved, changed, etc.
2225 		ent->s.eFlags = EF_PERMANENT;
2226 	}
2227 }
2228 
func_static_use(gentity_t * self,gentity_t * other,gentity_t * activator)2229 void func_static_use ( gentity_t *self, gentity_t *other, gentity_t *activator )
2230 {
2231 	G_ActivateBehavior( self, BSET_USE );
2232 
2233 	if ( self->spawnflags & 4/*SWITCH_SHADER*/ )
2234 	{
2235 		self->s.frame = self->s.frame ? 0 : 1;//toggle frame
2236 	}
2237 	G_UseTargets( self, activator );
2238 }
2239 
2240 /*
2241 ===============================================================================
2242 
2243 ROTATING
2244 
2245 ===============================================================================
2246 */
func_rotating_touch(gentity_t * self,gentity_t * other,trace_t * trace)2247 void func_rotating_touch( gentity_t *self, gentity_t *other, trace_t *trace )
2248 {
2249 //	vec3_t spot;
2250 //	gentity_t	*tent;
2251 	if( !other->client ) // don't want to disintegrate items or weapons, etc...
2252 	{
2253 		return;
2254 	}
2255 	// yeah, this is a bit hacky...
2256 	if(	self->s.apos.trType != TR_STATIONARY && !(other->flags & FL_DISINTEGRATED) ) // only damage if it's moving
2257 	{
2258 //		VectorCopy( self->currentOrigin, spot );
2259 //		tent = G_TempEntity( spot, EV_DISINTEGRATION );
2260 //		tent->s.eventParm = PW_DISRUPTION;
2261 //		tent->owner = self;
2262 		// let G_Damage call the fx instead, beside, this way you can disintegrate a corpse this way
2263 		G_Sound( other, G_SoundIndex( "sound/effects/energy_crackle.wav" ) );
2264 		G_Damage( other, self, self, NULL, NULL, 10000, DAMAGE_NO_KNOCKBACK, MOD_SNIPER );
2265 	}
2266 }
2267 
2268 
func_rotating_use(gentity_t * self,gentity_t * other,gentity_t * activator)2269 void func_rotating_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2270 {
2271 	if(	self->s.apos.trType == TR_LINEAR )
2272 	{
2273 		self->s.apos.trType = TR_STATIONARY;
2274 		// stop the sound if it stops moving
2275 		self->s.loopSound = 0;
2276 		// play stop sound too?
2277 		if ( VALIDSTRING( self->soundSet ) == true )
2278 		{
2279 			G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_END ));
2280 		}
2281 	}
2282 	else
2283 	{
2284 		if ( VALIDSTRING( self->soundSet ) == true )
2285 		{
2286 			G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_START ));
2287 			self->s.loopSound = CAS_GetBModelSound( self->soundSet, BMS_MID );
2288 			if ( self->s.loopSound < 0 )
2289 			{
2290 				self->s.loopSound = 0;
2291 			}
2292 		}
2293 		self->s.apos.trType = TR_LINEAR;
2294 	}
2295 }
2296 
2297 
2298 /*QUAKED func_rotating (0 .5 .8) ? START_ON TOUCH_KILL X_AXIS Y_AXIS x x PLAYER_USE INACTIVE
2299 PLAYER_USE	Player can use it with the use button
2300 TOUCH_KILL  Inflicts massive damage upon touching it, disitegrates bodies
2301 INACTIVE	must be used by a target_activate before it can be used
2302 
2303 You need to have an origin brush as part of this entity.  The center of that brush will be
2304 the point around which it is rotated. It will rotate around the Z axis by default.  You can
2305 check either the X_AXIS or Y_AXIS box to change that.
2306 
2307 "model2"	.md3 model to also draw
2308 "modelAngles" md3 model's angles <pitch yaw roll> (in addition to any rotation on the part of the brush entity itself)
2309 "speed"		determines how fast it moves; default value is 100.
2310 "dmg"		damage to inflict when blocked (2 default)
2311 "color"		constantLight color
2312 "light"		constantLight radius
2313 */
SP_func_rotating(gentity_t * ent)2314 void SP_func_rotating (gentity_t *ent) {
2315 	if ( !ent->speed ) {
2316 		ent->speed = 100;
2317 	}
2318 
2319 
2320 	ent->s.apos.trType = TR_STATIONARY;
2321 	if( ent->spawnflags & 1 )	// start on
2322 	{
2323 		ent->s.apos.trType = TR_LINEAR;
2324 	}
2325 	// set the axis of rotation
2326 	if ( ent->spawnflags & 4 ) {
2327 		ent->s.apos.trDelta[2] = ent->speed;
2328 	} else if ( ent->spawnflags & 8 ) {
2329 		ent->s.apos.trDelta[0] = ent->speed;
2330 	} else {
2331 		ent->s.apos.trDelta[1] = ent->speed;
2332 	}
2333 
2334 	if (!ent->damage) {
2335 		ent->damage = 2;
2336 	}
2337 
2338 
2339 	gi.SetBrushModel( ent, ent->model );
2340 	InitMover( ent );
2341 
2342 	if ( ent->targetname )
2343 	{
2344 		ent->e_UseFunc = useF_func_rotating_use;
2345 	}
2346 
2347 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
2348 	VectorCopy( ent->s.pos.trBase, ent->currentOrigin );
2349 	VectorCopy( ent->s.apos.trBase, ent->currentAngles );
2350 	if( ent->spawnflags & 2 )
2351 	{
2352 		ent->e_TouchFunc = touchF_func_rotating_touch;
2353 		G_SoundIndex( "sound/effects/energy_crackle.wav" );
2354 	}
2355 
2356 	gi.linkentity( ent );
2357 }
2358 
2359 
2360 /*
2361 ===============================================================================
2362 
2363 BOBBING
2364 
2365 ===============================================================================
2366 */
2367 
func_bobbing_use(gentity_t * self,gentity_t * other,gentity_t * activator)2368 void func_bobbing_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2369 {
2370 	// Toggle our move state
2371 	if ( self->s.pos.trType == TR_SINE )
2372 	{
2373 		self->s.pos.trType = TR_INTERPOLATE;
2374 
2375 		// Save off roughly where we were
2376 		VectorCopy( self->currentOrigin, self->s.pos.trBase );
2377 		// Store the current phase value so we know how to start up where we left off.
2378 		self->radius = ( level.time - self->s.pos.trTime ) / (float)self->s.pos.trDuration;
2379 	}
2380 	else
2381 	{
2382 		self->s.pos.trType = TR_SINE;
2383 
2384 		// Set the time based on the saved phase value
2385 		self->s.pos.trTime = level.time - self->s.pos.trDuration * self->radius;
2386 		// Always make sure we are starting with a fresh base
2387 		VectorCopy( self->s.origin, self->s.pos.trBase );
2388 	}
2389 }
2390 
2391 /*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS START_OFF x x x PLAYER_USE INACTIVE
2392 PLAYER_USE	Player can use it with the use button
2393 INACTIVE	must be used by a target_activate before it can be used
2394 
2395 Normally bobs on the Z axis
2396 "model2"	.md3 model to also draw
2397 "modelAngles" md3 model's angles <pitch yaw roll> (in addition to any rotation on the part of the brush entity itself)
2398 "height"	amplitude of bob (32 default)
2399 "speed"		seconds to complete a bob cycle (4 default)
2400 "phase"		the 0.0 to 1.0 offset in the cycle to start at
2401 "dmg"		damage to inflict when blocked (2 default)
2402 "color"		constantLight color
2403 "light"		constantLight radius
2404 "targetname" turns on/off when used
2405 */
SP_func_bobbing(gentity_t * ent)2406 void SP_func_bobbing (gentity_t *ent) {
2407 	float		height;
2408 	float		phase;
2409 
2410 	G_SpawnFloat( "speed", "4", &ent->speed );
2411 	G_SpawnFloat( "height", "32", &height );
2412 	G_SpawnInt( "dmg", "2", &ent->damage );
2413 	G_SpawnFloat( "phase", "0", &phase );
2414 
2415 	gi.SetBrushModel( ent, ent->model );
2416 	InitMover( ent );
2417 
2418 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
2419 	VectorCopy( ent->s.origin, ent->currentOrigin );
2420 
2421 	// set the axis of bobbing
2422 	if ( ent->spawnflags & 1 ) {
2423 		ent->s.pos.trDelta[0] = height;
2424 	} else if ( ent->spawnflags & 2 ) {
2425 		ent->s.pos.trDelta[1] = height;
2426 	} else {
2427 		ent->s.pos.trDelta[2] = height;
2428 	}
2429 
2430 	ent->s.pos.trDuration = ent->speed * 1000;
2431 	ent->s.pos.trTime = ent->s.pos.trDuration * phase;
2432 
2433 	if ( ent->spawnflags & 4 ) // start_off
2434 	{
2435 		ent->s.pos.trType = TR_INTERPOLATE;
2436 
2437 		// Now use the phase to calculate where it should be at the start.
2438 		ent->radius = phase;
2439 		phase = (float)sin( phase * M_PI * 2 );
2440 		VectorMA( ent->s.pos.trBase, phase, ent->s.pos.trDelta, ent->s.pos.trBase );
2441 
2442 		if ( ent->targetname )
2443 		{
2444 			ent->e_UseFunc = useF_func_bobbing_use;
2445 		}
2446 	}
2447 	else
2448 	{
2449 		ent->s.pos.trType = TR_SINE;
2450 	}
2451 }
2452 
2453 /*
2454 ===============================================================================
2455 
2456 PENDULUM
2457 
2458 ===============================================================================
2459 */
2460 
2461 
2462 /*QUAKED func_pendulum (0 .5 .8) ? x x x x x x PLAYER_USE INACTIVE
2463 PLAYER_USE	Player can use it with the use button
2464 INACTIVE	must be used by a target_activate before it can be used
2465 
2466 You need to have an origin brush as part of this entity.
2467 Pendulums always swing north / south on unrotated models.  Add an angles field to the model to allow rotation in other directions.
2468 Pendulum frequency is a physical constant based on the length of the beam and gravity.
2469 "model2"	.md3 model to also draw
2470 "modelAngles" md3 model's angles <pitch yaw roll> (in addition to any rotation on the part of the brush entity itself)
2471 "speed"		the number of degrees each way the pendulum swings, (30 default)
2472 "phase"		the 0.0 to 1.0 offset in the cycle to start at
2473 "dmg"		damage to inflict when blocked (2 default)
2474 "color"		constantLight color
2475 "light"		constantLight radius
2476 */
SP_func_pendulum(gentity_t * ent)2477 void SP_func_pendulum(gentity_t *ent) {
2478 	float		freq;
2479 	float		length;
2480 	float		phase;
2481 	float		speed;
2482 
2483 	G_SpawnFloat( "speed", "30", &speed );
2484 	G_SpawnInt( "dmg", "2", &ent->damage );
2485 	G_SpawnFloat( "phase", "0", &phase );
2486 
2487 	gi.SetBrushModel( ent, ent->model );
2488 
2489 	// find pendulum length
2490 	length = fabs( ent->mins[2] );
2491 	if ( length < 8 ) {
2492 		length = 8;
2493 	}
2494 
2495 	freq = 1 / ( M_PI * 2 ) * sqrt( g_gravity->value / ( 3 * length ) );
2496 
2497 	ent->s.pos.trDuration = ( 1000 / freq );
2498 
2499 	InitMover( ent );
2500 
2501 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
2502 	VectorCopy( ent->s.origin, ent->currentOrigin );
2503 
2504 	VectorCopy( ent->s.angles, ent->s.apos.trBase );
2505 
2506 	ent->s.apos.trDuration = 1000 / freq;
2507 	ent->s.apos.trTime = ent->s.apos.trDuration * phase;
2508 	ent->s.apos.trType = TR_SINE;
2509 
2510 	ent->s.apos.trDelta[2] = speed;
2511 }
2512 
2513 /*
2514 ===============================================================================
2515 
2516 WALL
2517 
2518 ===============================================================================
2519 */
2520 
2521 //static -slc
use_wall(gentity_t * ent,gentity_t * other,gentity_t * activator)2522 void use_wall( gentity_t *ent, gentity_t *other, gentity_t *activator )
2523 {
2524 	G_ActivateBehavior(ent,BSET_USE);
2525 
2526 	// Not there so make it there
2527 	//if (!(ent->contents & CONTENTS_SOLID))
2528 	if (!ent->count) // off
2529 	{
2530 		ent->svFlags &= ~SVF_NOCLIENT;
2531 		ent->s.eFlags &= ~EF_NODRAW;
2532 		ent->count = 1;
2533 		//NOTE: reset the brush model because we need to get *all* the contents back
2534 		//ent->contents |= CONTENTS_SOLID;
2535 		gi.SetBrushModel( ent, ent->model );
2536 		if ( !(ent->spawnflags&1) )
2537 		{//START_OFF doesn't effect area portals
2538 			gi.AdjustAreaPortalState( ent, qfalse );
2539 		}
2540 	}
2541 	// Make it go away
2542 	else
2543 	{
2544 		//NOTE: MUST do this BEFORE clearing contents, or you may not open the area portal!!!
2545 		if ( !(ent->spawnflags&1) )
2546 		{//START_OFF doesn't effect area portals
2547 			gi.AdjustAreaPortalState( ent, qtrue );
2548 		}
2549 		ent->contents = 0;
2550 		ent->svFlags |= SVF_NOCLIENT;
2551 		ent->s.eFlags |= EF_NODRAW;
2552 		ent->count = 0; // off
2553 	}
2554 }
2555 
2556 #define FUNC_WALL_OFF	1
2557 #define FUNC_WALL_ANIM	2
2558 
2559 
2560 /*QUAKED func_wall (0 .5 .8) ? START_OFF AUTOANIMATE x x x x PLAYER_USE INACTIVE
2561 PLAYER_USE	Player can use it with the use button
2562 INACTIVE	must be used by a target_activate before it can be used
2563 
2564 A bmodel that just sits there, doing nothing.  Can be used for conditional walls and models.
2565 "model2"	.md3 model to also draw
2566 "modelAngles" md3 model's angles <pitch yaw roll> (in addition to any rotation on the part of the brush entity itself)
2567 "color"		constantLight color
2568 "light"		constantLight radius
2569 
2570 START_OFF - the wall will not be there
2571 AUTOANIMATE - if a model is used it will animate
2572 */
SP_func_wall(gentity_t * ent)2573 void SP_func_wall( gentity_t *ent )
2574 {
2575 	gi.SetBrushModel( ent, ent->model );
2576 
2577 	VectorCopy( ent->s.origin, ent->pos1 );
2578 	VectorCopy( ent->s.origin, ent->pos2 );
2579 
2580 	InitMover( ent );
2581 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
2582 	VectorCopy( ent->s.origin, ent->currentOrigin );
2583 
2584 	// count is used as an on/off switch (start it on)
2585 	ent->count = 1;
2586 
2587 	// it must be START_OFF
2588 	if (ent->spawnflags & FUNC_WALL_OFF)
2589 	{
2590 		ent->spawnContents = ent->contents;
2591 		ent->contents = 0;
2592 		ent->svFlags |= SVF_NOCLIENT;
2593 		ent->s.eFlags |= EF_NODRAW;
2594 		// turn it off
2595 		ent->count = 0;
2596 	}
2597 
2598 	if (!(ent->spawnflags & FUNC_WALL_ANIM))
2599 	{
2600 		ent->s.eFlags |= EF_ANIM_ALLFAST;
2601 	}
2602 	ent->e_UseFunc = useF_use_wall;
2603 
2604 	gi.linkentity (ent);
2605 
2606 }
2607 
2608 
security_panel_use(gentity_t * self,gentity_t * other,gentity_t * activator)2609 void security_panel_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2610 {
2611 	if ( !activator )
2612 	{
2613 		return;
2614 	}
2615 	if ( INV_SecurityKeyCheck( activator, self->message ) )
2616 	{//congrats!
2617 		gi.SendServerCommand( 0, "cp @SP_INGAME_SECURITY_KEY_UNLOCKEDDOOR" );
2618 		//use targets
2619 		G_UseTargets( self, activator );
2620 		//take key
2621 		INV_SecurityKeyTake( activator, self->message );
2622 		if ( activator->ghoul2.size() )
2623 		{
2624 			gi.G2API_SetSurfaceOnOff( &activator->ghoul2[activator->playerModel], "l_arm_key", 0x00000002 );
2625 		}
2626 		//FIXME: maybe set frame on me to have key sticking out?
2627 		//self->s.frame = 1;
2628 		//play sound
2629 		G_Sound( self, self->soundPos2 );
2630 		//unusable
2631 		self->e_UseFunc = useF_NULL;
2632 	}
2633 	else
2634 	{//failure sound/display
2635 		if ( activator->message )
2636 		{//have a key, just the wrong one
2637 			gi.SendServerCommand( 0, "cp @SP_INGAME_INCORRECT_KEY" );
2638 		}
2639 		else
2640 		{//don't have a key at all
2641 			gi.SendServerCommand( 0, "cp @SP_INGAME_NEED_SECURITY_KEY" );
2642 		}
2643 		G_UseTargets2( self, activator, self->target2 );
2644 		//FIXME: change display?  Maybe shader animmap change?
2645 		//play sound
2646 		G_Sound( self, self->soundPos1 );
2647 	}
2648 }
2649 
2650 /*QUAKED misc_security_panel (0 .5 .8) (-8 -8 -8) (8 8 8) x x x x x x x INACTIVE
2651 model="models/map_objects/kejim/sec_panel.md3"
2652   A model that just sits there and opens when a player uses it and has right key
2653 
2654 INACTIVE - Start off, has to be activated to be usable
2655 
2656 "message"	name of the key player must have
2657 "target"	thing to use when successfully opened
2658 "target2"	thing to use when player uses the panel without the key
2659 */
SP_misc_security_panel(gentity_t * self)2660 void SP_misc_security_panel ( gentity_t *self )
2661 {
2662 	self->s.modelindex = G_ModelIndex( "models/map_objects/kejim/sec_panel.md3" );
2663 	self->soundPos1 = G_SoundIndex( "sound/movers/sec_panel_fail.mp3" );
2664 	self->soundPos2 = G_SoundIndex( "sound/movers/sec_panel_pass.mp3" );
2665 	G_SetOrigin( self, self->s.origin );
2666 	G_SetAngles( self, self->s.angles );
2667 	VectorSet( self->mins, -8, -8, -8 );
2668 	VectorSet( self->maxs, 8, 8, 8 );
2669 	self->contents = CONTENTS_SOLID;
2670 	gi.linkentity( self );
2671 
2672 	//NOTE: self->message is the key
2673 	self->svFlags |= SVF_PLAYER_USABLE;
2674 	if(self->spawnflags & 128)
2675 	{
2676 		self->svFlags |= SVF_INACTIVE;
2677 	}
2678 	self->e_UseFunc = useF_security_panel_use;
2679 }
2680