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