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) 2005 - 2015, ioquake3 contributors
7 Copyright (C) 2013 - 2015, OpenJK contributors
8 
9 This file is part of the OpenJK source code.
10 
11 OpenJK is free software; you can redistribute it and/or modify it
12 under the terms of the GNU General Public License version 2 as
13 published by the Free Software Foundation.
14 
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, see <http://www.gnu.org/licenses/>.
22 ===========================================================================
23 */
24 
25 #include "g_local.h"
26 
27 
28 /*
29 ===============================================================================
30 
31 PUSHMOVE
32 
33 ===============================================================================
34 */
35 
36 void MatchTeam( gentity_t *teamLeader, int moverState, int time );
37 
38 typedef struct pushed_s {
39 	gentity_t	*ent;
40 	vec3_t	origin;
41 	vec3_t	angles;
42 	float	deltayaw;
43 } pushed_t;
44 pushed_t	pushed[MAX_GENTITIES], *pushed_p;
45 
46 #define MOVER_START_ON		1
47 #define MOVER_FORCE_ACTIVATE	2
48 #define MOVER_CRUSHER		4
49 #define MOVER_TOGGLE		8
50 #define MOVER_LOCKED		16
51 #define MOVER_GOODIE		32
52 #define MOVER_PLAYER_USE	64
53 #define MOVER_INACTIVE		128
54 
55 int	BMS_START = 0;
56 int	BMS_MID = 1;
57 int	BMS_END = 2;
58 
59 /*
60 -------------------------
61 G_PlayDoorLoopSound
62 -------------------------
63 */
64 
G_PlayDoorLoopSound(gentity_t * ent)65 void G_PlayDoorLoopSound( gentity_t *ent )
66 {
67 	if (!ent->soundSet || !ent->soundSet[0])
68 	{
69 		return;
70 	}
71 
72 	ent->s.soundSetIndex = G_SoundSetIndex(ent->soundSet);
73 	ent->s.loopIsSoundset = qtrue;
74 	ent->s.loopSound = BMS_MID;
75 	/*
76 	ent->s.soundSetIndex = G_SoundSetIndex(ent->soundSet);
77 	ent->loopingOnClient = qtrue;
78 	G_AddEvent(ent, EV_PLAYDOORLOOPSOUND, 0);
79 	*/
80 }
81 
82 /*
83 -------------------------
84 G_PlayDoorSound
85 -------------------------
86 */
87 
G_PlayDoorSound(gentity_t * ent,int type)88 void G_PlayDoorSound( gentity_t *ent, int type )
89 {
90 	if (!ent->soundSet || !ent->soundSet[0])
91 	{
92 		return;
93 	}
94 
95 	ent->s.soundSetIndex = G_SoundSetIndex(ent->soundSet);
96 
97 	G_AddEvent(ent, EV_PLAYDOORSOUND, type);
98 }
99 
100 /*
101 ============
102 G_TestEntityPosition
103 
104 ============
105 */
G_TestEntityPosition(gentity_t * ent)106 gentity_t	*G_TestEntityPosition( gentity_t *ent ) {
107 	trace_t	tr;
108 	int		mask;
109 
110 	if ( ent->clipmask ) {
111 		mask = ent->clipmask;
112 	} else {
113 		mask = MASK_SOLID;
114 	}
115 	if ( ent->client ) {
116 		vec3_t vMax;
117 		VectorCopy(ent->r.maxs, vMax);
118 		if (vMax[2] < 1)
119 		{
120 			vMax[2] = 1;
121 		}
122 		trap->Trace( &tr, ent->client->ps.origin, ent->r.mins, vMax, ent->client->ps.origin, ent->s.number, mask, qfalse, 0, 0 );
123 	} else {
124 		trap->Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, mask, qfalse, 0, 0 );
125 	}
126 
127 	if (tr.startsolid)
128 		return &g_entities[ tr.entityNum ];
129 
130 	return NULL;
131 }
132 
133 /*
134 ================
135 G_CreateRotationMatrix
136 ================
137 */
G_CreateRotationMatrix(vec3_t angles,matrix3_t matrix)138 void G_CreateRotationMatrix(vec3_t angles, matrix3_t matrix) {
139 	AngleVectors(angles, matrix[0], matrix[1], matrix[2]);
140 	VectorInverse(matrix[1]);
141 }
142 
143 /*
144 ================
145 G_TransposeMatrix
146 ================
147 */
G_TransposeMatrix(matrix3_t matrix,matrix3_t transpose)148 void G_TransposeMatrix(matrix3_t matrix, matrix3_t transpose) {
149 	int i, j;
150 	for (i = 0; i < 3; i++) {
151 		for (j = 0; j < 3; j++) {
152 			transpose[i][j] = matrix[j][i];
153 		}
154 	}
155 }
156 
157 /*
158 ================
159 G_RotatePoint
160 ================
161 */
G_RotatePoint(vec3_t point,matrix3_t matrix)162 void G_RotatePoint(vec3_t point, matrix3_t matrix) {
163 	vec3_t tvec;
164 
165 	VectorCopy(point, tvec);
166 	point[0] = DotProduct(matrix[0], tvec);
167 	point[1] = DotProduct(matrix[1], tvec);
168 	point[2] = DotProduct(matrix[2], tvec);
169 }
170 
171 /*
172 ==================
173 G_TryPushingEntity
174 
175 Returns qfalse if the move is blocked
176 ==================
177 */
G_TryPushingEntity(gentity_t * check,gentity_t * pusher,vec3_t move,vec3_t amove)178 qboolean	G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) {
179 	matrix3_t	matrix, transpose;
180 	vec3_t		org, org2, move2;
181 	gentity_t	*block;
182 
183 	//This was only serverside not to mention it was never set.
184 	/*
185 	// EF_MOVER_STOP will just stop when contacting another entity
186 	// instead of pushing it, but entities can still ride on top of it
187 	if ( ( pusher->s.eFlags & EF_MOVER_STOP ) &&
188 		check->s.groundEntityNum != pusher->s.number ) {
189 		return qfalse;
190 	}
191 	*/
192 	if ( pusher->s.apos.trType != TR_STATIONARY//rotating
193 		&& (pusher->spawnflags&16) //IMPACT
194 		&& Q_stricmp( "func_rotating", pusher->classname ) == 0 )
195 	{//just blow the fuck out of them
196 		G_Damage( check, pusher, pusher, NULL, NULL, pusher->damage, DAMAGE_NO_KNOCKBACK, MOD_CRUSH );
197 		return qtrue;
198 	}
199 
200 	// save off the old position
201 	if (pushed_p > &pushed[MAX_GENTITIES]) {
202 		trap->Error( ERR_DROP, "pushed_p > &pushed[MAX_GENTITIES]" );
203 	}
204 	pushed_p->ent = check;
205 	VectorCopy (check->s.pos.trBase, pushed_p->origin);
206 	VectorCopy (check->s.apos.trBase, pushed_p->angles);
207 	if ( check->client ) {
208 		pushed_p->deltayaw = check->client->ps.delta_angles[YAW];
209 		VectorCopy (check->client->ps.origin, pushed_p->origin);
210 	}
211 	pushed_p++;
212 
213 	// try moving the contacted entity
214 	// figure movement due to the pusher's amove
215 	G_CreateRotationMatrix( amove, transpose );
216 	G_TransposeMatrix( transpose, matrix );
217 	if ( check->client ) {
218 		VectorSubtract (check->client->ps.origin, pusher->r.currentOrigin, org);
219 	}
220 	else {
221 		VectorSubtract (check->s.pos.trBase, pusher->r.currentOrigin, org);
222 	}
223 	VectorCopy( org, org2 );
224 	G_RotatePoint( org2, matrix );
225 	VectorSubtract (org2, org, move2);
226 	// add movement
227 	VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase);
228 	VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase);
229 	if ( check->client ) {
230 		VectorAdd (check->client->ps.origin, move, check->client->ps.origin);
231 		VectorAdd (check->client->ps.origin, move2, check->client->ps.origin);
232 		// make sure the client's view rotates when on a rotating mover
233 		check->client->ps.delta_angles[YAW] += ANGLE2SHORT(amove[YAW]);
234 	}
235 
236 	// may have pushed them off an edge
237 	if ( check->s.groundEntityNum != pusher->s.number ) {
238 		check->s.groundEntityNum = ENTITYNUM_NONE;
239 	}
240 
241 	block = G_TestEntityPosition( check );
242 	if (!block) {
243 		// pushed ok
244 		if ( check->client ) {
245 			VectorCopy( check->client->ps.origin, check->r.currentOrigin );
246 		} else {
247 			VectorCopy( check->s.pos.trBase, check->r.currentOrigin );
248 		}
249 		trap->LinkEntity ((sharedEntity_t *)check);
250 		return qtrue;
251 	}
252 
253 	if (check->takedamage && !check->client && check->s.weapon && check->r.ownerNum < MAX_CLIENTS &&
254 		check->health < 500)
255 	{
256 		if (check->health > 0)
257 		{
258 			G_Damage(check, pusher, pusher, vec3_origin, check->r.currentOrigin, 999, 0, MOD_UNKNOWN);
259 		}
260 	}
261 	// if it is ok to leave in the old position, do it
262 	// this is only relevent for riding entities, not pushed
263 	// Sliding trapdoors can cause this.
264 	VectorCopy( (pushed_p-1)->origin, check->s.pos.trBase);
265 	if ( check->client ) {
266 		VectorCopy( (pushed_p-1)->origin, check->client->ps.origin);
267 	}
268 	VectorCopy( (pushed_p-1)->angles, check->s.apos.trBase );
269 	block = G_TestEntityPosition (check);
270 	if ( !block ) {
271 		check->s.groundEntityNum = ENTITYNUM_NONE;
272 		pushed_p--;
273 		return qtrue;
274 	}
275 
276 	// blocked
277 	return qfalse;
278 }
279 
280 
281 void G_ExplodeMissile( gentity_t *ent );
282 
283 /*
284 ============
285 G_MoverPush
286 
287 Objects need to be moved back on a failed push,
288 otherwise riders would continue to slide.
289 If qfalse is returned, *obstacle will be the blocking entity
290 ============
291 */
292 void NPC_RemoveBody( gentity_t *self );
G_MoverPush(gentity_t * pusher,vec3_t move,vec3_t amove,gentity_t ** obstacle)293 qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **obstacle ) {
294 	int			i, e;
295 	gentity_t	*check;
296 	vec3_t		mins, maxs;
297 	pushed_t	*p;
298 	int			entityList[MAX_GENTITIES];
299 	int			listedEntities;
300 	vec3_t		totalMins, totalMaxs;
301 
302 	*obstacle = NULL;
303 
304 
305 	// mins/maxs are the bounds at the destination
306 	// totalMins / totalMaxs are the bounds for the entire move
307 	if ( pusher->r.currentAngles[0] || pusher->r.currentAngles[1] || pusher->r.currentAngles[2]
308 		|| amove[0] || amove[1] || amove[2] ) {
309 		float		radius;
310 
311 		radius = RadiusFromBounds( pusher->r.mins, pusher->r.maxs );
312 		for ( i = 0 ; i < 3 ; i++ ) {
313 			mins[i] = pusher->r.currentOrigin[i] + move[i] - radius;
314 			maxs[i] = pusher->r.currentOrigin[i] + move[i] + radius;
315 			totalMins[i] = mins[i] - move[i];
316 			totalMaxs[i] = maxs[i] - move[i];
317 		}
318 	} else {
319 		for (i=0 ; i<3 ; i++) {
320 			mins[i] = pusher->r.absmin[i] + move[i];
321 			maxs[i] = pusher->r.absmax[i] + move[i];
322 		}
323 
324 		VectorCopy( pusher->r.absmin, totalMins );
325 		VectorCopy( pusher->r.absmax, totalMaxs );
326 		for (i=0 ; i<3 ; i++) {
327 			if ( move[i] > 0 ) {
328 				totalMaxs[i] += move[i];
329 			} else {
330 				totalMins[i] += move[i];
331 			}
332 		}
333 	}
334 
335 	// unlink the pusher so we don't get it in the entityList
336 	trap->UnlinkEntity( (sharedEntity_t *)pusher );
337 
338 	listedEntities = trap->EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES );
339 
340 	// move the pusher to it's final position
341 	VectorAdd( pusher->r.currentOrigin, move, pusher->r.currentOrigin );
342 	VectorAdd( pusher->r.currentAngles, amove, pusher->r.currentAngles );
343 	trap->LinkEntity( (sharedEntity_t *)pusher );
344 
345 	// see if any solid entities are inside the final position
346 	for ( e = 0 ; e < listedEntities ; e++ ) {
347 		check = &g_entities[ entityList[ e ] ];
348 
349 		// only push items and players
350 		if ( /*check->s.eType != ET_ITEM &&*/ check->s.eType != ET_PLAYER && check->s.eType != ET_NPC && !check->physicsObject ) {
351 			continue;
352 		}
353 
354 		// if the entity is standing on the pusher, it will definitely be moved
355 		if ( check->s.groundEntityNum != pusher->s.number ) {
356 			// see if the ent needs to be tested
357 			if ( check->r.absmin[0] >= maxs[0]
358 			|| check->r.absmin[1] >= maxs[1]
359 			|| check->r.absmin[2] >= maxs[2]
360 			|| check->r.absmax[0] <= mins[0]
361 			|| check->r.absmax[1] <= mins[1]
362 			|| check->r.absmax[2] <= mins[2] ) {
363 				continue;
364 			}
365 			// see if the ent's bbox is inside the pusher's final position
366 			// this does allow a fast moving object to pass through a thin entity...
367 			if (!G_TestEntityPosition (check)) {
368 				continue;
369 			}
370 		}
371 
372 		// the entity needs to be pushed
373 		if ( G_TryPushingEntity( check, pusher, move, amove ) ) {
374 			continue;
375 		}
376 
377 		if (pusher->damage && check->client && (pusher->spawnflags & 32))
378 		{
379 			G_Damage( check, pusher, pusher, NULL, NULL, pusher->damage, 0, MOD_CRUSH );
380 			continue;
381 		}
382 
383 		if (check->s.eType == ET_BODY ||
384 			(check->s.eType == ET_PLAYER && check->health < 1))
385 		{ //whatever, just crush it
386 			G_Damage( check, pusher, pusher, NULL, NULL, 999, 0, MOD_CRUSH );
387 			continue;
388 		}
389 
390 		if ( (check->r.contents & CONTENTS_TRIGGER) && check->s.weapon == G2_MODEL_PART)
391 		{//keep limbs from blocking elevators.  Kill the limb and keep moving.
392 			G_FreeEntity(check);
393 			continue;
394 		}
395 
396 		if( check->s.eFlags & EF_DROPPEDWEAPON )
397 		{//keep dropped weapons from blocking elevators.  Kill the weapon and keep moving.
398 			G_FreeEntity(check);
399 			continue;
400 		}
401 
402 		if ( check->s.eType == ET_NPC //an NPC
403 			&& check->health <= 0 //NPC is dead
404 			&& !(check->flags & FL_NOTARGET) )  //NPC isn't still spawned or in no target mode.
405 		{//dead npcs should be removed now!
406 			NPC_RemoveBody( check );
407 			continue;
408 		}
409 
410 		// the move was blocked an entity
411 
412 		// bobbing entities are instant-kill and never get blocked
413 		if ( pusher->s.pos.trType == TR_SINE || pusher->s.apos.trType == TR_SINE ) {
414 			G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH );
415 			continue;
416 		}
417 
418 
419 		// save off the obstacle so we can call the block function (crush, etc)
420 		*obstacle = check;
421 
422 		// move back any entities we already moved
423 		// go backwards, so if the same entity was pushed
424 		// twice, it goes back to the original position
425 		for ( p=pushed_p-1 ; p>=pushed ; p-- ) {
426 			VectorCopy (p->origin, p->ent->s.pos.trBase);
427 			VectorCopy (p->angles, p->ent->s.apos.trBase);
428 			if ( p->ent->client ) {
429 				p->ent->client->ps.delta_angles[YAW] = p->deltayaw;
430 				VectorCopy (p->origin, p->ent->client->ps.origin);
431 			}
432 			trap->LinkEntity ((sharedEntity_t *)p->ent);
433 		}
434 		return qfalse;
435 	}
436 
437 	return qtrue;
438 }
439 
440 
441 /*
442 =================
443 G_MoverTeam
444 =================
445 */
G_MoverTeam(gentity_t * ent)446 void G_MoverTeam( gentity_t *ent ) {
447 	vec3_t		move, amove;
448 	gentity_t	*part, *obstacle;
449 	vec3_t		origin, angles;
450 
451 	obstacle = NULL;
452 
453 	// make sure all team slaves can move before commiting
454 	// any moves or calling any think functions
455 	// if the move is blocked, all moved objects will be backed out
456 	pushed_p = pushed;
457 	for (part = ent ; part ; part=part->teamchain) {
458 		// get current position
459 		BG_EvaluateTrajectory( &part->s.pos, level.time, origin );
460 		BG_EvaluateTrajectory( &part->s.apos, level.time, angles );
461 		VectorSubtract( origin, part->r.currentOrigin, move );
462 		VectorSubtract( angles, part->r.currentAngles, amove );
463 		if ( !VectorCompare( move, vec3_origin )
464 			|| !VectorCompare( amove, vec3_origin ) )
465 		{//actually moved
466 			if ( !G_MoverPush( part, move, amove, &obstacle ) ) {
467 				break;	// move was blocked
468 			}
469 		}
470 	}
471 
472 	if (part) {
473 		// go back to the previous position
474 		for ( part = ent ; part ; part = part->teamchain ) {
475 			part->s.pos.trTime += level.time - level.previousTime;
476 			part->s.apos.trTime += level.time - level.previousTime;
477 			BG_EvaluateTrajectory( &part->s.pos, level.time, part->r.currentOrigin );
478 			BG_EvaluateTrajectory( &part->s.apos, level.time, part->r.currentAngles );
479 			trap->LinkEntity( (sharedEntity_t *)part );
480 		}
481 
482 		// if the pusher has a "blocked" function, call it
483 		if (ent->blocked) {
484 			ent->blocked( ent, obstacle );
485 		}
486 		return;
487 	}
488 
489 	// the move succeeded
490 	for ( part = ent ; part ; part = part->teamchain ) {
491 		// call the reached function if time is at or past end point
492 		if ( part->s.pos.trType == TR_LINEAR_STOP ||
493 			part->s.pos.trType == TR_NONLINEAR_STOP) {
494 			if ( level.time >= part->s.pos.trTime + part->s.pos.trDuration ) {
495 				if ( part->reached ) {
496 					part->reached( part );
497 				}
498 			}
499 		}
500 	}
501 }
502 
503 /*
504 ================
505 G_RunMover
506 
507 ================
508 */
G_RunMover(gentity_t * ent)509 void G_RunMover( gentity_t *ent ) {
510 	// if not a team captain, don't do anything, because
511 	// the captain will handle everything
512 	if ( ent->flags & FL_TEAMSLAVE ) {
513 		return;
514 	}
515 
516 	// if stationary at one of the positions, don't move anything
517 	if ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) {
518 		G_MoverTeam( ent );
519 	}
520 
521 	// check think function
522 	G_RunThink( ent );
523 }
524 
525 /*
526 ============================================================================
527 
528 GENERAL MOVERS
529 
530 Doors, plats, and buttons are all binary (two position) movers
531 Pos1 is "at rest", pos2 is "activated"
532 ============================================================================
533 */
534 
535 /*
536 CalcTeamDoorCenter
537 
538 Finds all the doors of a team and returns their center position
539 */
540 
CalcTeamDoorCenter(gentity_t * ent,vec3_t center)541 void CalcTeamDoorCenter ( gentity_t *ent, vec3_t center )
542 {
543 	vec3_t		slavecenter;
544 	gentity_t	*slave;
545 
546 	//Start with our center
547 	VectorAdd(ent->r.mins, ent->r.maxs, center);
548 	VectorScale(center, 0.5, center);
549 	for ( slave = ent->teamchain ; slave ; slave = slave->teamchain )
550 	{
551 		//Find slave's center
552 		VectorAdd(slave->r.mins, slave->r.maxs, slavecenter);
553 		VectorScale(slavecenter, 0.5, slavecenter);
554 		//Add that to our own, find middle
555 		VectorAdd(center, slavecenter, center);
556 		VectorScale(center, 0.5, center);
557 	}
558 }
559 
560 /*
561 ===============
562 SetMoverState
563 ===============
564 */
SetMoverState(gentity_t * ent,moverState_t moverState,int time)565 void SetMoverState( gentity_t *ent, moverState_t moverState, int time ) {
566 	vec3_t			delta;
567 	float			f;
568 
569 	ent->moverState = moverState;
570 
571 	ent->s.pos.trTime = time;
572 
573 	if ( ent->s.pos.trDuration <= 0 )
574 	{//Don't allow divide by zero!
575 		ent->s.pos.trDuration = 1;
576 	}
577 
578 	switch( moverState ) {
579 	case MOVER_POS1:
580 		VectorCopy( ent->pos1, ent->s.pos.trBase );
581 		ent->s.pos.trType = TR_STATIONARY;
582 		break;
583 	case MOVER_POS2:
584 		VectorCopy( ent->pos2, ent->s.pos.trBase );
585 		ent->s.pos.trType = TR_STATIONARY;
586 		break;
587 	case MOVER_1TO2:
588 		VectorCopy( ent->pos1, ent->s.pos.trBase );
589 		VectorSubtract( ent->pos2, ent->pos1, delta );
590 		f = 1000.0 / ent->s.pos.trDuration;
591 		VectorScale( delta, f, ent->s.pos.trDelta );
592 		if ( ent->alt_fire )
593 		{
594 			ent->s.pos.trType = TR_LINEAR_STOP;
595 		}
596 		else
597 		{
598 			ent->s.pos.trType = TR_NONLINEAR_STOP;
599 		}
600 		//ent->s.eFlags &= ~EF_BLOCKED_MOVER;
601 		break;
602 	case MOVER_2TO1:
603 		VectorCopy( ent->pos2, ent->s.pos.trBase );
604 		VectorSubtract( ent->pos1, ent->pos2, delta );
605 		f = 1000.0 / ent->s.pos.trDuration;
606 		VectorScale( delta, f, ent->s.pos.trDelta );
607 		if ( ent->alt_fire )
608 		{
609 			ent->s.pos.trType = TR_LINEAR_STOP;
610 		}
611 		else
612 		{
613 			ent->s.pos.trType = TR_NONLINEAR_STOP;
614 		}
615 		//ent->s.eFlags &= ~EF_BLOCKED_MOVER;
616 		break;
617 	}
618 	BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin );
619 	trap->LinkEntity( (sharedEntity_t *)ent );
620 }
621 
622 /*
623 ================
624 MatchTeam
625 
626 All entities in a mover team will move from pos1 to pos2
627 in the same amount of time
628 ================
629 */
MatchTeam(gentity_t * teamLeader,int moverState,int time)630 void MatchTeam( gentity_t *teamLeader, int moverState, int time ) {
631 	gentity_t		*slave;
632 
633 	for ( slave = teamLeader ; slave ; slave = slave->teamchain ) {
634 		SetMoverState( slave, (moverState_t) moverState, time );
635 	}
636 }
637 
638 
639 
640 /*
641 ================
642 ReturnToPos1
643 ================
644 */
ReturnToPos1(gentity_t * ent)645 void ReturnToPos1( gentity_t *ent ) {
646 	ent->think = 0;
647 	ent->nextthink = 0;
648 	ent->s.time = level.time;
649 
650 	MatchTeam( ent, MOVER_2TO1, level.time );
651 
652 	// starting sound
653 	G_PlayDoorLoopSound( ent );
654 	G_PlayDoorSound( ent, BMS_START );	//??
655 }
656 
657 
658 /*
659 ================
660 Reached_BinaryMover
661 ================
662 */
663 
Reached_BinaryMover(gentity_t * ent)664 void Reached_BinaryMover( gentity_t *ent )
665 {
666 	// stop the looping sound
667 	ent->s.loopSound = 0;
668 	ent->s.loopIsSoundset = qfalse;
669 
670 	if ( ent->moverState == MOVER_1TO2 )
671 	{//reached open
672 		vec3_t	doorcenter;
673 
674 		// reached pos2
675 		SetMoverState( ent, MOVER_POS2, level.time );
676 
677 		CalcTeamDoorCenter( ent, doorcenter );
678 
679 		// play sound
680 		G_PlayDoorSound( ent, BMS_END );
681 
682 		if ( ent->wait < 0 )
683 		{//Done for good
684 			ent->think = 0;
685 			ent->nextthink = 0;
686 			ent->use = 0;
687 		}
688 		else
689 		{
690 			// return to pos1 after a delay
691 			ent->think = ReturnToPos1;
692 			if(ent->spawnflags & 8)
693 			{//Toggle, keep think, wait for next use?
694 				ent->nextthink = -1;
695 			}
696 			else
697 			{
698 				ent->nextthink = level.time + ent->wait;
699 			}
700 		}
701 
702 		// fire targets
703 		if ( !ent->activator )
704 		{
705 			ent->activator = ent;
706 		}
707 		G_UseTargets2( ent, ent->activator, ent->opentarget );
708 	}
709 	else if ( ent->moverState == MOVER_2TO1 )
710 	{//closed
711 		vec3_t	doorcenter;
712 
713 		// reached pos1
714 		SetMoverState( ent, MOVER_POS1, level.time );
715 
716 		CalcTeamDoorCenter( ent, doorcenter );
717 
718 		// play sound
719 		G_PlayDoorSound( ent, BMS_END );
720 
721 		// close areaportals
722 		if ( ent->teammaster == ent || !ent->teammaster )
723 		{
724 			trap->AdjustAreaPortalState( (sharedEntity_t *)ent, qfalse );
725 		}
726 		G_UseTargets2( ent, ent->activator, ent->closetarget );
727 	}
728 	else
729 	{
730 		trap->Error( ERR_DROP, "Reached_BinaryMover: bad moverState" );
731 	}
732 }
733 
734 
735 /*
736 ================
737 Use_BinaryMover_Go
738 ================
739 */
Use_BinaryMover_Go(gentity_t * ent)740 void Use_BinaryMover_Go( gentity_t *ent )
741 {
742 	int		total;
743 	int		partial;
744 //	gentity_t	*other = ent->enemy;
745 	gentity_t	*activator = ent->activator;
746 
747 	ent->activator = activator;
748 
749 	if ( ent->moverState == MOVER_POS1 )
750 	{
751 		vec3_t	doorcenter;
752 
753 		// start moving 50 msec later, becase if this was player
754 		// triggered, level.time hasn't been advanced yet
755 		MatchTeam( ent, MOVER_1TO2, level.time + 50 );
756 
757 		CalcTeamDoorCenter( ent, doorcenter );
758 
759 		// starting sound
760 		G_PlayDoorLoopSound( ent );
761 		G_PlayDoorSound( ent, BMS_START );
762 		ent->s.time = level.time;
763 
764 		// open areaportal
765 		if ( ent->teammaster == ent || !ent->teammaster ) {
766 			trap->AdjustAreaPortalState( (sharedEntity_t *)ent, qtrue );
767 		}
768 		G_UseTargets( ent, ent->activator );
769 		return;
770 	}
771 
772 	// if all the way up, just delay before coming down
773 	if ( ent->moverState == MOVER_POS2 ) {
774 		//have to do this because the delay sets our think to Use_BinaryMover_Go
775 		ent->think = ReturnToPos1;
776 		if ( ent->spawnflags & 8 )
777 		{//TOGGLE doors don't use wait!
778 			ent->nextthink = level.time + FRAMETIME;
779 		}
780 		else
781 		{
782 			ent->nextthink = level.time + ent->wait;
783 		}
784 		G_UseTargets2( ent, ent->activator, ent->target2 );
785 		return;
786 	}
787 
788 	// only partway down before reversing
789 	if ( ent->moverState == MOVER_2TO1 )
790 	{
791 		if ( ent->s.pos.trType == TR_NONLINEAR_STOP )
792 		{
793 			vec3_t curDelta;
794 			float fPartial;
795 			total = ent->s.pos.trDuration-50;
796 			VectorSubtract( ent->r.currentOrigin, ent->pos1, curDelta );
797 			fPartial = VectorLength( curDelta )/VectorLength( ent->s.pos.trDelta );
798 			VectorScale( ent->s.pos.trDelta, fPartial, curDelta );
799 			fPartial /= ent->s.pos.trDuration;
800 			fPartial /= 0.001f;
801 			fPartial = acos( fPartial );
802 			fPartial = RAD2DEG( fPartial );
803 			fPartial = (90.0f - fPartial)/90.0f*ent->s.pos.trDuration;
804 			partial = total - floor( fPartial );
805 		}
806 		else
807 		{
808 			total = ent->s.pos.trDuration;
809 			partial = level.time - ent->s.pos.trTime;
810 		}
811 
812 		if ( partial > total ) {
813 			partial = total;
814 		}
815 		ent->s.pos.trTime = level.time - ( total - partial );//ent->s.time;
816 
817 		MatchTeam( ent, MOVER_1TO2, ent->s.pos.trTime );
818 
819 		G_PlayDoorSound( ent, BMS_START );
820 
821 		return;
822 	}
823 
824 	// only partway up before reversing
825 	if ( ent->moverState == MOVER_1TO2 )
826 	{
827 		if ( ent->s.pos.trType == TR_NONLINEAR_STOP )
828 		{
829 			vec3_t curDelta;
830 			float fPartial;
831 			total = ent->s.pos.trDuration-50;
832 			VectorSubtract( ent->r.currentOrigin, ent->pos2, curDelta );
833 			fPartial = VectorLength( curDelta )/VectorLength( ent->s.pos.trDelta );
834 			VectorScale( ent->s.pos.trDelta, fPartial, curDelta );
835 			fPartial /= ent->s.pos.trDuration;
836 			fPartial /= 0.001f;
837 			fPartial = acos( fPartial );
838 			fPartial = RAD2DEG( fPartial );
839 			fPartial = (90.0f - fPartial)/90.0f*ent->s.pos.trDuration;
840 			partial = total - floor( fPartial );
841 		}
842 		else
843 		{
844 			total = ent->s.pos.trDuration;
845 			partial = level.time - ent->s.pos.trTime;
846 		}
847 		if ( partial > total ) {
848 			partial = total;
849 		}
850 
851 		ent->s.pos.trTime = level.time - ( total - partial );//ent->s.time;
852 		MatchTeam( ent, MOVER_2TO1, ent->s.pos.trTime );
853 
854 		G_PlayDoorSound( ent, BMS_START );
855 
856 		return;
857 	}
858 }
859 
UnLockDoors(gentity_t * const ent)860 void UnLockDoors(gentity_t *const ent)
861 {
862 	//noise?
863 	//go through and unlock the door and all the slaves
864 	gentity_t	*slave = ent;
865 	do
866 	{	// want to allow locked toggle doors, so keep the targetname
867 		if( !(slave->spawnflags & MOVER_TOGGLE) )
868 		{
869 			slave->targetname = NULL;//not usable ever again
870 		}
871 		slave->spawnflags &= ~MOVER_LOCKED;
872 		slave->s.frame = 1;//second stage of anim
873 		slave = slave->teamchain;
874 	} while  ( slave );
875 }
LockDoors(gentity_t * const ent)876 void LockDoors(gentity_t *const ent)
877 {
878 	//noise?
879 	//go through and lock the door and all the slaves
880 	gentity_t	*slave = ent;
881 	do
882 	{
883 		slave->spawnflags |= MOVER_LOCKED;
884 		slave->s.frame = 0;//first stage of anim
885 		slave = slave->teamchain;
886 	} while  ( slave );
887 }
888 /*
889 ================
890 Use_BinaryMover
891 ================
892 */
Use_BinaryMover(gentity_t * ent,gentity_t * other,gentity_t * activator)893 void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator )
894 {
895 	if ( !ent->use )
896 	{//I cannot be used anymore, must be a door with a wait of -1 that's opened.
897 		return;
898 	}
899 
900 	// only the master should be used
901 	if ( ent->flags & FL_TEAMSLAVE )
902 	{
903 		Use_BinaryMover( ent->teammaster, other, activator );
904 		return;
905 	}
906 
907 	if ( ent->flags & FL_INACTIVE )
908 	{
909 		return;
910 	}
911 
912 	if ( ent->spawnflags & MOVER_LOCKED )
913 	{//a locked door, unlock it
914 		UnLockDoors(ent);
915 		return;
916 	}
917 
918 	G_ActivateBehavior(ent,BSET_USE);
919 
920 	ent->enemy = other;
921 	ent->activator = activator;
922 	if(ent->delay)
923 	{
924 		ent->think = Use_BinaryMover_Go;
925 		ent->nextthink = level.time + ent->delay;
926 	}
927 	else
928 	{
929 		Use_BinaryMover_Go(ent);
930 	}
931 }
932 
933 
934 
935 /*
936 ================
937 InitMover
938 
939 "pos1", "pos2", and "speed" should be set before calling,
940 so the movement delta can be calculated
941 ================
942 */
InitMoverTrData(gentity_t * ent)943 void InitMoverTrData( gentity_t *ent )
944 {
945 	vec3_t		move;
946 	float		distance;
947 
948 	ent->s.pos.trType = TR_STATIONARY;
949 	VectorCopy( ent->pos1, ent->s.pos.trBase );
950 
951 	// calculate time to reach second position from speed
952 	VectorSubtract( ent->pos2, ent->pos1, move );
953 	distance = VectorLength( move );
954 	if ( ! ent->speed )
955 	{
956 		ent->speed = 100;
957 	}
958 	VectorScale( move, ent->speed, ent->s.pos.trDelta );
959 	ent->s.pos.trDuration = distance * 1000 / ent->speed;
960 	if ( ent->s.pos.trDuration <= 0 )
961 	{
962 		ent->s.pos.trDuration = 1;
963 	}
964 }
965 
InitMover(gentity_t * ent)966 void InitMover( gentity_t *ent )
967 {
968 	float		light;
969 	vec3_t		color;
970 	qboolean	lightSet, colorSet;
971 
972 	// if the "model2" key is set, use a seperate model
973 	// for drawing, but clip against the brushes
974 	if ( ent->model2 )
975 	{
976 		if ( strstr( ent->model2, ".glm" ))
977 		{ //for now, not supported in MP.
978 			ent->s.modelindex2 = 0;
979 		}
980 		else
981 		{
982 			ent->s.modelindex2 = G_ModelIndex( ent->model2 );
983 		}
984 	}
985 
986 	// if the "color" or "light" keys are set, setup constantLight
987 	lightSet = G_SpawnFloat( "light", "100", &light );
988 	colorSet = G_SpawnVector( "color", "1 1 1", color );
989 	if ( lightSet || colorSet ) {
990 		int		r, g, b, i;
991 
992 		r = color[0] * 255;
993 		if ( r > 255 ) {
994 			r = 255;
995 		}
996 		g = color[1] * 255;
997 		if ( g > 255 ) {
998 			g = 255;
999 		}
1000 		b = color[2] * 255;
1001 		if ( b > 255 ) {
1002 			b = 255;
1003 		}
1004 		i = light / 4;
1005 		if ( i > 255 ) {
1006 			i = 255;
1007 		}
1008 		ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
1009 	}
1010 
1011 	ent->use = Use_BinaryMover;
1012 	ent->reached = Reached_BinaryMover;
1013 
1014 	ent->moverState = MOVER_POS1;
1015 	ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
1016 	if ( ent->spawnflags & MOVER_INACTIVE )
1017 	{// Make it inactive
1018 		ent->flags |= FL_INACTIVE;
1019 	}
1020 	if(ent->spawnflags & MOVER_PLAYER_USE)
1021 	{//Can be used by the player's BUTTON_USE
1022 		ent->r.svFlags |= SVF_PLAYER_USABLE;
1023 	}
1024 	ent->s.eType = ET_MOVER;
1025 	VectorCopy( ent->pos1, ent->r.currentOrigin );
1026 	trap->LinkEntity( (sharedEntity_t *)ent );
1027 
1028 	InitMoverTrData( ent );
1029 }
1030 
1031 
1032 /*
1033 ===============================================================================
1034 
1035 DOOR
1036 
1037 A use can be triggered either by a touch function, by being shot, or by being
1038 targeted by another entity.
1039 
1040 ===============================================================================
1041 */
1042 
1043 /*
1044 ================
1045 Blocked_Door
1046 ================
1047 */
Blocked_Door(gentity_t * ent,gentity_t * other)1048 void Blocked_Door( gentity_t *ent, gentity_t *other )
1049 {
1050 	//determines if we need to relock after moving or not.
1051 	qboolean relock = (ent->spawnflags & MOVER_LOCKED) ? qtrue : qfalse;
1052 	if ( ent->damage ) {
1053 		G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH );
1054 	}
1055 	if ( ent->spawnflags & MOVER_CRUSHER ) {
1056 		return;		// crushers don't reverse
1057 	}
1058 
1059 	// reverse direction
1060 	Use_BinaryMover( ent, ent, other );
1061 	if(relock)
1062 	{//door was locked before reverse move, relock door.
1063 		LockDoors(ent);
1064 	}
1065 }
1066 
1067 
1068 /*
1069 ================
1070 Touch_DoorTriggerSpectator
1071 ================
1072 */
1073 static vec3_t doorangles = { 10000000.0, 0, 0 };
Touch_DoorTriggerSpectator(gentity_t * ent,gentity_t * other,trace_t * trace)1074 static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_t *trace ) {
1075 	int axis;
1076 	float doorMin, doorMax;
1077 	vec3_t origin, pMins, pMaxs;
1078 	trace_t tr;
1079 
1080 	axis = ent->count;
1081 	// the constants below relate to constants in Think_SpawnNewDoorTrigger()
1082 	doorMin = ent->r.absmin[axis] + 100;
1083 	doorMax = ent->r.absmax[axis] - 100;
1084 
1085 	VectorCopy(other->client->ps.origin, origin);
1086 
1087 	if (origin[axis] < doorMin || origin[axis] > doorMax) return;
1088 
1089 	if (fabs(origin[axis] - doorMax) < fabs(origin[axis] - doorMin)) {
1090 		origin[axis] = doorMin - 25; // 10
1091 	} else {
1092 		origin[axis] = doorMax + 25; // 10
1093 	}
1094 
1095 	VectorSet(pMins, -15.0f, -15.0f, DEFAULT_MINS_2);
1096 	VectorSet(pMaxs, 15.0f, 15.0f, DEFAULT_MAXS_2);
1097 	trap->Trace(&tr, origin, pMins, pMaxs, origin, other->s.number, other->clipmask, qfalse, 0, 0);
1098 	if (!tr.startsolid &&
1099 		!tr.allsolid &&
1100 		tr.fraction == 1.0f &&
1101 		tr.entityNum == ENTITYNUM_NONE)
1102 	{
1103 		TeleportPlayer( other, origin, doorangles );
1104 	}
1105 }
1106 
1107 /*
1108 ================
1109 Touch_DoorTrigger
1110 ================
1111 */
Touch_DoorTrigger(gentity_t * ent,gentity_t * other,trace_t * trace)1112 void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace )
1113 {
1114 	gentity_t *relockEnt = NULL;
1115 
1116 	if ( other->client && other->client->sess.sessionTeam == TEAM_SPECTATOR )
1117 	{
1118 		// if the door is not open and not opening
1119 		if ( ent->parent->moverState != MOVER_1TO2 &&
1120 			ent->parent->moverState != MOVER_POS2 )
1121 		{
1122 			Touch_DoorTriggerSpectator( ent, other, trace );
1123 		}
1124 		return;
1125 	}
1126 
1127 	if (!ent->genericValue14 &&
1128 		(!ent->parent || !ent->parent->genericValue14))
1129 	{
1130 		if (other->client && other->s.number >= MAX_CLIENTS &&
1131 			other->s.eType == ET_NPC && other->s.NPC_class == CLASS_VEHICLE)
1132 		{ //doors don't open for vehicles
1133 			return;
1134 		}
1135 
1136 		if (other->client && other->s.number < MAX_CLIENTS &&
1137 			other->client->ps.m_iVehicleNum)
1138 		{ //can't open a door while on a vehicle
1139 			return;
1140 		}
1141 	}
1142 
1143 	if ( ent->flags & FL_INACTIVE )
1144 	{
1145 		return;
1146 	}
1147 
1148 	if ( ent->parent->spawnflags & MOVER_LOCKED )
1149 	{//don't even try to use the door if it's locked
1150 		if ( !ent->parent->alliedTeam //we don't have a "teamallow" team
1151 			|| !other->client //we do have a "teamallow" team, but this isn't a client
1152 			|| other->client->sess.sessionTeam != ent->parent->alliedTeam )//it is a client, but it's not on the right team
1153 		{
1154 			return;
1155 		}
1156 		else
1157 		{//temporarily unlock us while we call Use_BinaryMover (so it doesn't unlock all the doors in this team)
1158 			if ( ent->parent->flags & FL_TEAMSLAVE )
1159 			{
1160 				relockEnt = ent->parent->teammaster;
1161 			}
1162 			else
1163 			{
1164 				relockEnt = ent->parent;
1165 			}
1166 			if ( relockEnt != NULL )
1167 			{
1168 				relockEnt->spawnflags &= ~MOVER_LOCKED;
1169 			}
1170 		}
1171 	}
1172 
1173 	if ( ent->parent->moverState != MOVER_1TO2 )
1174 	{//Door is not already opening
1175 		//if ( ent->parent->moverState == MOVER_POS1 || ent->parent->moverState == MOVER_2TO1 )
1176 		//{//only check these if closed or closing
1177 
1178 		//If door is closed, opening or open, check this
1179 		Use_BinaryMover( ent->parent, ent, other );
1180 	}
1181 	if ( relockEnt != NULL )
1182 	{//re-lock us
1183 		relockEnt->spawnflags |= MOVER_LOCKED;
1184 	}
1185 
1186 	/*
1187 	//Old style
1188 	if ( ent->parent->moverState != MOVER_1TO2 ) {
1189 		Use_BinaryMover( ent->parent, ent, other );
1190 	}
1191 	*/
1192 }
1193 
1194 /*
1195 ======================
1196 Think_SpawnNewDoorTrigger
1197 
1198 All of the parts of a door have been spawned, so create
1199 a trigger that encloses all of them
1200 ======================
1201 */
Think_SpawnNewDoorTrigger(gentity_t * ent)1202 void Think_SpawnNewDoorTrigger( gentity_t *ent )
1203 {
1204 	gentity_t		*other;
1205 	vec3_t		mins, maxs;
1206 	int			i, best;
1207 
1208 	// set all of the slaves as shootable
1209 	if ( ent->takedamage )
1210 	{
1211 		for ( other = ent ; other ; other = other->teamchain )
1212 		{
1213 			other->takedamage = qtrue;
1214 		}
1215 	}
1216 
1217 	// find the bounds of everything on the team
1218 	VectorCopy (ent->r.absmin, mins);
1219 	VectorCopy (ent->r.absmax, maxs);
1220 
1221 	for (other = ent->teamchain ; other ; other=other->teamchain) {
1222 		AddPointToBounds (other->r.absmin, mins, maxs);
1223 		AddPointToBounds (other->r.absmax, mins, maxs);
1224 	}
1225 
1226 	// find the thinnest axis, which will be the one we expand
1227 	best = 0;
1228 	for ( i = 1 ; i < 3 ; i++ ) {
1229 		if ( maxs[i] - mins[i] < maxs[best] - mins[best] ) {
1230 			best = i;
1231 		}
1232 	}
1233 	maxs[best] += 120;
1234 	mins[best] -= 120;
1235 
1236 	// create a trigger with this size
1237 	other = G_Spawn ();
1238 	VectorCopy (mins, other->r.mins);
1239 	VectorCopy (maxs, other->r.maxs);
1240 	other->parent = ent;
1241 	other->r.contents = CONTENTS_TRIGGER;
1242 	other->touch = Touch_DoorTrigger;
1243 	trap->LinkEntity ((sharedEntity_t *)other);
1244 	other->classname = "trigger_door";
1245 	// remember the thinnest axis
1246 	other->count = best;
1247 
1248 	MatchTeam( ent, ent->moverState, level.time );
1249 }
1250 
Think_MatchTeam(gentity_t * ent)1251 void Think_MatchTeam( gentity_t *ent )
1252 {
1253 	MatchTeam( ent, ent->moverState, level.time );
1254 }
1255 
G_EntIsDoor(int entityNum)1256 qboolean G_EntIsDoor( int entityNum )
1257 {
1258 	gentity_t *ent;
1259 
1260 	if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD )
1261 	{
1262 		return qfalse;
1263 	}
1264 
1265 	ent = &g_entities[entityNum];
1266 	if ( ent && !Q_stricmp( "func_door", ent->classname ) )
1267 	{//blocked by a door
1268 		return qtrue;
1269 	}
1270 	return qfalse;
1271 }
1272 
G_FindDoorTrigger(gentity_t * ent)1273 gentity_t *G_FindDoorTrigger( gentity_t *ent )
1274 {
1275 	gentity_t *owner = NULL;
1276 	gentity_t *door = ent;
1277 	if ( door->flags & FL_TEAMSLAVE )
1278 	{//not the master door, get the master door
1279 		while ( door->teammaster && (door->flags&FL_TEAMSLAVE))
1280 		{
1281 			door = door->teammaster;
1282 		}
1283 	}
1284 	if ( door->targetname )
1285 	{//find out what is targeting it
1286 		//FIXME: if ent->targetname, check what kind of trigger/ent is targetting it?  If a normal trigger (active, etc), then it's okay?
1287 		while ( (owner = G_Find( owner, FOFS( target ), door->targetname )) != NULL )
1288 		{
1289 			if ( owner && (owner->r.contents&CONTENTS_TRIGGER) )
1290 			{
1291 				return owner;
1292 			}
1293 		}
1294 		owner = NULL;
1295 		while ( (owner = G_Find( owner, FOFS( target2 ), door->targetname )) != NULL )
1296 		{
1297 			if ( owner && (owner->r.contents&CONTENTS_TRIGGER) )
1298 			{
1299 				return owner;
1300 			}
1301 		}
1302 	}
1303 
1304 	owner = NULL;
1305 	while ( (owner = G_Find( owner, FOFS( classname ), "trigger_door" )) != NULL )
1306 	{
1307 		if ( owner->parent == door )
1308 		{
1309 			return owner;
1310 		}
1311 	}
1312 
1313 	return NULL;
1314 }
1315 
G_EntIsUnlockedDoor(int entityNum)1316 qboolean G_EntIsUnlockedDoor( int entityNum )
1317 {
1318 	if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD )
1319 	{
1320 		return qfalse;
1321 	}
1322 
1323 	if ( G_EntIsDoor( entityNum ) )
1324 	{
1325 		gentity_t *ent = &g_entities[entityNum];
1326 		gentity_t *owner = NULL;
1327 		if ( ent->flags & FL_TEAMSLAVE )
1328 		{//not the master door, get the master door
1329 			while ( ent->teammaster && (ent->flags&FL_TEAMSLAVE))
1330 			{
1331 				ent = ent->teammaster;
1332 			}
1333 		}
1334 		if ( ent->targetname )
1335 		{//find out what is targetting it
1336 			owner = NULL;
1337 			//FIXME: if ent->targetname, check what kind of trigger/ent is targetting it?  If a normal trigger (active, etc), then it's okay?
1338 			while ( (owner = G_Find( owner, FOFS( target ), ent->targetname )) != NULL )
1339 			{
1340 				if ( !Q_stricmp( "trigger_multiple", owner->classname ) )//FIXME: other triggers okay too?
1341 				{
1342 					if ( !(owner->flags & FL_INACTIVE) )
1343 					{
1344 						return qtrue;
1345 					}
1346 				}
1347 			}
1348 			owner = NULL;
1349 			while ( (owner = G_Find( owner, FOFS( target2 ), ent->targetname )) != NULL )
1350 			{
1351 				if ( !Q_stricmp( "trigger_multiple", owner->classname ) )//FIXME: other triggers okay too?
1352 				{
1353 					if ( !(owner->flags & FL_INACTIVE) )
1354 					{
1355 						return qtrue;
1356 					}
1357 				}
1358 			}
1359 			return qfalse;
1360 		}
1361 		else
1362 		{//check the door's auto-created trigger instead
1363 			owner = G_FindDoorTrigger( ent );
1364 			if ( owner && (owner->flags&FL_INACTIVE) )
1365 			{//owning auto-created trigger is inactive
1366 				return qfalse;
1367 			}
1368 		}
1369 		if ( !(ent->flags & FL_INACTIVE) && //assumes that the reactivate trigger isn't right next to the door!
1370 			!ent->health &&
1371 			!(ent->spawnflags & MOVER_PLAYER_USE) &&
1372 			!(ent->spawnflags & MOVER_FORCE_ACTIVATE) &&
1373 			!(ent->spawnflags & MOVER_LOCKED))
1374 			//FIXME: what about MOVER_GOODIE?
1375 		{
1376 			return qtrue;
1377 		}
1378 	}
1379 	return qfalse;
1380 }
1381 
1382 
1383 /*QUAKED func_door (0 .5 .8) ? START_OPEN FORCE_ACTIVATE CRUSHER TOGGLE LOCKED x PLAYER_USE INACTIVE
1384 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).
1385 FORCE_ACTIVATE	Can only be activated by a force push or pull
1386 CRUSHER		?
1387 TOGGLE		wait in both the start and end states for a trigger event - does NOT work on Trek doors.
1388 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.
1389 PLAYER_USE	Player can use it with the use button
1390 INACTIVE	must be used by a target_activate before it can be used
1391 
1392 "target"     Door fires this when it starts moving from it's closed position to its open position.
1393 "opentarget" Door fires this after reaching its "open" position
1394 "target2"    Door fires this when it starts moving from it's open position to its closed position.
1395 "closetarget" Door fires this after reaching its "closed" position
1396 "model2"	.md3 model to also draw
1397 "angle"		determines the opening direction
1398 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1399 "speed"		movement speed (100 default)
1400 "wait"		wait before returning (3 default, -1 = never return)
1401 "delay"		when used, how many seconds to wait before moving - default is none
1402 "lip"		lip remaining at end of move (8 default)
1403 "dmg"		damage to inflict when blocked (2 default, set to negative for no damage)
1404 "color"		constantLight color
1405 "light"		constantLight radius
1406 "health"	if set, the door must be shot open
1407 "linear"	set to 1 and it will move linearly rather than with acceleration (default is 0)
1408 "teamallow" even if locked, this team can always open and close it just by walking up to it
1409 	0 - none (locked to everyone)
1410 	1 - red
1411 	2 - blue
1412 "vehopen"	if non-0, vehicles/players riding vehicles can open
1413 */
SP_func_door(gentity_t * ent)1414 void SP_func_door (gentity_t *ent)
1415 {
1416 	vec3_t	abs_movedir;
1417 	float	distance;
1418 	vec3_t	size;
1419 	float	lip;
1420 
1421 	G_SpawnInt("vehopen", "0", &ent->genericValue14);
1422 
1423 	ent->blocked = Blocked_Door;
1424 
1425 	// default speed of 400
1426 	if (!ent->speed)
1427 		ent->speed = 400;
1428 
1429 	// default wait of 2 seconds
1430 	if (!ent->wait)
1431 		ent->wait = 2;
1432 	ent->wait *= 1000;
1433 
1434 	ent->delay *= 1000;
1435 
1436 	// default lip of 8 units
1437 	G_SpawnFloat( "lip", "8", &lip );
1438 
1439 	// default damage of 2 points
1440 	G_SpawnInt( "dmg", "2", &ent->damage );
1441 	if ( ent->damage < 0 )
1442 	{
1443 		ent->damage = 0;
1444 	}
1445 
1446 	G_SpawnInt( "teamallow", "0", &ent->alliedTeam );
1447 
1448 	// first position at start
1449 	VectorCopy( ent->s.origin, ent->pos1 );
1450 
1451 	// calculate second position
1452 	trap->SetBrushModel( (sharedEntity_t *)ent, ent->model );
1453 	G_SetMovedir( ent->s.angles, ent->movedir );
1454 	abs_movedir[0] = fabs( ent->movedir[0] );
1455 	abs_movedir[1] = fabs( ent->movedir[1] );
1456 	abs_movedir[2] = fabs( ent->movedir[2] );
1457 	VectorSubtract( ent->r.maxs, ent->r.mins, size );
1458 	distance = DotProduct( abs_movedir, size ) - lip;
1459 	VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 );
1460 
1461 	// if "start_open", reverse position 1 and 2
1462 	if ( ent->spawnflags & 1 )
1463 	{
1464 		vec3_t	temp;
1465 
1466 		VectorCopy( ent->pos2, temp );
1467 		VectorCopy( ent->s.origin, ent->pos2 );
1468 		VectorCopy( temp, ent->pos1 );
1469 	}
1470 
1471 	if ( ent->spawnflags & MOVER_LOCKED )
1472 	{//a locked door, set up as locked until used directly
1473 		ent->s.eFlags |= EF_SHADER_ANIM;//use frame-controlled shader anim
1474 		ent->s.frame = 0;//first stage of anim
1475 	}
1476 	InitMover( ent );
1477 
1478 	ent->nextthink = level.time + FRAMETIME;
1479 
1480 	if ( !(ent->flags&FL_TEAMSLAVE) )
1481 	{
1482 		int health;
1483 
1484 		G_SpawnInt( "health", "0", &health );
1485 
1486 		if ( health )
1487 		{
1488 			ent->takedamage = qtrue;
1489 		}
1490 
1491 		if ( !(ent->spawnflags&MOVER_LOCKED) && (ent->targetname || health || ent->spawnflags & MOVER_PLAYER_USE || ent->spawnflags & MOVER_FORCE_ACTIVATE) )
1492 		{
1493 			// non touch/shoot doors
1494 			ent->think = Think_MatchTeam;
1495 
1496 			if (ent->spawnflags & MOVER_FORCE_ACTIVATE)
1497 			{ //so we know it's push/pullable on the client
1498 				ent->s.bolt1 = 1;
1499 			}
1500 		}
1501 		else
1502 		{//locked doors still spawn a trigger
1503 			ent->think = Think_SpawnNewDoorTrigger;
1504 		}
1505 	}
1506 }
1507 
1508 /*
1509 ===============================================================================
1510 
1511 PLAT
1512 
1513 ===============================================================================
1514 */
1515 
1516 /*
1517 ==============
1518 Touch_Plat
1519 
1520 Don't allow descent if a living player is on it
1521 ===============
1522 */
Touch_Plat(gentity_t * ent,gentity_t * other,trace_t * trace)1523 void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) {
1524 	if ( !other->client || other->client->ps.stats[STAT_HEALTH] <= 0 ) {
1525 		return;
1526 	}
1527 
1528 	// delay return-to-pos1 by one second
1529 	if ( ent->moverState == MOVER_POS2 ) {
1530 		ent->nextthink = level.time + 1000;
1531 	}
1532 }
1533 
1534 /*
1535 ==============
1536 Touch_PlatCenterTrigger
1537 
1538 If the plat is at the bottom position, start it going up
1539 ===============
1540 */
Touch_PlatCenterTrigger(gentity_t * ent,gentity_t * other,trace_t * trace)1541 void Touch_PlatCenterTrigger(gentity_t *ent, gentity_t *other, trace_t *trace ) {
1542 	if ( !other->client ) {
1543 		return;
1544 	}
1545 
1546 	if ( ent->parent->moverState == MOVER_POS1 ) {
1547 		Use_BinaryMover( ent->parent, ent, other );
1548 	}
1549 }
1550 
1551 
1552 /*
1553 ================
1554 SpawnPlatTrigger
1555 
1556 Spawn a trigger in the middle of the plat's low position
1557 Elevator cars require that the trigger extend through the entire low position,
1558 not just sit on top of it.
1559 ================
1560 */
SpawnPlatTrigger(gentity_t * ent)1561 void SpawnPlatTrigger( gentity_t *ent ) {
1562 	gentity_t	*trigger;
1563 	vec3_t	tmin, tmax;
1564 
1565 	// the middle trigger will be a thin trigger just
1566 	// above the starting position
1567 	trigger = G_Spawn();
1568 	trigger->touch = Touch_PlatCenterTrigger;
1569 	trigger->r.contents = CONTENTS_TRIGGER;
1570 	trigger->parent = ent;
1571 
1572 	tmin[0] = ent->pos1[0] + ent->r.mins[0] + 33;
1573 	tmin[1] = ent->pos1[1] + ent->r.mins[1] + 33;
1574 	tmin[2] = ent->pos1[2] + ent->r.mins[2];
1575 
1576 	tmax[0] = ent->pos1[0] + ent->r.maxs[0] - 33;
1577 	tmax[1] = ent->pos1[1] + ent->r.maxs[1] - 33;
1578 	tmax[2] = ent->pos1[2] + ent->r.maxs[2] + 8;
1579 
1580 	if ( tmax[0] <= tmin[0] ) {
1581 		tmin[0] = ent->pos1[0] + (ent->r.mins[0] + ent->r.maxs[0]) *0.5;
1582 		tmax[0] = tmin[0] + 1;
1583 	}
1584 	if ( tmax[1] <= tmin[1] ) {
1585 		tmin[1] = ent->pos1[1] + (ent->r.mins[1] + ent->r.maxs[1]) *0.5;
1586 		tmax[1] = tmin[1] + 1;
1587 	}
1588 
1589 	VectorCopy (tmin, trigger->r.mins);
1590 	VectorCopy (tmax, trigger->r.maxs);
1591 
1592 	trap->LinkEntity ((sharedEntity_t *)trigger);
1593 }
1594 
1595 
1596 /*QUAKED func_plat (0 .5 .8) ? x x x x x x PLAYER_USE INACTIVE
1597 PLAYER_USE	Player can use it with the use button
1598 INACTIVE	must be used by a target_activate before it can be used
1599 
1600 Plats are always drawn in the extended position so they will light correctly.
1601 
1602 "lip"		default 8, protrusion above rest position
1603 "height"	total height of movement, defaults to model height
1604 "speed"		overrides default 200.
1605 "dmg"		overrides default 2
1606 "model2"	.md3 model to also draw
1607 "color"		constantLight color
1608 "light"		constantLight radius
1609 */
SP_func_plat(gentity_t * ent)1610 void SP_func_plat (gentity_t *ent) {
1611 	float		lip, height;
1612 
1613 //	ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/plats/pt1_strt.wav");
1614 //	ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/plats/pt1_end.wav");
1615 
1616 	VectorClear (ent->s.angles);
1617 
1618 	G_SpawnFloat( "speed", "200", &ent->speed );
1619 	G_SpawnInt( "dmg", "2", &ent->damage );
1620 	G_SpawnFloat( "wait", "1", &ent->wait );
1621 	G_SpawnFloat( "lip", "8", &lip );
1622 
1623 	ent->wait = 1000;
1624 
1625 	// create second position
1626 	trap->SetBrushModel( (sharedEntity_t *)ent, ent->model );
1627 
1628 	if ( !G_SpawnFloat( "height", "0", &height ) ) {
1629 		height = (ent->r.maxs[2] - ent->r.mins[2]) - lip;
1630 	}
1631 
1632 	// pos1 is the rest (bottom) position, pos2 is the top
1633 	VectorCopy( ent->s.origin, ent->pos2 );
1634 	VectorCopy( ent->pos2, ent->pos1 );
1635 	ent->pos1[2] -= height;
1636 
1637 	InitMover( ent );
1638 
1639 	// touch function keeps the plat from returning while
1640 	// a live player is standing on it
1641 	ent->touch = Touch_Plat;
1642 
1643 	ent->blocked = Blocked_Door;
1644 
1645 	ent->parent = ent;	// so it can be treated as a door
1646 
1647 	// spawn the trigger if one hasn't been custom made
1648 	if ( !ent->targetname ) {
1649 		SpawnPlatTrigger(ent);
1650 	}
1651 }
1652 
1653 /*
1654 ===============================================================================
1655 
1656 BUTTON
1657 
1658 ===============================================================================
1659 */
1660 
1661 /*
1662 ==============
1663 Touch_Button
1664 
1665 ===============
1666 */
Touch_Button(gentity_t * ent,gentity_t * other,trace_t * trace)1667 void Touch_Button(gentity_t *ent, gentity_t *other, trace_t *trace ) {
1668 	if ( !other->client ) {
1669 		return;
1670 	}
1671 
1672 	if ( ent->moverState == MOVER_POS1 ) {
1673 		Use_BinaryMover( ent, other, other );
1674 	}
1675 }
1676 
1677 
1678 /*QUAKED func_button (0 .5 .8) ? x x x x x x PLAYER_USE INACTIVE
1679 PLAYER_USE	Player can use it with the use button
1680 INACTIVE	must be used by a target_activate before it can be used
1681 
1682 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.
1683 
1684 "model2"	.md3 model to also draw
1685 "angle"		determines the opening direction
1686 "target"	all entities with a matching targetname will be used
1687 "speed"		override the default 40 speed
1688 "wait"		override the default 1 second wait (-1 = never return)
1689 "lip"		override the default 4 pixel lip remaining at end of move
1690 "health"	if set, the button must be killed instead of touched
1691 "color"		constantLight color
1692 "light"		constantLight radius
1693 */
SP_func_button(gentity_t * ent)1694 void SP_func_button( gentity_t *ent ) {
1695 	vec3_t		abs_movedir;
1696 	float		distance;
1697 	vec3_t		size;
1698 	float		lip;
1699 
1700 //	ent->sound1to2 = G_SoundIndex("sound/movers/switches/butn2.wav");
1701 
1702 	if ( !ent->speed ) {
1703 		ent->speed = 40;
1704 	}
1705 
1706 	if ( !ent->wait ) {
1707 		ent->wait = 1;
1708 	}
1709 	ent->wait *= 1000;
1710 
1711 	// first position
1712 	VectorCopy( ent->s.origin, ent->pos1 );
1713 
1714 	// calculate second position
1715 	trap->SetBrushModel( (sharedEntity_t *)ent, ent->model );
1716 
1717 	G_SpawnFloat( "lip", "4", &lip );
1718 
1719 	G_SetMovedir( ent->s.angles, ent->movedir );
1720 	abs_movedir[0] = fabs(ent->movedir[0]);
1721 	abs_movedir[1] = fabs(ent->movedir[1]);
1722 	abs_movedir[2] = fabs(ent->movedir[2]);
1723 	VectorSubtract( ent->r.maxs, ent->r.mins, size );
1724 	distance = abs_movedir[0] * size[0] + abs_movedir[1] * size[1] + abs_movedir[2] * size[2] - lip;
1725 	VectorMA (ent->pos1, distance, ent->movedir, ent->pos2);
1726 
1727 	if (ent->health) {
1728 		// shootable button
1729 		ent->takedamage = qtrue;
1730 	} else {
1731 		// touchable button
1732 		ent->touch = Touch_Button;
1733 	}
1734 
1735 	InitMover( ent );
1736 }
1737 
1738 
1739 
1740 
1741 /*
1742 ===============================================================================
1743 
1744 TRAIN
1745 
1746 ===============================================================================
1747 */
1748 
1749 
1750 #define TRAIN_START_ON		1
1751 #define TRAIN_TOGGLE		2
1752 #define TRAIN_BLOCK_STOPS	4
1753 
1754 /*
1755 ===============
1756 Think_BeginMoving
1757 
1758 The wait time at a corner has completed, so start moving again
1759 ===============
1760 */
Think_BeginMoving(gentity_t * ent)1761 void Think_BeginMoving( gentity_t *ent ) {
1762 	G_PlayDoorSound( ent, BMS_START );
1763 	G_PlayDoorLoopSound( ent );
1764 	ent->s.pos.trTime = level.time;
1765 	ent->s.pos.trType = TR_LINEAR_STOP;
1766 }
1767 
1768 /*
1769 ===============
1770 Reached_Train
1771 ===============
1772 */
Reached_Train(gentity_t * ent)1773 void Reached_Train( gentity_t *ent ) {
1774 	gentity_t		*next;
1775 	float			speed;
1776 	vec3_t			move;
1777 	float			length;
1778 
1779 	// copy the apropriate values
1780 	next = ent->nextTrain;
1781 	if ( !next || !next->nextTrain ) {
1782 		return;		// just stop
1783 	}
1784 
1785 	// fire all other targets
1786 	G_UseTargets( next, NULL );
1787 
1788 	// set the new trajectory
1789 	ent->nextTrain = next->nextTrain;
1790 	VectorCopy( next->s.origin, ent->pos1 );
1791 	VectorCopy( next->nextTrain->s.origin, ent->pos2 );
1792 
1793 	// if the path_corner has a speed, use that
1794 	if ( next->speed ) {
1795 		speed = next->speed;
1796 	} else {
1797 		// otherwise use the train's speed
1798 		speed = ent->speed;
1799 	}
1800 	if ( speed < 1 ) {
1801 		speed = 1;
1802 	}
1803 
1804 	// calculate duration
1805 	VectorSubtract( ent->pos2, ent->pos1, move );
1806 	length = VectorLength( move );
1807 
1808 	ent->s.pos.trDuration = length * 1000 / speed;
1809 
1810 	// start it going
1811 	SetMoverState( ent, MOVER_1TO2, level.time );
1812 
1813 	G_PlayDoorSound( ent, BMS_END );
1814 
1815 	// if there is a "wait" value on the target, don't start moving yet
1816 	if ( next->wait ) {
1817 		ent->s.loopSound = 0;
1818 		ent->s.loopIsSoundset = qfalse;
1819 		ent->nextthink = level.time + next->wait * 1000;
1820 		ent->think = Think_BeginMoving;
1821 		ent->s.pos.trType = TR_STATIONARY;
1822 	}
1823 	else
1824 	{
1825 		G_PlayDoorLoopSound( ent );
1826 	}
1827 }
1828 
1829 /*
1830 ===============
1831 Think_SetupTrainTargets
1832 
1833 Link all the corners together
1834 ===============
1835 */
Think_SetupTrainTargets(gentity_t * ent)1836 void Think_SetupTrainTargets( gentity_t *ent ) {
1837 	gentity_t		*path, *next, *start;
1838 
1839 	ent->nextTrain = G_Find( NULL, FOFS(targetname), ent->target );
1840 	if ( !ent->nextTrain ) {
1841 		Com_Printf( "func_train at %s with an unfound target\n",
1842 			vtos(ent->r.absmin) );
1843 		//Free me?`
1844 		return;
1845 	}
1846 
1847 	//FIXME: this can go into an infinite loop if last path_corner doesn't link to first
1848 	//path_corner, like so:
1849 	// t1---->t2---->t3
1850 	//         ^      |
1851 	//          \_____|
1852 	start = NULL;
1853 	for ( path = ent->nextTrain ; path != start ; path = next ) {
1854 		if ( !start ) {
1855 			start = path;
1856 		}
1857 
1858 		if ( !path->target ) {
1859 //			trap->Printf( "Train corner at %s without a target\n",
1860 //				vtos(path->s.origin) );
1861 			//end of path
1862 			break;
1863 		}
1864 
1865 		// find a path_corner among the targets
1866 		// there may also be other targets that get fired when the corner
1867 		// is reached
1868 		next = NULL;
1869 		do {
1870 			next = G_Find( next, FOFS(targetname), path->target );
1871 			if ( !next ) {
1872 //				trap->Printf( "Train corner at %s without a target path_corner\n",
1873 //					vtos(path->s.origin) );
1874 				//end of path
1875 				break;
1876 			}
1877 		} while ( strcmp( next->classname, "path_corner" ) );
1878 
1879 		if ( next )
1880 		{
1881 			path->nextTrain = next;
1882 		}
1883 		else
1884 		{
1885 			break;
1886 		}
1887 	}
1888 
1889 	if ( !ent->targetname || (ent->spawnflags&1) /*start on*/)
1890 	{
1891 		// start the train moving from the first corner
1892 		Reached_Train( ent );
1893 	}
1894 	else
1895 	{
1896 		G_SetOrigin( ent, ent->s.origin );
1897 	}
1898 }
1899 
1900 
1901 /*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8)
1902 Train path corners.
1903 Target: next path corner and other targets to fire
1904 "speed" speed to move to the next corner
1905 "wait" seconds to wait before behining move to next corner
1906 */
SP_path_corner(gentity_t * self)1907 void SP_path_corner( gentity_t *self ) {
1908 	if ( !self->targetname ) {
1909 		trap->Print ("path_corner with no targetname at %s\n", vtos(self->s.origin));
1910 		G_FreeEntity( self );
1911 		return;
1912 	}
1913 	// path corners don't need to be linked in
1914 }
1915 
1916 
1917 
1918 /*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS ? ? CRUSH_THROUGH PLAYER_USE INACTIVE
1919 A train is a mover that moves between path_corner target points.
1920 Trains MUST HAVE AN ORIGIN BRUSH.
1921 The train spawns at the first target it is pointing at.
1922 CRUSH_THROUGH spawnflag combined with a dmg value will make the train pass through
1923 entities and damage them on contact as well.
1924 "model2"	.md3 model to also draw
1925 "speed"		default 100
1926 "dmg"		default	2
1927 "target"	next path corner
1928 "color"		constantLight color
1929 "light"		constantLight radius
1930 */
SP_func_train(gentity_t * self)1931 void SP_func_train (gentity_t *self) {
1932 	VectorClear (self->s.angles);
1933 
1934 	if (self->spawnflags & TRAIN_BLOCK_STOPS) {
1935 		self->damage = 0;
1936 	} else {
1937 		if (!self->damage) {
1938 			self->damage = 2;
1939 		}
1940 	}
1941 
1942 	if ( !self->speed ) {
1943 		self->speed = 100;
1944 	}
1945 
1946 	if ( !self->target ) {
1947 		trap->Print ("func_train without a target at %s\n", vtos(self->r.absmin));
1948 		G_FreeEntity( self );
1949 		return;
1950 	}
1951 
1952 	trap->SetBrushModel( (sharedEntity_t *)self, self->model );
1953 	InitMover( self );
1954 
1955 	self->reached = Reached_Train;
1956 
1957 	// start trains on the second frame, to make sure their targets have had
1958 	// a chance to spawn
1959 	self->nextthink = level.time + FRAMETIME;
1960 	self->think = Think_SetupTrainTargets;
1961 }
1962 
1963 /*
1964 ===============================================================================
1965 
1966 STATIC
1967 
1968 ===============================================================================
1969 */
1970 
1971 void func_static_use ( gentity_t *self, gentity_t *other, gentity_t *activator );
1972 /*QUAKED func_static (0 .5 .8) ? F_PUSH F_PULL SWITCH_SHADER CRUSHER IMPACT x PLAYER_USE INACTIVE BROADCAST
1973 F_PUSH		Will be used when you Force-Push it
1974 F_PULL		Will be used when you Force-Pull it
1975 SWITCH_SHADER	Toggle the shader anim frame between 1 and 2 when used
1976 CRUSHER		Make it do damage when it's blocked
1977 IMPACT		Make it do damage when it hits any entity
1978 PLAYER_USE	Player can use it with the use button
1979 INACTIVE	must be used by a target_activate before it can be used
1980 BROADCAST   don't ever use this, it's evil
1981 
1982 A bmodel that just sits there, doing nothing.  Can be used for conditional walls and models.
1983 "model2"	.md3 model to also draw
1984 "model2scale"	percent of normal scale (on all x y and z axii) to scale the model2 if there is one. 100 is normal scale, min is 1 (100 times smaller than normal), max is 1000 (ten times normal).
1985 "color"		constantLight color
1986 "light"		constantLight radius
1987 "dmg"		how much damage to do when it crushes (use with CRUSHER spawnflag)
1988 "linear" set to 1 and it will move linearly rather than with acceleration (default is 0)
1989 */
SP_func_static(gentity_t * ent)1990 void SP_func_static( gentity_t *ent )
1991 {
1992 	int		test;
1993 	trap->SetBrushModel( (sharedEntity_t *)ent, ent->model );
1994 
1995 	VectorCopy( ent->s.origin, ent->pos1 );
1996 	VectorCopy( ent->s.origin, ent->pos2 );
1997 
1998 	InitMover( ent );
1999 
2000 	ent->use = func_static_use;
2001 	ent->reached = 0;
2002 
2003 	G_SetOrigin( ent, ent->s.origin );
2004 	G_SetAngles( ent, ent->s.angles );
2005 
2006 	if( ent->spawnflags & 2048 )
2007 	{								   // yes this is very very evil, but for now (pre-alpha) it's a solution
2008 		ent->r.svFlags |= SVF_BROADCAST; // I need to rotate something that is huge and it's touching too many area portals...
2009 	}
2010 
2011 	if ( ent->spawnflags & 4/*SWITCH_SHADER*/ )
2012 	{
2013 		ent->s.eFlags |= EF_SHADER_ANIM;//use frame-controlled shader anim
2014 		ent->s.frame = 0;//first stage of anim
2015 	}
2016 
2017 	if ((ent->spawnflags & 1) || (ent->spawnflags & 2))
2018 	{ //so we know it's push/pullable on the client
2019 		ent->s.bolt1 = 1;
2020 	}
2021 
2022 	G_SpawnInt("model2scale", "0", &ent->s.iModelScale);
2023 	if (ent->s.iModelScale < 0)
2024 	{
2025 		ent->s.iModelScale = 0;
2026 	}
2027 	else if (ent->s.iModelScale > 1023)
2028 	{
2029 		ent->s.iModelScale = 1023;
2030 	}
2031 
2032 	G_SpawnInt( "hyperspace", "0", &test );
2033 	if ( test )
2034 	{
2035 		ent->r.svFlags |= SVF_BROADCAST; // I need to rotate something that is huge and it's touching too many area portals...
2036 		ent->s.eFlags2 |= EF2_HYPERSPACE;
2037 	}
2038 
2039 	trap->LinkEntity( (sharedEntity_t *)ent );
2040 
2041 	if (level.mBSPInstanceDepth)
2042 	{	// this means that this guy will never be updated, moved, changed, etc.
2043 		ent->s.eFlags = EF_PERMANENT;
2044 	}
2045 }
2046 
func_static_use(gentity_t * self,gentity_t * other,gentity_t * activator)2047 void func_static_use ( gentity_t *self, gentity_t *other, gentity_t *activator )
2048 {
2049 	G_ActivateBehavior( self, BSET_USE );
2050 
2051 	if ( self->spawnflags & 4/*SWITCH_SHADER*/ )
2052 	{
2053 		self->s.frame = self->s.frame ? 0 : 1;//toggle frame
2054 	}
2055 	G_UseTargets( self, activator );
2056 }
2057 
2058 /*
2059 ===============================================================================
2060 
2061 ROTATING
2062 
2063 ===============================================================================
2064 */
2065 
func_rotating_use(gentity_t * self,gentity_t * other,gentity_t * activator)2066 void func_rotating_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2067 {
2068 	if(	self->s.apos.trType == TR_LINEAR )
2069 	{
2070 		self->s.apos.trType = TR_STATIONARY;
2071 		// stop the sound if it stops moving
2072 		self->s.loopSound = 0;
2073 		self->s.loopIsSoundset = qfalse;
2074 		// play stop sound too?
2075 		if ( self->soundSet && self->soundSet[0] )
2076 		{
2077 			self->s.soundSetIndex = G_SoundSetIndex(self->soundSet);
2078 			G_AddEvent( self, EV_BMODEL_SOUND, BMS_END );
2079 		}
2080 	}
2081 	else
2082 	{
2083 		if ( self->soundSet && self->soundSet[0] )
2084 		{
2085 			self->s.soundSetIndex = G_SoundSetIndex(self->soundSet);
2086 			G_AddEvent( self, EV_BMODEL_SOUND, BMS_START );
2087 			self->s.loopSound = BMS_MID;
2088 			self->s.loopIsSoundset = qtrue;
2089 		}
2090 		self->s.apos.trType = TR_LINEAR;
2091 	}
2092 }
2093 
2094 /*QUAKED func_rotating (0 .5 .8) ? START_ON RADAR X_AXIS Y_AXIS IMPACT x PLAYER_USE INACTIVE
2095 RADAR - shows up on Radar in Siege mode (good for asteroids when you're in a ship)
2096 IMPACT - Kills anything on impact when rotating (10000 points of damage, unless specified otherwise)
2097 
2098 You need to have an origin brush as part of this entity.  The center of that brush will be
2099 the point around which it is rotated. It will rotate around the Z axis by default.  You can
2100 check either the X_AXIS or Y_AXIS box to change that.
2101 
2102 "model2"		.md3 model to also draw
2103 "model2scale"	percent of normal scale (on all x y and z axii) to scale the model2 if there is one. 100 is normal scale, min is 1 (100 times smaller than normal), max is 1000 (ten times normal).
2104 "speed"			determines how fast it moves; default value is 100.
2105 "dmg"			damage to inflict when blocked (2 default)
2106 "color"			constantLight color
2107 "light"			constantLight radius
2108 "spinangles"	instead of using "speed", you can set use this to set rotation on all 3 axes (pitch yaw and roll)
2109 
2110 ==================================================
2111 "health"	default is 0
2112 
2113 If health is set, the following key/values are available:
2114 
2115 "numchunks" Multiplies the number of chunks spawned.  Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1)  (.5) is half as many chunks, (2) is twice as many chunks
2116 "chunksize"	scales up/down the chunk size by this number (default is 1)
2117 
2118 "showhealth" if non-0, will display the health bar on the hud when the crosshair is over this ent (in siege)
2119 "teamowner" in siege this will specify which team this thing is "owned" by. To that team the crosshair will
2120 highlight green, to the other team it will highlight red.
2121 
2122 Damage: default is none
2123 "splashDamage" - damage to do
2124 "splashRadius" - radius for above damage
2125 
2126 "team" - If set, only this team can trip this trigger
2127 	0 - any
2128 	1 - red
2129 	2 - blue
2130 
2131 Don't know if these work:
2132 "color"		constantLight color
2133 "light"		constantLight radius
2134 
2135 "material" - default is "0 - MAT_METAL" - choose from this list:
2136 0 = MAT_METAL		(basic blue-grey scorched-DEFAULT)
2137 1 = MAT_GLASS
2138 2 = MAT_ELECTRICAL	(sparks only)
2139 3 = MAT_ELEC_METAL	(METAL2 chunks and sparks)
2140 4 =	MAT_DRK_STONE	(brown stone chunks)
2141 5 =	MAT_LT_STONE	(tan stone chunks)
2142 6 =	MAT_GLASS_METAL (glass and METAL2 chunks)
2143 7 = MAT_METAL2		(electronic type of metal)
2144 8 = MAT_NONE		(no chunks)
2145 9 = MAT_GREY_STONE	(grey colored stone)
2146 10 = MAT_METAL3		(METAL and METAL2 chunk combo)
2147 11 = MAT_CRATE1		(yellow multi-colored crate chunks)
2148 12 = MAT_GRATE1		(grate chunks--looks horrible right now)
2149 13 = MAT_ROPE		(for yavin_trial, no chunks, just wispy bits )
2150 14 = MAT_CRATE2		(red multi-colored crate chunks)
2151 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )
2152 16 = MAT_SNOWY_ROCK	 (mix of gray and brown rocks)
2153 
2154 Applicable only during Siege gametype:
2155 teamnodmg - if 1, team 1 can't damage this. If 2, team 2 can't damage this.
2156 
2157 */
2158 void SP_func_breakable( gentity_t *self );
SP_func_rotating(gentity_t * ent)2159 void SP_func_rotating (gentity_t *ent) {
2160 	vec3_t spinangles;
2161 	if ( ent->health )
2162 	{
2163 		int sav_spawnflags = ent->spawnflags;
2164 		ent->spawnflags = 0;
2165 		SP_func_breakable( ent );
2166 		ent->spawnflags = sav_spawnflags;
2167 	}
2168 	else
2169 	{
2170 		trap->SetBrushModel( (sharedEntity_t *)ent, ent->model );
2171 		InitMover( ent );
2172 
2173 		VectorCopy( ent->s.origin, ent->s.pos.trBase );
2174 		VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
2175 		VectorCopy( ent->s.apos.trBase, ent->r.currentAngles );
2176 
2177 		trap->LinkEntity( (sharedEntity_t *)ent );
2178 	}
2179 
2180 	G_SpawnInt("model2scale", "0", &ent->s.iModelScale);
2181 	if (ent->s.iModelScale < 0)
2182 	{
2183 		ent->s.iModelScale = 0;
2184 	}
2185 	else if (ent->s.iModelScale > 1023)
2186 	{
2187 		ent->s.iModelScale = 1023;
2188 	}
2189 
2190 	if ( G_SpawnVector( "spinangles", "0 0 0", spinangles ) )
2191 	{
2192 		ent->speed = VectorLength( spinangles );
2193 		// set the axis of rotation
2194 		VectorCopy( spinangles, ent->s.apos.trDelta );
2195 	}
2196 	else
2197 	{
2198 		if ( !ent->speed ) {
2199 			ent->speed = 100;
2200 		}
2201 		// set the axis of rotation
2202 		if ( ent->spawnflags & 4 ) {
2203 			ent->s.apos.trDelta[2] = ent->speed;
2204 		} else if ( ent->spawnflags & 8 ) {
2205 			ent->s.apos.trDelta[0] = ent->speed;
2206 		} else {
2207 			ent->s.apos.trDelta[1] = ent->speed;
2208 		}
2209 	}
2210 	ent->s.apos.trType = TR_LINEAR;
2211 
2212 	if (!ent->damage) {
2213 		if ( (ent->spawnflags&16) )//IMPACT
2214 		{
2215 			ent->damage = 10000;
2216 		}
2217 		else
2218 		{
2219 			ent->damage = 2;
2220 		}
2221 	}
2222 	if ( (ent->spawnflags&2) )//RADAR
2223 	{//show up on Radar at close range and play impact sound when close...?  Range based on my size
2224 		ent->s.speed = Distance( ent->r.absmin, ent->r.absmax )*0.5f;
2225 		ent->s.eFlags |= EF_RADAROBJECT;
2226 	}
2227 }
2228 
2229 
2230 /*
2231 ===============================================================================
2232 
2233 BOBBING
2234 
2235 ===============================================================================
2236 */
2237 
2238 
2239 /*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS x x x x PLAYER_USE INACTIVE
2240 Normally bobs on the Z axis
2241 "model2"	.md3 model to also draw
2242 "height"	amplitude of bob (32 default)
2243 "speed"		seconds to complete a bob cycle (4 default)
2244 "phase"		the 0.0 to 1.0 offset in the cycle to start at
2245 "dmg"		damage to inflict when blocked (2 default)
2246 "color"		constantLight color
2247 "light"		constantLight radius
2248 */
SP_func_bobbing(gentity_t * ent)2249 void SP_func_bobbing (gentity_t *ent) {
2250 	float		height;
2251 	float		phase;
2252 
2253 	G_SpawnFloat( "speed", "4", &ent->speed );
2254 	G_SpawnFloat( "height", "32", &height );
2255 	G_SpawnInt( "dmg", "2", &ent->damage );
2256 	G_SpawnFloat( "phase", "0", &phase );
2257 
2258 	trap->SetBrushModel( (sharedEntity_t *)ent, ent->model );
2259 	InitMover( ent );
2260 
2261 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
2262 	VectorCopy( ent->s.origin, ent->r.currentOrigin );
2263 
2264 	ent->s.pos.trDuration = ent->speed * 1000;
2265 	ent->s.pos.trTime = ent->s.pos.trDuration * phase;
2266 	ent->s.pos.trType = TR_SINE;
2267 
2268 	// set the axis of bobbing
2269 	if ( ent->spawnflags & 1 ) {
2270 		ent->s.pos.trDelta[0] = height;
2271 	} else if ( ent->spawnflags & 2 ) {
2272 		ent->s.pos.trDelta[1] = height;
2273 	} else {
2274 		ent->s.pos.trDelta[2] = height;
2275 	}
2276 }
2277 
2278 /*
2279 ===============================================================================
2280 
2281 PENDULUM
2282 
2283 ===============================================================================
2284 */
2285 
2286 
2287 /*QUAKED func_pendulum (0 .5 .8) ? x x x x x x PLAYER_USE INACTIVE
2288 You need to have an origin brush as part of this entity.
2289 Pendulums always swing north / south on unrotated models.  Add an angles field to the model to allow rotation in other directions.
2290 Pendulum frequency is a physical constant based on the length of the beam and gravity.
2291 "model2"	.md3 model to also draw
2292 "speed"		the number of degrees each way the pendulum swings, (30 default)
2293 "phase"		the 0.0 to 1.0 offset in the cycle to start at
2294 "dmg"		damage to inflict when blocked (2 default)
2295 "color"		constantLight color
2296 "light"		constantLight radius
2297 */
SP_func_pendulum(gentity_t * ent)2298 void SP_func_pendulum(gentity_t *ent) {
2299 	float		freq;
2300 	float		length;
2301 	float		phase;
2302 	float		speed;
2303 
2304 	G_SpawnFloat( "speed", "30", &speed );
2305 	G_SpawnInt( "dmg", "2", &ent->damage );
2306 	G_SpawnFloat( "phase", "0", &phase );
2307 
2308 	trap->SetBrushModel( (sharedEntity_t *)ent, ent->model );
2309 
2310 	// find pendulum length
2311 	length = fabs( ent->r.mins[2] );
2312 	if ( length < 8 ) {
2313 		length = 8;
2314 	}
2315 
2316 	freq = 1 / ( M_PI * 2 ) * sqrt( g_gravity.value / ( 3 * length ) );
2317 
2318 	ent->s.pos.trDuration = ( 1000 / freq );
2319 
2320 	InitMover( ent );
2321 
2322 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
2323 	VectorCopy( ent->s.origin, ent->r.currentOrigin );
2324 
2325 	VectorCopy( ent->s.angles, ent->s.apos.trBase );
2326 
2327 	ent->s.apos.trDuration = 1000 / freq;
2328 	ent->s.apos.trTime = ent->s.apos.trDuration * phase;
2329 	ent->s.apos.trType = TR_SINE;
2330 	ent->s.apos.trDelta[2] = speed;
2331 }
2332 
2333 /*
2334 ===============================================================================
2335 
2336 BREAKABLE BRUSH
2337 
2338 ===============================================================================
2339 */
2340 //---------------------------------------------------
CacheChunkEffects(material_t material)2341 static void CacheChunkEffects( material_t material )
2342 {
2343 	switch( material )
2344 	{
2345 	case MAT_GLASS:
2346 		G_EffectIndex( "chunks/glassbreak" );
2347 		break;
2348 	case MAT_GLASS_METAL:
2349 		G_EffectIndex( "chunks/glassbreak" );
2350 		G_EffectIndex( "chunks/metalexplode" );
2351 		break;
2352 	case MAT_ELECTRICAL:
2353 	case MAT_ELEC_METAL:
2354 		G_EffectIndex( "chunks/sparkexplode" );
2355 		break;
2356 	case MAT_METAL:
2357 	case MAT_METAL2:
2358 	case MAT_METAL3:
2359 	case MAT_CRATE1:
2360 	case MAT_CRATE2:
2361 		G_EffectIndex( "chunks/metalexplode" );
2362 		break;
2363 	case MAT_GRATE1:
2364 		G_EffectIndex( "chunks/grateexplode" );
2365 		break;
2366 	case MAT_DRK_STONE:
2367 	case MAT_LT_STONE:
2368 	case MAT_GREY_STONE:
2369 	case MAT_WHITE_METAL: // what is this crap really supposed to be??
2370 	case MAT_SNOWY_ROCK:
2371 		G_EffectIndex( "chunks/rockbreaklg" );
2372 		G_EffectIndex( "chunks/rockbreakmed" );
2373 		break;
2374 	case MAT_ROPE:
2375 		G_EffectIndex( "chunks/ropebreak" );
2376 //		G_SoundIndex(); // FIXME: give it a sound
2377 		break;
2378 	default:
2379 		break;
2380 	}
2381 }
2382 
G_MiscModelExplosion(vec3_t mins,vec3_t maxs,int size,material_t chunkType)2383 void G_MiscModelExplosion( vec3_t mins, vec3_t maxs, int size, material_t chunkType )
2384 {
2385 	gentity_t *te;
2386 	vec3_t mid;
2387 
2388 	VectorAdd( mins, maxs, mid );
2389 	VectorScale( mid, 0.5f, mid );
2390 
2391 	te = G_TempEntity( mid, EV_MISC_MODEL_EXP );
2392 
2393 	VectorCopy(maxs, te->s.origin2);
2394 	VectorCopy(mins, te->s.angles2);
2395 	te->s.time = size;
2396 	te->s.eventParm = chunkType;
2397 }
2398 
G_Chunks(int owner,vec3_t origin,const vec3_t normal,const vec3_t mins,const vec3_t maxs,float speed,int numChunks,material_t chunkType,int customChunk,float baseScale)2399 void G_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs,
2400 						float speed, int numChunks, material_t chunkType, int customChunk, float baseScale )
2401 {
2402 	gentity_t *te = G_TempEntity( origin, EV_DEBRIS );
2403 
2404 	//Now it's time to cram everything horribly into the entitystate of an event entity.
2405 	te->s.owner = owner;
2406 	VectorCopy(origin, te->s.origin);
2407 	VectorCopy(normal, te->s.angles);
2408 	VectorCopy(maxs, te->s.origin2);
2409 	VectorCopy(mins, te->s.angles2);
2410 	te->s.speed = speed;
2411 	te->s.eventParm = numChunks;
2412 	te->s.trickedentindex = chunkType;
2413 	te->s.modelindex = customChunk;
2414 	te->s.apos.trBase[0] = baseScale;
2415 }
2416 
2417 //--------------------------------------
funcBBrushDieGo(gentity_t * self)2418 void funcBBrushDieGo (gentity_t *self)
2419 {
2420 	vec3_t		org, dir, up;
2421 	gentity_t	*attacker = self->enemy;
2422 	float		scale;
2423 	int			i, numChunks, size = 0;
2424 	material_t	chunkType = self->material;
2425 
2426 	// if a missile is stuck to us, blow it up so we don't look dumb
2427 	for ( i = 0; i < MAX_GENTITIES; i++ )
2428 	{
2429 		if ( g_entities[i].s.groundEntityNum == self->s.number && ( g_entities[i].s.eFlags & EF_MISSILE_STICK ))
2430 		{
2431 			G_Damage( &g_entities[i], self, self, NULL, NULL, 99999, 0, MOD_CRUSH ); //?? MOD?
2432 		}
2433 	}
2434 
2435 	//So chunks don't get stuck inside me
2436 	self->s.solid = 0;
2437 	self->r.contents = 0;
2438 	self->clipmask = 0;
2439 	trap->LinkEntity((sharedEntity_t *)self);
2440 
2441 	VectorSet(up, 0, 0, 1);
2442 
2443 	if ( self->target && attacker != NULL )
2444 	{
2445 		G_UseTargets(self, attacker);
2446 	}
2447 
2448 	VectorSubtract( self->r.absmax, self->r.absmin, org );// size
2449 
2450 	numChunks = Q_flrand(0.0f, 1.0f) * 6 + 18;
2451 
2452 	// This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted.
2453 	// Volume is length * width * height...then break that volume down based on how many chunks we have
2454 	scale = sqrt( sqrt( org[0] * org[1] * org[2] )) * 1.75f;
2455 
2456 	if ( scale > 48 )
2457 	{
2458 		size = 2;
2459 	}
2460 	else if ( scale > 24 )
2461 	{
2462 		size = 1;
2463 	}
2464 
2465 	scale = scale / numChunks;
2466 
2467 	if ( self->radius > 0.0f )
2468 	{
2469 		// designer wants to scale number of chunks, helpful because the above scale code is far from perfect
2470 		//	I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak
2471 		numChunks *= self->radius;
2472 	}
2473 
2474 	VectorMA( self->r.absmin, 0.5, org, org );
2475 	VectorAdd( self->r.absmin,self->r.absmax, org );
2476 	VectorScale( org, 0.5f, org );
2477 
2478 	if ( attacker != NULL && attacker->client )
2479 	{
2480 		VectorSubtract( org, attacker->r.currentOrigin, dir );
2481 		VectorNormalize( dir );
2482 	}
2483 	else
2484 	{
2485 		VectorCopy(up, dir);
2486 	}
2487 
2488 	if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION
2489 	{
2490 		// we are allowed to explode
2491 		G_MiscModelExplosion( self->r.absmin, self->r.absmax, size, chunkType );
2492 	}
2493 
2494 	if (self->genericValue15)
2495 	{ //a custom effect to play
2496 		vec3_t ang;
2497 		VectorSet(ang, 0.0f, 1.0f, 0.0f);
2498 		G_PlayEffectID(self->genericValue15, org, ang);
2499 	}
2500 
2501 	if ( self->splashDamage > 0 && self->splashRadius > 0 )
2502 	{
2503 		gentity_t *te;
2504 		//explode
2505 		G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, NULL, MOD_UNKNOWN );
2506 
2507 		te = G_TempEntity( org, EV_GENERAL_SOUND );
2508 		te->s.eventParm = G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
2509 	}
2510 
2511 	//FIXME: base numChunks off size?
2512 	G_Chunks( self->s.number, org, dir, self->r.absmin, self->r.absmax, 300, numChunks, chunkType, 0, (scale*self->mass) );
2513 
2514 	trap->AdjustAreaPortalState( (sharedEntity_t *)self, qtrue );
2515 	self->think = G_FreeEntity;
2516 	self->nextthink = level.time + 50;
2517 	//G_FreeEntity( self );
2518 }
2519 
funcBBrushDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)2520 void funcBBrushDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
2521 {
2522 	self->takedamage = qfalse;//stop chain reaction runaway loops
2523 
2524 	self->enemy = attacker;
2525 
2526 	if(self->delay)
2527 	{
2528 		self->think = funcBBrushDieGo;
2529 		self->nextthink = level.time + floor(self->delay * 1000.0f);
2530 		return;
2531 	}
2532 
2533 	funcBBrushDieGo(self);
2534 }
2535 
funcBBrushUse(gentity_t * self,gentity_t * other,gentity_t * activator)2536 void funcBBrushUse (gentity_t *self, gentity_t *other, gentity_t *activator)
2537 {
2538 	G_ActivateBehavior( self, BSET_USE );
2539 	if(self->spawnflags & 64)
2540 	{//Using it doesn't break it, makes it use it's targets
2541 		if(self->target && self->target[0])
2542 		{
2543 			G_UseTargets(self, activator);
2544 		}
2545 	}
2546 	else
2547 	{
2548 		funcBBrushDie(self, other, activator, self->health, MOD_UNKNOWN);
2549 	}
2550 }
2551 
funcBBrushPain(gentity_t * self,gentity_t * attacker,int damage)2552 void funcBBrushPain(gentity_t *self, gentity_t *attacker, int damage)
2553 {
2554 	if ( self->painDebounceTime > level.time )
2555 	{
2556 		return;
2557 	}
2558 
2559 	if ( self->paintarget && self->paintarget[0] )
2560 	{
2561 		if (!self->activator)
2562 		{
2563 			if (attacker && attacker->inuse && attacker->client)
2564 			{
2565 				G_UseTargets2 (self, attacker, self->paintarget);
2566 			}
2567 		}
2568 		else
2569 		{
2570 			G_UseTargets2 (self, self->activator, self->paintarget);
2571 		}
2572 	}
2573 
2574 	G_ActivateBehavior( self, BSET_PAIN );
2575 
2576 	if ( self->material == MAT_DRK_STONE
2577 		|| self->material == MAT_LT_STONE
2578 		|| self->material == MAT_GREY_STONE
2579 		|| self->material == MAT_SNOWY_ROCK )
2580 	{
2581 		vec3_t	org, dir;
2582 		float	scale;
2583 		int		numChunks;
2584 		VectorSubtract( self->r.absmax, self->r.absmin, org );// size
2585 		// This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted.
2586 		// Volume is length * width * height...then break that volume down based on how many chunks we have
2587 		scale = VectorLength( org ) / 100.0f;
2588 		VectorMA( self->r.absmin, 0.5, org, org );
2589 		VectorAdd( self->r.absmin,self->r.absmax, org );
2590 		VectorScale( org, 0.5f, org );
2591 		if ( attacker != NULL && attacker->client )
2592 		{
2593 			VectorSubtract( attacker->r.currentOrigin, org, dir );
2594 			VectorNormalize( dir );
2595 		}
2596 		else
2597 		{
2598 			VectorSet( dir, 0, 0, 1 );
2599 		}
2600 		numChunks = Q_irand( 1, 3 );
2601 		if ( self->radius > 0.0f )
2602 		{
2603 			// designer wants to scale number of chunks, helpful because the above scale code is far from perfect
2604 			//	I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak
2605 			numChunks = ceil(numChunks*self->radius);
2606 		}
2607 		G_Chunks( self->s.number, org, dir, self->r.absmin, self->r.absmax, 300, numChunks, self->material, 0, (scale*self->mass) );
2608 	}
2609 
2610 	if ( self->wait == -1 )
2611 	{
2612 		self->pain = 0;
2613 		return;
2614 	}
2615 
2616 	self->painDebounceTime = level.time + self->wait;
2617 }
2618 
InitBBrush(gentity_t * ent)2619 static void InitBBrush ( gentity_t *ent )
2620 {
2621 	float		light;
2622 	vec3_t		color;
2623 	qboolean	lightSet, colorSet;
2624 
2625 	VectorCopy( ent->s.origin, ent->pos1 );
2626 
2627 	trap->SetBrushModel( (sharedEntity_t *)ent, ent->model );
2628 
2629 	ent->die = funcBBrushDie;
2630 
2631 	ent->flags |= FL_BBRUSH;
2632 
2633 	//This doesn't have to be an svFlag, can just be a flag.
2634 	//And it might not be needed anyway.
2635 	//ent->r.svFlags |= SVF_BBRUSH;
2636 
2637 
2638 	// if the "model2" key is set, use a seperate model
2639 	// for drawing, but clip against the brushes
2640 	if ( ent->model2 && ent->model2[0] )
2641 	{
2642 		ent->s.modelindex2 = G_ModelIndex( ent->model2 );
2643 	}
2644 
2645 	// if the "color" or "light" keys are set, setup constantLight
2646 	lightSet = G_SpawnFloat( "light", "100", &light );
2647 	colorSet = G_SpawnVector( "color", "1 1 1", color );
2648 	if ( lightSet || colorSet )
2649 	{
2650 		int		r, g, b, i;
2651 
2652 		r = color[0] * 255;
2653 		if ( r > 255 ) {
2654 			r = 255;
2655 		}
2656 		g = color[1] * 255;
2657 		if ( g > 255 ) {
2658 			g = 255;
2659 		}
2660 		b = color[2] * 255;
2661 		if ( b > 255 ) {
2662 			b = 255;
2663 		}
2664 		i = light / 4;
2665 		if ( i > 255 ) {
2666 			i = 255;
2667 		}
2668 		ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
2669 	}
2670 
2671 	if(ent->spawnflags & 128)
2672 	{//Can be used by the player's BUTTON_USE
2673 		ent->r.svFlags |= SVF_PLAYER_USABLE;
2674 	}
2675 
2676 	ent->s.eType = ET_MOVER;
2677 	trap->LinkEntity((sharedEntity_t *)ent);
2678 
2679 	ent->s.pos.trType = TR_STATIONARY;
2680 	VectorCopy( ent->pos1, ent->s.pos.trBase );
2681 }
2682 
funcBBrushTouch(gentity_t * ent,gentity_t * other,trace_t * trace)2683 void funcBBrushTouch( gentity_t *ent, gentity_t *other, trace_t *trace )
2684 {
2685 }
2686 
2687 /*QUAKED func_breakable (0 .8 .5) ? INVINCIBLE IMPACT CRUSHER THIN SABERONLY HEAVY_WEAP USE_NOT_BREAK PLAYER_USE NO_EXPLOSION
2688 INVINCIBLE - can only be broken by being used
2689 IMPACT - does damage on impact
2690 CRUSHER - won't reverse movement when hit an obstacle
2691 THIN - can be broken by impact damage, like glass
2692 SABERONLY - only takes damage from sabers
2693 HEAVY_WEAP - only takes damage by a heavy weapon, like an emplaced gun or AT-ST gun.
2694 USE_NOT_BREAK - Using it doesn't make it break, still can be destroyed by damage
2695 PLAYER_USE - Player can use it with the use button
2696 NO_EXPLOSION - Does not play an explosion effect, though will still create chunks if specified
2697 
2698 When destroyed, fires it's trigger and chunks and plays sound "noise" or sound for type if no noise specified
2699 
2700 "targetname" entities with matching target will fire it
2701 "paintarget" target to fire when hit (but not destroyed)
2702 "wait"		how long minimum to wait between firing paintarget each time hit
2703 "delay"		When killed or used, how long (in seconds) to wait before blowing up (none by default)
2704 "model2"	.md3 model to also draw
2705 "target"	all entities with a matching targetname will be used when this is destoryed
2706 "health"	default is 10
2707 "numchunks" Multiplies the number of chunks spawned.  Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1)  (.5) is half as many chunks, (2) is twice as many chunks
2708 "chunksize"	scales up/down the chunk size by this number (default is 1)
2709 "playfx"	path of effect to play on death
2710 
2711 "showhealth" if non-0, will display the health bar on the hud when the crosshair is over this ent (in siege)
2712 "teamowner" in siege this will specify which team this thing is "owned" by. To that team the crosshair will
2713 highlight green, to the other team it will highlight red.
2714 
2715 Damage: default is none
2716 "splashDamage" - damage to do
2717 "splashRadius" - radius for above damage
2718 
2719 "team" - If set, only this team can trip this trigger
2720 	0 - any
2721 	1 - red
2722 	2 - blue
2723 
2724 Don't know if these work:
2725 "color"		constantLight color
2726 "light"		constantLight radius
2727 
2728 "material" - default is "0 - MAT_METAL" - choose from this list:
2729 0 = MAT_METAL		(basic blue-grey scorched-DEFAULT)
2730 1 = MAT_GLASS
2731 2 = MAT_ELECTRICAL	(sparks only)
2732 3 = MAT_ELEC_METAL	(METAL2 chunks and sparks)
2733 4 =	MAT_DRK_STONE	(brown stone chunks)
2734 5 =	MAT_LT_STONE	(tan stone chunks)
2735 6 =	MAT_GLASS_METAL (glass and METAL2 chunks)
2736 7 = MAT_METAL2		(electronic type of metal)
2737 8 = MAT_NONE		(no chunks)
2738 9 = MAT_GREY_STONE	(grey colored stone)
2739 10 = MAT_METAL3		(METAL and METAL2 chunk combo)
2740 11 = MAT_CRATE1		(yellow multi-colored crate chunks)
2741 12 = MAT_GRATE1		(grate chunks--looks horrible right now)
2742 13 = MAT_ROPE		(for yavin_trial, no chunks, just wispy bits )
2743 14 = MAT_CRATE2		(red multi-colored crate chunks)
2744 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )
2745 16 = MAT_SNOWY_ROCK	 (mix of gray and brown rocks)
2746 
2747 Applicable only during Siege gametype:
2748 teamnodmg - if 1, team 1 can't damage this. If 2, team 2 can't damage this.
2749 
2750 */
SP_func_breakable(gentity_t * self)2751 void SP_func_breakable( gentity_t *self )
2752 {
2753 	int t;
2754 	char *s = NULL;
2755 
2756 	G_SpawnString("playfx", "", &s);
2757 
2758 	if (s && s[0])
2759 	{ //should we play a special death effect?
2760 		self->genericValue15 = G_EffectIndex(s);
2761 	}
2762 	else
2763 	{
2764 		self->genericValue15 = 0;
2765 	}
2766 
2767 	if(!(self->spawnflags & 1))
2768 	{
2769 		if(!self->health)
2770 		{
2771 			self->health = 10;
2772 		}
2773 	}
2774 
2775 	G_SpawnInt( "showhealth", "0", &t );
2776 
2777 	if (t)
2778 	{ //a non-0 maxhealth value will mean we want to show the health on the hud
2779 		self->maxHealth = self->health;
2780 		G_ScaleNetHealth(self);
2781 	}
2782 
2783 	//NOTE: g_spawn.c does this automatically now
2784 	//G_SpawnInt( "teamowner", "0", &t );
2785 	//self->s.teamowner = t;
2786 
2787 	if ( self->spawnflags & 16 ) // saber only
2788 	{
2789 		self->flags |= FL_DMG_BY_SABER_ONLY;
2790 	}
2791 	else if ( self->spawnflags & 32 ) // heavy weap
2792 	{
2793 		self->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY;
2794 	}
2795 
2796 	if (self->health)
2797 	{
2798 		self->takedamage = qtrue;
2799 	}
2800 
2801 	G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");//precaching
2802 	G_SpawnFloat( "radius", "1", &self->radius ); // used to scale chunk code if desired by a designer
2803 	G_SpawnInt( "material", "0", (int*)&self->material );
2804 
2805 	G_SpawnInt( "splashDamage", "0", &self->splashDamage );
2806 	G_SpawnInt( "splashRadius", "0", &self->splashRadius );
2807 
2808 	CacheChunkEffects( self->material );
2809 
2810 	self->use = funcBBrushUse;
2811 
2812 	//if ( self->paintarget )
2813 	{
2814 		self->pain = funcBBrushPain;
2815 	}
2816 
2817 	self->touch = funcBBrushTouch;
2818 
2819 	/*
2820 	if ( self->team && self->team[0] )
2821 	{
2822 		self->alliedTeam = TranslateTeamName( self->team );
2823 		if(self->alliedTeam == TEAM_FREE)
2824 		{
2825 			trap->Error( ERR_DROP, "team name %s not recognized\n", self->team );
2826 		}
2827 	}
2828 	*/
2829 	if ( self->team && self->team[0] && level.gametype == GT_SIEGE &&
2830 		!self->teamnodmg)
2831 	{
2832 		self->teamnodmg = atoi(self->team);
2833 	}
2834 	self->team = NULL;
2835 	if (!self->model) {
2836 		trap->Error( ERR_DROP, "func_breakable with NULL model\n" );
2837 	}
2838 	InitBBrush( self );
2839 
2840 	if ( !self->radius )
2841 	{//numchunks multiplier
2842 		self->radius = 1.0f;
2843 	}
2844 	if ( !self->mass )
2845 	{//chunksize multiplier
2846 		self->mass = 1.0f;
2847 	}
2848 	self->genericValue4 = 1; //so damage sys knows it's a bbrush
2849 }
2850 
G_EntIsBreakable(int entityNum)2851 qboolean G_EntIsBreakable( int entityNum )
2852 {
2853 	gentity_t *ent;
2854 
2855 	if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD )
2856 	{
2857 		return qfalse;
2858 	}
2859 
2860 	ent = &g_entities[entityNum];
2861 	if ( (ent->r.svFlags & SVF_GLASS_BRUSH) )
2862 	{
2863 		return qtrue;
2864 	}
2865 	/*
2866 	if ( (ent->svFlags&SVF_BBRUSH) )
2867 	{
2868 		return qtrue;
2869 	}
2870 	*/
2871 	if ( !Q_stricmp( "func_breakable", ent->classname ) )
2872 	{
2873 		return qtrue;
2874 	}
2875 
2876 	if ( !Q_stricmp( "misc_model_breakable", ent->classname ) )
2877 	{
2878 		return qtrue;
2879 	}
2880 	if ( !Q_stricmp( "misc_maglock", ent->classname ) )
2881 	{
2882 		return qtrue;
2883 	}
2884 
2885 	return qfalse;
2886 }
2887 
2888 /*
2889 ===============================================================================
2890 
2891 GLASS
2892 
2893 ===============================================================================
2894 */
GlassDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)2895 void GlassDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
2896 {
2897 	gentity_t *te;
2898 	vec3_t dif;
2899 
2900 	if (self->genericValue5)
2901 	{ //was already destroyed, do not retrigger it
2902 		return;
2903 	}
2904 
2905 	self->genericValue5 = 1;
2906 
2907 	dif[0] = (self->r.absmax[0]+self->r.absmin[0])/2;
2908 	dif[1] = (self->r.absmax[1]+self->r.absmin[1])/2;
2909 	dif[2] = (self->r.absmax[2]+self->r.absmin[2])/2;
2910 
2911 	G_UseTargets(self, attacker);
2912 
2913 	self->splashRadius = 40; // ?? some random number, maybe it's ok?
2914 
2915 	te = G_TempEntity( dif, EV_GLASS_SHATTER );
2916 	te->s.genericenemyindex = self->s.number;
2917 	VectorCopy(self->pos1, te->s.origin);
2918 	VectorCopy(self->pos2, te->s.angles);
2919 	te->s.trickedentindex = (int)self->splashRadius;
2920 	te->s.pos.trTime = (int)self->genericValue3;
2921 
2922 	G_FreeEntity(self);
2923 }
2924 
GlassDie_Old(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)2925 void GlassDie_Old(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
2926 {
2927 	gentity_t *te;
2928 	vec3_t dif;
2929 
2930 	dif[0] = (self->r.absmax[0]+self->r.absmin[0])/2;
2931 	dif[1] = (self->r.absmax[1]+self->r.absmin[1])/2;
2932 	dif[2] = (self->r.absmax[2]+self->r.absmin[2])/2;
2933 
2934 	G_UseTargets(self, attacker);
2935 
2936 	te = G_TempEntity( dif, EV_GLASS_SHATTER );
2937 	te->s.genericenemyindex = self->s.number;
2938 	VectorCopy(self->r.maxs, te->s.origin);
2939 	VectorCopy(self->r.mins, te->s.angles);
2940 
2941 	G_FreeEntity(self);
2942 }
2943 
GlassPain(gentity_t * self,gentity_t * attacker,int damage)2944 void GlassPain(gentity_t *self, gentity_t *attacker, int damage)
2945 {
2946 	//trap->Print("Mr. Glass says: PLZ NO IT HURTS\n");
2947 	//Make "cracking" sound?
2948 }
2949 
GlassUse(gentity_t * self,gentity_t * other,gentity_t * activator)2950 void GlassUse(gentity_t *self, gentity_t *other, gentity_t *activator)
2951 {
2952 	vec3_t temp1, temp2;
2953 
2954 	//no direct object to blame for the break, so fill the values with whatever
2955 	VectorAdd( self->r.mins, self->r.maxs, temp1 );
2956 	VectorScale( temp1, 0.5f, temp1 );
2957 
2958 	VectorAdd( other->r.mins, other->r.maxs, temp2 );
2959 	VectorScale( temp2, 0.5f, temp2 );
2960 
2961 	VectorSubtract( temp1, temp2, self->pos2 );
2962 	VectorCopy( temp1, self->pos1 );
2963 
2964 	VectorNormalize( self->pos2 );
2965 	VectorScale( self->pos2, 390, self->pos2 );
2966 
2967 	GlassDie(self, other, activator, 100, MOD_UNKNOWN);
2968 }
2969 
2970 /*QUAKED func_glass (0 .5 .8) ? x x x x x x PLAYER_USE INACTIVE
2971 Breakable glass
2972 "model2"	.md3 model to also draw
2973 "color"		constantLight color
2974 "light"		constantLight radius
2975 "maxshards"	Max number of shards to spawn on glass break
2976 */
SP_func_glass(gentity_t * ent)2977 void SP_func_glass( gentity_t *ent ) {
2978 	trap->SetBrushModel( (sharedEntity_t *)ent, ent->model );
2979 	InitMover( ent );
2980 
2981 	ent->r.svFlags = SVF_GLASS_BRUSH;
2982 
2983 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
2984 	VectorCopy( ent->s.origin, ent->r.currentOrigin );
2985 	if (!ent->health)
2986 	{
2987 		ent->health = 1;
2988 	}
2989 
2990 	G_SpawnInt("maxshards", "0", &ent->genericValue3);
2991 
2992 	ent->genericValue1 = 0;
2993 
2994 	ent->genericValue4 = 1;
2995 
2996 	ent->moverState = MOVER_POS1;
2997 
2998 	if (ent->spawnflags & 1)
2999 	{
3000 		ent->takedamage = qfalse;
3001 	}
3002 	else
3003 	{
3004 		ent->takedamage = qtrue;
3005 	}
3006 
3007 	ent->die = GlassDie;
3008 	ent->use = GlassUse;
3009 	ent->pain = GlassPain;
3010 }
3011 
3012 void func_usable_use (gentity_t *self, gentity_t *other, gentity_t *activator);
3013 
3014 extern gentity_t	*G_TestEntityPosition( gentity_t *ent );
func_wait_return_solid(gentity_t * self)3015 void func_wait_return_solid( gentity_t *self )
3016 {
3017 	//once a frame, see if it's clear.
3018 	self->clipmask = CONTENTS_BODY;
3019 	if ( !(self->spawnflags&16) || G_TestEntityPosition( self ) == NULL )
3020 	{
3021 		trap->SetBrushModel( (sharedEntity_t *)self, self->model );
3022 		InitMover( self );
3023 		VectorCopy( self->s.origin, self->s.pos.trBase );
3024 		VectorCopy( self->s.origin, self->r.currentOrigin );
3025 		self->r.svFlags &= ~SVF_NOCLIENT;
3026 		self->s.eFlags &= ~EF_NODRAW;
3027 		self->use = func_usable_use;
3028 		self->clipmask = 0;
3029 		if ( self->target2 && self->target2[0] )
3030 		{
3031 			G_UseTargets2( self, self->activator, self->target2 );
3032 		}
3033 		//FIXME: Animations?
3034 		/*if ( self->s.eFlags & EF_ANIM_ONCE )
3035 		{//Start our anim
3036 			self->s.frame = 0;
3037 		}*/
3038 	}
3039 	else
3040 	{
3041 		self->clipmask = 0;
3042 		self->think = func_wait_return_solid;
3043 		self->nextthink = level.time + FRAMETIME;
3044 	}
3045 }
3046 
func_usable_think(gentity_t * self)3047 void func_usable_think( gentity_t *self )
3048 {
3049 	if ( self->spawnflags & 8 )
3050 	{
3051 		self->r.svFlags |= SVF_PLAYER_USABLE;	//Replace the usable flag
3052 		self->use = func_usable_use;
3053 		self->think = 0;
3054 	}
3055 }
3056 
G_EntIsRemovableUsable(int entNum)3057 qboolean G_EntIsRemovableUsable( int entNum )
3058 {
3059 	gentity_t *ent = &g_entities[entNum];
3060 	if ( ent->classname && !Q_stricmp( "func_usable", ent->classname ) )
3061 	{
3062 		if ( !(ent->s.eFlags&EF_SHADER_ANIM) && !(ent->spawnflags&8) && ent->targetname )
3063 		{//not just a shader-animator and not ALWAYS_ON, so it must be removable somehow
3064 			return qtrue;
3065 		}
3066 	}
3067 	return qfalse;
3068 }
3069 
func_usable_use(gentity_t * self,gentity_t * other,gentity_t * activator)3070 void func_usable_use (gentity_t *self, gentity_t *other, gentity_t *activator)
3071 {//Toggle on and off
3072 	G_ActivateBehavior( self, BSET_USE );
3073 	if ( self->s.eFlags & EF_SHADER_ANIM )
3074 	{//animate shader when used
3075 		self->s.frame++;//inc frame
3076 		if ( self->s.frame > self->genericValue5 )
3077 		{//wrap around
3078 			self->s.frame = 0;
3079 		}
3080 		if ( self->target && self->target[0] )
3081 		{
3082 			G_UseTargets( self, activator );
3083 		}
3084 	}
3085 	else if ( self->spawnflags & 8 )
3086 	{//ALWAYS_ON
3087 		//Remove the ability to use the entity directly
3088 		self->r.svFlags &= ~SVF_PLAYER_USABLE;
3089 		//also remove ability to call any use func at all!
3090 		self->use = 0;
3091 
3092 		if(self->target && self->target[0])
3093 		{
3094 			G_UseTargets(self, activator);
3095 		}
3096 
3097 		if ( self->wait )
3098 		{
3099 			self->think = func_usable_think;
3100 			self->nextthink = level.time + ( self->wait * 1000 );
3101 		}
3102 
3103 		return;
3104 	}
3105 	else if ( !self->count )
3106 	{//become solid again
3107 		self->count = 1;
3108 		func_wait_return_solid( self );
3109 	}
3110 	else
3111 	{
3112 		self->s.solid = 0;
3113 		self->r.contents = 0;
3114 		self->clipmask = 0;
3115 		self->r.svFlags |= SVF_NOCLIENT;
3116 		self->s.eFlags |= EF_NODRAW;
3117 		self->count = 0;
3118 
3119 		if(self->target && self->target[0])
3120 		{
3121 			G_UseTargets(self, activator);
3122 		}
3123 		self->think = 0;
3124 		self->nextthink = -1;
3125 	}
3126 }
3127 
func_usable_pain(gentity_t * self,gentity_t * attacker,int damage)3128 void func_usable_pain(gentity_t *self, gentity_t *attacker, int damage)
3129 {
3130 	GlobalUse(self, attacker, attacker);
3131 }
3132 
func_usable_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)3133 void func_usable_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
3134 {
3135 	self->takedamage = qfalse;
3136 	GlobalUse(self, inflictor, attacker);
3137 }
3138 
3139 /*QUAKED func_usable (0 .5 .8) ? STARTOFF x x ALWAYS_ON x x PLAYER_USE INACTIVE
3140 START_OFF - the wall will not be there
3141 ALWAYS_ON - Doesn't toggle on and off when used, just runs usescript and fires target
3142 
3143 A bmodel that just sits there, doing nothing.  Can be used for conditional walls and models.
3144 "targetname" - When used, will toggle on and off
3145 "target"	Will fire this target every time it is toggled OFF
3146 "model2"	.md3 model to also draw
3147 "color"		constantLight color
3148 "light"		constantLight radius
3149 "usescript" script to run when turned on
3150 "deathscript"  script to run when turned off
3151 "wait"		amount of time before the object is usable again (only valid with ALWAYS_ON flag)
3152 "health"	if it has health, it will be used whenever shot at/killed - if you want it to only be used once this way, set health to 1
3153 "endframe"	Will make it animate to next shader frame when used, not turn on/off... set this to number of frames in the shader, minus 1
3154 
3155 Applicable only during Siege gametype:
3156 teamuser - if 1, team 2 can't use this. If 2, team 1 can't use this.
3157 
3158 */
3159 
SP_func_usable(gentity_t * self)3160 void SP_func_usable( gentity_t *self )
3161 {
3162 	trap->SetBrushModel( (sharedEntity_t *)self, self->model );
3163 	InitMover( self );
3164 	VectorCopy( self->s.origin, self->s.pos.trBase );
3165 	VectorCopy( self->s.origin, self->r.currentOrigin );
3166 	VectorCopy( self->s.origin, self->pos1 );
3167 
3168 	G_SpawnInt("endframe", "0", &self->genericValue5);
3169 
3170 	if ( self->model2 && self->model2[0] )
3171 	{
3172 		if ( strstr( self->model2, ".glm" ))
3173 		{ //for now, not supported in MP.
3174 			self->s.modelindex2 = 0;
3175 		}
3176 		else
3177 		{
3178 			self->s.modelindex2 = G_ModelIndex( self->model2 );
3179 		}
3180 	}
3181 
3182 	self->count = 1;
3183 	if (self->spawnflags & 1)
3184 	{
3185 		self->s.solid = 0;
3186 		self->r.contents = 0;
3187 		self->clipmask = 0;
3188 		self->r.svFlags |= SVF_NOCLIENT;
3189 		self->s.eFlags |= EF_NODRAW;
3190 		self->count = 0;
3191 	}
3192 
3193 	//FIXME: Animation?
3194 	/*
3195 	if (self->spawnflags & 2)
3196 	{
3197 		self->s.eFlags |= EF_ANIM_ALLFAST;
3198 	}
3199 
3200 	if (self->spawnflags & 4)
3201 	{//FIXME: need to be able to do change to something when it's done?  Or not be usable until it's done?
3202 		self->s.eFlags |= EF_ANIM_ONCE;
3203 	}
3204 	*/
3205 
3206 	self->use = func_usable_use;
3207 
3208 	if ( self->health )
3209 	{
3210 		self->takedamage = qtrue;
3211 		self->die = func_usable_die;
3212 		self->pain = func_usable_pain;
3213 	}
3214 
3215 	if ( self->genericValue5 > 0 )
3216 	{
3217 		self->s.frame = 0;
3218 		self->s.eFlags |= EF_SHADER_ANIM;
3219 		self->s.time = self->genericValue5 + 1;
3220 	}
3221 
3222 	trap->LinkEntity ((sharedEntity_t *)self);
3223 }
3224 
3225 
3226 /*
3227 ===============================================================================
3228 
3229 WALL
3230 
3231 ===============================================================================
3232 */
3233 
3234 //static -slc
use_wall(gentity_t * ent,gentity_t * other,gentity_t * activator)3235 void use_wall( gentity_t *ent, gentity_t *other, gentity_t *activator )
3236 {
3237 	G_ActivateBehavior(ent,BSET_USE);
3238 
3239 	// Not there so make it there
3240 	if (!(ent->r.contents & CONTENTS_SOLID))
3241 	{
3242 		ent->r.svFlags &= ~SVF_NOCLIENT;
3243 		ent->s.eFlags &= ~EF_NODRAW;
3244 		ent->r.contents = CONTENTS_SOLID;
3245 		if ( !(ent->spawnflags&1) )
3246 		{//START_OFF doesn't effect area portals
3247 			trap->AdjustAreaPortalState( (sharedEntity_t *)ent, qfalse );
3248 		}
3249 	}
3250 	// Make it go away
3251 	else
3252 	{
3253 		ent->r.contents = 0;
3254 		ent->r.svFlags |= SVF_NOCLIENT;
3255 		ent->s.eFlags |= EF_NODRAW;
3256 		if ( !(ent->spawnflags&1) )
3257 		{//START_OFF doesn't effect area portals
3258 			trap->AdjustAreaPortalState( (sharedEntity_t *)ent, qtrue );
3259 		}
3260 	}
3261 }
3262 
3263 #define FUNC_WALL_OFF	1
3264 
3265 /*QUAKED func_wall (0 .5 .8) ? START_OFF x x x x x PLAYER_USE INACTIVE
3266 PLAYER_USE	Player can use it with the use button
3267 INACTIVE	must be used by a target_activate before it can be used
3268 
3269 A bmodel that just sits there, doing nothing.  Can be used for conditional walls and models.
3270 "model2"	.md3 model to also draw
3271 "color"		constantLight color
3272 "light"		constantLight radius
3273 
3274 START_OFF - the wall will not be there
3275 */
SP_func_wall(gentity_t * ent)3276 void SP_func_wall( gentity_t *ent )
3277 {
3278 	trap->SetBrushModel( (sharedEntity_t *)ent, ent->model );
3279 
3280 	VectorCopy( ent->s.origin, ent->pos1 );
3281 	VectorCopy( ent->s.origin, ent->pos2 );
3282 
3283 	InitMover( ent );
3284 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
3285 	VectorCopy( ent->s.origin, ent->r.currentOrigin );
3286 
3287 	// it must be START_OFF
3288 	if (ent->spawnflags & FUNC_WALL_OFF)
3289 	{
3290 		ent->r.contents = 0;
3291 		ent->r.svFlags |= SVF_NOCLIENT;
3292 		ent->s.eFlags |= EF_NODRAW;
3293 	}
3294 
3295 	ent->use = use_wall;
3296 
3297 	trap->LinkEntity ((sharedEntity_t *)ent);
3298 
3299 }
3300