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