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