1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22 //
23
24 #include "g_local.h"
25
26
27
28 /*
29 ===============================================================================
30
31 PUSHMOVE
32
33 ===============================================================================
34 */
35
36 void MatchTeam( gentity_t *teamLeader, int moverState, int time );
37
38 typedef struct {
39 gentity_t *ent;
40 vec3_t origin;
41 vec3_t angles;
42 float deltayaw;
43 } pushed_t;
44 pushed_t pushed[MAX_GENTITIES], *pushed_p;
45
46
47 /*
48 ============
49 G_TestEntityPosition
50
51 ============
52 */
G_TestEntityPosition(gentity_t * ent)53 gentity_t *G_TestEntityPosition( gentity_t *ent ) {
54 trace_t tr;
55 int mask;
56
57 if ( ent->clipmask ) {
58 mask = ent->clipmask;
59 } else {
60 mask = MASK_SOLID;
61 }
62 if ( ent->client ) {
63 trap_Trace( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, ent->client->ps.origin, ent->s.number, mask );
64 } else {
65 trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, mask );
66 }
67
68 if (tr.startsolid)
69 return &g_entities[ tr.entityNum ];
70
71 return NULL;
72 }
73
74 /*
75 ================
76 G_CreateRotationMatrix
77 ================
78 */
G_CreateRotationMatrix(vec3_t angles,vec3_t matrix[3])79 void G_CreateRotationMatrix(vec3_t angles, vec3_t matrix[3]) {
80 AngleVectors(angles, matrix[0], matrix[1], matrix[2]);
81 VectorInverse(matrix[1]);
82 }
83
84 /*
85 ================
86 G_TransposeMatrix
87 ================
88 */
G_TransposeMatrix(vec3_t matrix[3],vec3_t transpose[3])89 void G_TransposeMatrix(vec3_t matrix[3], vec3_t transpose[3]) {
90 int i, j;
91 for (i = 0; i < 3; i++) {
92 for (j = 0; j < 3; j++) {
93 transpose[i][j] = matrix[j][i];
94 }
95 }
96 }
97
98 /*
99 ================
100 G_RotatePoint
101 ================
102 */
G_RotatePoint(vec3_t point,vec3_t matrix[3])103 void G_RotatePoint(vec3_t point, vec3_t matrix[3]) {
104 vec3_t tvec;
105
106 VectorCopy(point, tvec);
107 point[0] = DotProduct(matrix[0], tvec);
108 point[1] = DotProduct(matrix[1], tvec);
109 point[2] = DotProduct(matrix[2], tvec);
110 }
111
112 /*
113 ==================
114 G_TryPushingEntity
115
116 Returns qfalse if the move is blocked
117 ==================
118 */
G_TryPushingEntity(gentity_t * check,gentity_t * pusher,vec3_t move,vec3_t amove)119 qboolean G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) {
120 vec3_t matrix[3], transpose[3];
121 vec3_t org, org2, move2;
122 gentity_t *block;
123
124 // EF_MOVER_STOP will just stop when contacting another entity
125 // instead of pushing it, but entities can still ride on top of it
126 if ( ( pusher->s.eFlags & EF_MOVER_STOP ) &&
127 check->s.groundEntityNum != pusher->s.number ) {
128 return qfalse;
129 }
130
131 // save off the old position
132 if (pushed_p > &pushed[MAX_GENTITIES]) {
133 G_Error( "pushed_p > &pushed[MAX_GENTITIES]" );
134 }
135 pushed_p->ent = check;
136 VectorCopy (check->s.pos.trBase, pushed_p->origin);
137 VectorCopy (check->s.apos.trBase, pushed_p->angles);
138 if ( check->client ) {
139 pushed_p->deltayaw = check->client->ps.delta_angles[YAW];
140 VectorCopy (check->client->ps.origin, pushed_p->origin);
141 }
142 pushed_p++;
143
144 // try moving the contacted entity
145 // figure movement due to the pusher's amove
146 G_CreateRotationMatrix( amove, transpose );
147 G_TransposeMatrix( transpose, matrix );
148 if ( check->client ) {
149 VectorSubtract (check->client->ps.origin, pusher->r.currentOrigin, org);
150 }
151 else {
152 VectorSubtract (check->s.pos.trBase, pusher->r.currentOrigin, org);
153 }
154 VectorCopy( org, org2 );
155 G_RotatePoint( org2, matrix );
156 VectorSubtract (org2, org, move2);
157 // add movement
158 VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase);
159 VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase);
160 if ( check->client ) {
161 VectorAdd (check->client->ps.origin, move, check->client->ps.origin);
162 VectorAdd (check->client->ps.origin, move2, check->client->ps.origin);
163 // make sure the client's view rotates when on a rotating mover
164 check->client->ps.delta_angles[YAW] += ANGLE2SHORT(amove[YAW]);
165 }
166
167 // may have pushed them off an edge
168 if ( check->s.groundEntityNum != pusher->s.number ) {
169 check->s.groundEntityNum = -1;
170 }
171
172 block = G_TestEntityPosition( check );
173 if (!block) {
174 // pushed ok
175 if ( check->client ) {
176 VectorCopy( check->client->ps.origin, check->r.currentOrigin );
177 } else {
178 VectorCopy( check->s.pos.trBase, check->r.currentOrigin );
179 }
180 trap_LinkEntity (check);
181 return qtrue;
182 }
183
184 // if it is ok to leave in the old position, do it
185 // this is only relevent for riding entities, not pushed
186 // Sliding trapdoors can cause this.
187 VectorCopy( (pushed_p-1)->origin, check->s.pos.trBase);
188 if ( check->client ) {
189 VectorCopy( (pushed_p-1)->origin, check->client->ps.origin);
190 }
191 VectorCopy( (pushed_p-1)->angles, check->s.apos.trBase );
192 block = G_TestEntityPosition (check);
193 if ( !block ) {
194 check->s.groundEntityNum = -1;
195 pushed_p--;
196 return qtrue;
197 }
198
199 // blocked
200 return qfalse;
201 }
202
203 /*
204 ==================
205 G_CheckProxMinePosition
206 ==================
207 */
G_CheckProxMinePosition(gentity_t * check)208 qboolean G_CheckProxMinePosition( gentity_t *check ) {
209 vec3_t start, end;
210 trace_t tr;
211
212 VectorMA(check->s.pos.trBase, 0.125, check->movedir, start);
213 VectorMA(check->s.pos.trBase, 2, check->movedir, end);
214 trap_Trace( &tr, start, NULL, NULL, end, check->s.number, MASK_SOLID );
215
216 if (tr.startsolid || tr.fraction < 1)
217 return qfalse;
218
219 return qtrue;
220 }
221
222 /*
223 ==================
224 G_TryPushingProxMine
225 ==================
226 */
G_TryPushingProxMine(gentity_t * check,gentity_t * pusher,vec3_t move,vec3_t amove)227 qboolean G_TryPushingProxMine( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) {
228 vec3_t forward, right, up;
229 vec3_t org, org2, move2;
230 int ret;
231
232 // we need this for pushing things later
233 VectorSubtract (vec3_origin, amove, org);
234 AngleVectors (org, forward, right, up);
235
236 // try moving the contacted entity
237 VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase);
238
239 // figure movement due to the pusher's amove
240 VectorSubtract (check->s.pos.trBase, pusher->r.currentOrigin, org);
241 org2[0] = DotProduct (org, forward);
242 org2[1] = -DotProduct (org, right);
243 org2[2] = DotProduct (org, up);
244 VectorSubtract (org2, org, move2);
245 VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase);
246
247 ret = G_CheckProxMinePosition( check );
248 if (ret) {
249 VectorCopy( check->s.pos.trBase, check->r.currentOrigin );
250 trap_LinkEntity (check);
251 }
252 return ret;
253 }
254
255 void G_ExplodeMissile( gentity_t *ent );
256
257 /*
258 ============
259 G_MoverPush
260
261 Objects need to be moved back on a failed push,
262 otherwise riders would continue to slide.
263 If qfalse is returned, *obstacle will be the blocking entity
264 ============
265 */
G_MoverPush(gentity_t * pusher,vec3_t move,vec3_t amove,gentity_t ** obstacle)266 qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **obstacle ) {
267 int i, e;
268 gentity_t *check;
269 vec3_t mins, maxs;
270 pushed_t *p;
271 int entityList[MAX_GENTITIES];
272 int listedEntities;
273 vec3_t totalMins, totalMaxs;
274
275 *obstacle = NULL;
276
277
278 // mins/maxs are the bounds at the destination
279 // totalMins / totalMaxs are the bounds for the entire move
280 if ( pusher->r.currentAngles[0] || pusher->r.currentAngles[1] || pusher->r.currentAngles[2]
281 || amove[0] || amove[1] || amove[2] ) {
282 float radius;
283
284 radius = RadiusFromBounds( pusher->r.mins, pusher->r.maxs );
285 for ( i = 0 ; i < 3 ; i++ ) {
286 mins[i] = pusher->r.currentOrigin[i] + move[i] - radius;
287 maxs[i] = pusher->r.currentOrigin[i] + move[i] + radius;
288 totalMins[i] = mins[i] - move[i];
289 totalMaxs[i] = maxs[i] - move[i];
290 }
291 } else {
292 for (i=0 ; i<3 ; i++) {
293 mins[i] = pusher->r.absmin[i] + move[i];
294 maxs[i] = pusher->r.absmax[i] + move[i];
295 }
296
297 VectorCopy( pusher->r.absmin, totalMins );
298 VectorCopy( pusher->r.absmax, totalMaxs );
299 for (i=0 ; i<3 ; i++) {
300 if ( move[i] > 0 ) {
301 totalMaxs[i] += move[i];
302 } else {
303 totalMins[i] += move[i];
304 }
305 }
306 }
307
308 // unlink the pusher so we don't get it in the entityList
309 trap_UnlinkEntity( pusher );
310
311 listedEntities = trap_EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES );
312
313 // move the pusher to it's final position
314 VectorAdd( pusher->r.currentOrigin, move, pusher->r.currentOrigin );
315 VectorAdd( pusher->r.currentAngles, amove, pusher->r.currentAngles );
316 trap_LinkEntity( pusher );
317
318 // see if any solid entities are inside the final position
319 for ( e = 0 ; e < listedEntities ; e++ ) {
320 check = &g_entities[ entityList[ e ] ];
321
322 #ifdef MISSIONPACK
323 if ( check->s.eType == ET_MISSILE ) {
324 // if it is a prox mine
325 if ( !strcmp(check->classname, "prox mine") ) {
326 // if this prox mine is attached to this mover try to move it with the pusher
327 if ( check->enemy == pusher ) {
328 if (!G_TryPushingProxMine( check, pusher, move, amove )) {
329 //explode
330 check->s.loopSound = 0;
331 G_AddEvent( check, EV_PROXIMITY_MINE_TRIGGER, 0 );
332 G_ExplodeMissile(check);
333 if (check->activator) {
334 G_FreeEntity(check->activator);
335 check->activator = NULL;
336 }
337 //G_Printf("prox mine explodes\n");
338 }
339 }
340 else {
341 //check if the prox mine is crushed by the mover
342 if (!G_CheckProxMinePosition( check )) {
343 //explode
344 check->s.loopSound = 0;
345 G_AddEvent( check, EV_PROXIMITY_MINE_TRIGGER, 0 );
346 G_ExplodeMissile(check);
347 if (check->activator) {
348 G_FreeEntity(check->activator);
349 check->activator = NULL;
350 }
351 //G_Printf("prox mine explodes\n");
352 }
353 }
354 continue;
355 }
356 }
357 #endif
358 // only push items and players
359 if ( check->s.eType != ET_ITEM && check->s.eType != ET_PLAYER && !check->physicsObject ) {
360 continue;
361 }
362
363 // if the entity is standing on the pusher, it will definitely be moved
364 if ( check->s.groundEntityNum != pusher->s.number ) {
365 // see if the ent needs to be tested
366 if ( check->r.absmin[0] >= maxs[0]
367 || check->r.absmin[1] >= maxs[1]
368 || check->r.absmin[2] >= maxs[2]
369 || check->r.absmax[0] <= mins[0]
370 || check->r.absmax[1] <= mins[1]
371 || check->r.absmax[2] <= mins[2] ) {
372 continue;
373 }
374 // see if the ent's bbox is inside the pusher's final position
375 // this does allow a fast moving object to pass through a thin entity...
376 if (!G_TestEntityPosition (check)) {
377 continue;
378 }
379 }
380
381 // the entity needs to be pushed
382 if ( G_TryPushingEntity( check, pusher, move, amove ) ) {
383 continue;
384 }
385
386 // the move was blocked an entity
387
388 // bobbing entities are instant-kill and never get blocked
389 if ( pusher->s.pos.trType == TR_SINE || pusher->s.apos.trType == TR_SINE ) {
390 G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH );
391 continue;
392 }
393
394
395 // save off the obstacle so we can call the block function (crush, etc)
396 *obstacle = check;
397
398 // move back any entities we already moved
399 // go backwards, so if the same entity was pushed
400 // twice, it goes back to the original position
401 for ( p=pushed_p-1 ; p>=pushed ; p-- ) {
402 VectorCopy (p->origin, p->ent->s.pos.trBase);
403 VectorCopy (p->angles, p->ent->s.apos.trBase);
404 if ( p->ent->client ) {
405 p->ent->client->ps.delta_angles[YAW] = p->deltayaw;
406 VectorCopy (p->origin, p->ent->client->ps.origin);
407 }
408 trap_LinkEntity (p->ent);
409 }
410 return qfalse;
411 }
412
413 return qtrue;
414 }
415
416
417 /*
418 =================
419 G_MoverTeam
420 =================
421 */
G_MoverTeam(gentity_t * ent)422 void G_MoverTeam( gentity_t *ent ) {
423 vec3_t move, amove;
424 gentity_t *part, *obstacle;
425 vec3_t origin, angles;
426
427 obstacle = NULL;
428
429 // make sure all team slaves can move before commiting
430 // any moves or calling any think functions
431 // if the move is blocked, all moved objects will be backed out
432 pushed_p = pushed;
433 for (part = ent ; part ; part=part->teamchain) {
434 // get current position
435 BG_EvaluateTrajectory( &part->s.pos, level.time, origin );
436 BG_EvaluateTrajectory( &part->s.apos, level.time, angles );
437 VectorSubtract( origin, part->r.currentOrigin, move );
438 VectorSubtract( angles, part->r.currentAngles, amove );
439 if ( !G_MoverPush( part, move, amove, &obstacle ) ) {
440 break; // move was blocked
441 }
442 }
443
444 if (part) {
445 // go back to the previous position
446 for ( part = ent ; part ; part = part->teamchain ) {
447 part->s.pos.trTime += level.time - level.previousTime;
448 part->s.apos.trTime += level.time - level.previousTime;
449 BG_EvaluateTrajectory( &part->s.pos, level.time, part->r.currentOrigin );
450 BG_EvaluateTrajectory( &part->s.apos, level.time, part->r.currentAngles );
451 trap_LinkEntity( part );
452 }
453
454 // if the pusher has a "blocked" function, call it
455 if (ent->blocked) {
456 ent->blocked( ent, obstacle );
457 }
458 return;
459 }
460
461 // the move succeeded
462 for ( part = ent ; part ; part = part->teamchain ) {
463 // call the reached function if time is at or past end point
464 if ( part->s.pos.trType == TR_LINEAR_STOP ) {
465 if ( level.time >= part->s.pos.trTime + part->s.pos.trDuration ) {
466 if ( part->reached ) {
467 part->reached( part );
468 }
469 }
470 }
471 }
472 }
473
474 /*
475 ================
476 G_RunMover
477
478 ================
479 */
G_RunMover(gentity_t * ent)480 void G_RunMover( gentity_t *ent ) {
481 // if not a team captain, don't do anything, because
482 // the captain will handle everything
483 if ( ent->flags & FL_TEAMSLAVE ) {
484 return;
485 }
486
487 // if stationary at one of the positions, don't move anything
488 if ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) {
489 G_MoverTeam( ent );
490 }
491
492 // check think function
493 G_RunThink( ent );
494 }
495
496 /*
497 ============================================================================
498
499 GENERAL MOVERS
500
501 Doors, plats, and buttons are all binary (two position) movers
502 Pos1 is "at rest", pos2 is "activated"
503 ============================================================================
504 */
505
506 /*
507 ===============
508 SetMoverState
509 ===============
510 */
SetMoverState(gentity_t * ent,moverState_t moverState,int time)511 void SetMoverState( gentity_t *ent, moverState_t moverState, int time ) {
512 vec3_t delta;
513 float f;
514
515 ent->moverState = moverState;
516
517 ent->s.pos.trTime = time;
518 switch( moverState ) {
519 case MOVER_POS1:
520 VectorCopy( ent->pos1, ent->s.pos.trBase );
521 ent->s.pos.trType = TR_STATIONARY;
522 break;
523 case MOVER_POS2:
524 VectorCopy( ent->pos2, ent->s.pos.trBase );
525 ent->s.pos.trType = TR_STATIONARY;
526 break;
527 case MOVER_1TO2:
528 VectorCopy( ent->pos1, ent->s.pos.trBase );
529 VectorSubtract( ent->pos2, ent->pos1, delta );
530 f = 1000.0 / ent->s.pos.trDuration;
531 VectorScale( delta, f, ent->s.pos.trDelta );
532 ent->s.pos.trType = TR_LINEAR_STOP;
533 break;
534 case MOVER_2TO1:
535 VectorCopy( ent->pos2, ent->s.pos.trBase );
536 VectorSubtract( ent->pos1, ent->pos2, delta );
537 f = 1000.0 / ent->s.pos.trDuration;
538 VectorScale( delta, f, ent->s.pos.trDelta );
539 ent->s.pos.trType = TR_LINEAR_STOP;
540 break;
541 }
542 BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin );
543 trap_LinkEntity( ent );
544 }
545
546 /*
547 ================
548 MatchTeam
549
550 All entities in a mover team will move from pos1 to pos2
551 in the same amount of time
552 ================
553 */
MatchTeam(gentity_t * teamLeader,int moverState,int time)554 void MatchTeam( gentity_t *teamLeader, int moverState, int time ) {
555 gentity_t *slave;
556
557 for ( slave = teamLeader ; slave ; slave = slave->teamchain ) {
558 SetMoverState( slave, moverState, time );
559 }
560 }
561
562
563
564 /*
565 ================
566 ReturnToPos1
567 ================
568 */
ReturnToPos1(gentity_t * ent)569 void ReturnToPos1( gentity_t *ent ) {
570 MatchTeam( ent, MOVER_2TO1, level.time );
571
572 // looping sound
573 ent->s.loopSound = ent->soundLoop;
574
575 // starting sound
576 if ( ent->sound2to1 ) {
577 G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
578 }
579 }
580
581
582 /*
583 ================
584 Reached_BinaryMover
585 ================
586 */
Reached_BinaryMover(gentity_t * ent)587 void Reached_BinaryMover( gentity_t *ent ) {
588
589 // stop the looping sound
590 ent->s.loopSound = ent->soundLoop;
591
592 if ( ent->moverState == MOVER_1TO2 ) {
593 // reached pos2
594 SetMoverState( ent, MOVER_POS2, level.time );
595
596 // play sound
597 if ( ent->soundPos2 ) {
598 G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 );
599 }
600
601 // return to pos1 after a delay
602 ent->think = ReturnToPos1;
603 ent->nextthink = level.time + ent->wait;
604
605 // fire targets
606 if ( !ent->activator ) {
607 ent->activator = ent;
608 }
609 G_UseTargets( ent, ent->activator );
610 } else if ( ent->moverState == MOVER_2TO1 ) {
611 // reached pos1
612 SetMoverState( ent, MOVER_POS1, level.time );
613
614 // play sound
615 if ( ent->soundPos1 ) {
616 G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 );
617 }
618
619 // close areaportals
620 if ( ent->teammaster == ent || !ent->teammaster ) {
621 trap_AdjustAreaPortalState( ent, qfalse );
622 }
623 } else {
624 G_Error( "Reached_BinaryMover: bad moverState" );
625 }
626 }
627
628
629 /*
630 ================
631 Use_BinaryMover
632 ================
633 */
Use_BinaryMover(gentity_t * ent,gentity_t * other,gentity_t * activator)634 void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
635 int total;
636 int partial;
637
638 // only the master should be used
639 if ( ent->flags & FL_TEAMSLAVE ) {
640 Use_BinaryMover( ent->teammaster, other, activator );
641 return;
642 }
643
644 ent->activator = activator;
645
646 if ( ent->moverState == MOVER_POS1 ) {
647 // start moving 50 msec later, becase if this was player
648 // triggered, level.time hasn't been advanced yet
649 MatchTeam( ent, MOVER_1TO2, level.time + 50 );
650
651 // starting sound
652 if ( ent->sound1to2 ) {
653 G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
654 }
655
656 // looping sound
657 ent->s.loopSound = ent->soundLoop;
658
659 // open areaportal
660 if ( ent->teammaster == ent || !ent->teammaster ) {
661 trap_AdjustAreaPortalState( ent, qtrue );
662 }
663 return;
664 }
665
666 // if all the way up, just delay before coming down
667 if ( ent->moverState == MOVER_POS2 ) {
668 ent->nextthink = level.time + ent->wait;
669 return;
670 }
671
672 // only partway down before reversing
673 if ( ent->moverState == MOVER_2TO1 ) {
674 total = ent->s.pos.trDuration;
675 partial = level.time - ent->s.pos.trTime;
676 if ( partial > total ) {
677 partial = total;
678 }
679
680 MatchTeam( ent, MOVER_1TO2, level.time - ( total - partial ) );
681
682 if ( ent->sound1to2 ) {
683 G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
684 }
685 return;
686 }
687
688 // only partway up before reversing
689 if ( ent->moverState == MOVER_1TO2 ) {
690 total = ent->s.pos.trDuration;
691 partial = level.time - ent->s.pos.trTime;
692 if ( partial > total ) {
693 partial = total;
694 }
695
696 MatchTeam( ent, MOVER_2TO1, level.time - ( total - partial ) );
697
698 if ( ent->sound2to1 ) {
699 G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
700 }
701 return;
702 }
703 }
704
705
706
707 /*
708 ================
709 InitMover
710
711 "pos1", "pos2", and "speed" should be set before calling,
712 so the movement delta can be calculated
713 ================
714 */
InitMover(gentity_t * ent)715 void InitMover( gentity_t *ent ) {
716 vec3_t move;
717 float distance;
718 float light;
719 vec3_t color;
720 qboolean lightSet, colorSet;
721 char *sound;
722
723 // if the "model2" key is set, use a seperate model
724 // for drawing, but clip against the brushes
725 if ( ent->model2 ) {
726 ent->s.modelindex2 = G_ModelIndex( ent->model2 );
727 }
728
729 // if the "loopsound" key is set, use a constant looping sound when moving
730 if ( G_SpawnString( "noise", "100", &sound ) ) {
731 ent->s.loopSound = G_SoundIndex( sound );
732 }
733
734 // if the "color" or "light" keys are set, setup constantLight
735 lightSet = G_SpawnFloat( "light", "100", &light );
736 colorSet = G_SpawnVector( "color", "1 1 1", color );
737 if ( lightSet || colorSet ) {
738 int r, g, b, i;
739
740 r = color[0] * 255;
741 if ( r > 255 ) {
742 r = 255;
743 }
744 g = color[1] * 255;
745 if ( g > 255 ) {
746 g = 255;
747 }
748 b = color[2] * 255;
749 if ( b > 255 ) {
750 b = 255;
751 }
752 i = light / 4;
753 if ( i > 255 ) {
754 i = 255;
755 }
756 ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
757 }
758
759
760 ent->use = Use_BinaryMover;
761 ent->reached = Reached_BinaryMover;
762
763 ent->moverState = MOVER_POS1;
764 ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
765 ent->s.eType = ET_MOVER;
766 VectorCopy (ent->pos1, ent->r.currentOrigin);
767 trap_LinkEntity (ent);
768
769 ent->s.pos.trType = TR_STATIONARY;
770 VectorCopy( ent->pos1, ent->s.pos.trBase );
771
772 // calculate time to reach second position from speed
773 VectorSubtract( ent->pos2, ent->pos1, move );
774 distance = VectorLength( move );
775 if ( ! ent->speed ) {
776 ent->speed = 100;
777 }
778 VectorScale( move, ent->speed, ent->s.pos.trDelta );
779 ent->s.pos.trDuration = distance * 1000 / ent->speed;
780 if ( ent->s.pos.trDuration <= 0 ) {
781 ent->s.pos.trDuration = 1;
782 }
783 }
784
785
786 /*
787 ===============================================================================
788
789 DOOR
790
791 A use can be triggered either by a touch function, by being shot, or by being
792 targeted by another entity.
793
794 ===============================================================================
795 */
796
797 /*
798 ================
799 Blocked_Door
800 ================
801 */
Blocked_Door(gentity_t * ent,gentity_t * other)802 void Blocked_Door( gentity_t *ent, gentity_t *other ) {
803 // remove anything other than a client
804 if ( !other->client ) {
805 // except CTF flags!!!!
806 if( other->s.eType == ET_ITEM && other->item->giType == IT_TEAM ) {
807 Team_DroppedFlagThink( other );
808 return;
809 }
810 G_TempEntity( other->s.origin, EV_ITEM_POP );
811 G_FreeEntity( other );
812 return;
813 }
814
815 if ( ent->damage ) {
816 G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH );
817 }
818 if ( ent->spawnflags & 4 ) {
819 return; // crushers don't reverse
820 }
821
822 // reverse direction
823 Use_BinaryMover( ent, ent, other );
824 }
825
826 /*
827 ================
828 Touch_DoorTriggerSpectator
829 ================
830 */
Touch_DoorTriggerSpectator(gentity_t * ent,gentity_t * other,trace_t * trace)831 static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_t *trace ) {
832 int i, axis;
833 vec3_t origin, dir, angles;
834
835 axis = ent->count;
836 VectorClear(dir);
837 if (fabs(other->s.origin[axis] - ent->r.absmax[axis]) <
838 fabs(other->s.origin[axis] - ent->r.absmin[axis])) {
839 origin[axis] = ent->r.absmin[axis] - 10;
840 dir[axis] = -1;
841 }
842 else {
843 origin[axis] = ent->r.absmax[axis] + 10;
844 dir[axis] = 1;
845 }
846 for (i = 0; i < 3; i++) {
847 if (i == axis) continue;
848 origin[i] = (ent->r.absmin[i] + ent->r.absmax[i]) * 0.5;
849 }
850 vectoangles(dir, angles);
851 TeleportPlayer(other, origin, angles );
852 }
853
854 /*
855 ================
856 Touch_DoorTrigger
857 ================
858 */
Touch_DoorTrigger(gentity_t * ent,gentity_t * other,trace_t * trace)859 void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ) {
860 if ( other->client && other->client->sess.sessionTeam == TEAM_SPECTATOR ) {
861 // if the door is not open and not opening
862 if ( ent->parent->moverState != MOVER_1TO2 &&
863 ent->parent->moverState != MOVER_POS2) {
864 Touch_DoorTriggerSpectator( ent, other, trace );
865 }
866 }
867 else if ( ent->parent->moverState != MOVER_1TO2 ) {
868 Use_BinaryMover( ent->parent, ent, other );
869 }
870 }
871
872
873 /*
874 ======================
875 Think_SpawnNewDoorTrigger
876
877 All of the parts of a door have been spawned, so create
878 a trigger that encloses all of them
879 ======================
880 */
Think_SpawnNewDoorTrigger(gentity_t * ent)881 void Think_SpawnNewDoorTrigger( gentity_t *ent ) {
882 gentity_t *other;
883 vec3_t mins, maxs;
884 int i, best;
885
886 // set all of the slaves as shootable
887 for ( other = ent ; other ; other = other->teamchain ) {
888 other->takedamage = qtrue;
889 }
890
891 // find the bounds of everything on the team
892 VectorCopy (ent->r.absmin, mins);
893 VectorCopy (ent->r.absmax, maxs);
894
895 for (other = ent->teamchain ; other ; other=other->teamchain) {
896 AddPointToBounds (other->r.absmin, mins, maxs);
897 AddPointToBounds (other->r.absmax, mins, maxs);
898 }
899
900 // find the thinnest axis, which will be the one we expand
901 best = 0;
902 for ( i = 1 ; i < 3 ; i++ ) {
903 if ( maxs[i] - mins[i] < maxs[best] - mins[best] ) {
904 best = i;
905 }
906 }
907 maxs[best] += 120;
908 mins[best] -= 120;
909
910 // create a trigger with this size
911 other = G_Spawn ();
912 other->classname = "door_trigger";
913 VectorCopy (mins, other->r.mins);
914 VectorCopy (maxs, other->r.maxs);
915 other->parent = ent;
916 other->r.contents = CONTENTS_TRIGGER;
917 other->touch = Touch_DoorTrigger;
918 // remember the thinnest axis
919 other->count = best;
920 trap_LinkEntity (other);
921
922 MatchTeam( ent, ent->moverState, level.time );
923 }
924
Think_MatchTeam(gentity_t * ent)925 void Think_MatchTeam( gentity_t *ent ) {
926 MatchTeam( ent, ent->moverState, level.time );
927 }
928
929
930 /*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER
931 TOGGLE wait in both the start and end states for a trigger event.
932 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).
933 NOMONSTER monsters will not trigger this door
934
935 "model2" .md3 model to also draw
936 "angle" determines the opening direction
937 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
938 "speed" movement speed (100 default)
939 "wait" wait before returning (3 default, -1 = never return)
940 "lip" lip remaining at end of move (8 default)
941 "dmg" damage to inflict when blocked (2 default)
942 "color" constantLight color
943 "light" constantLight radius
944 "health" if set, the door must be shot open
945 */
SP_func_door(gentity_t * ent)946 void SP_func_door (gentity_t *ent) {
947 vec3_t abs_movedir;
948 float distance;
949 vec3_t size;
950 float lip;
951
952 ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/doors/dr1_strt.wav");
953 ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/doors/dr1_end.wav");
954
955 ent->blocked = Blocked_Door;
956
957 // default speed of 400
958 if (!ent->speed)
959 ent->speed = 400;
960
961 // default wait of 2 seconds
962 if (!ent->wait)
963 ent->wait = 2;
964 ent->wait *= 1000;
965
966 // default lip of 8 units
967 G_SpawnFloat( "lip", "8", &lip );
968
969 // default damage of 2 points
970 G_SpawnInt( "dmg", "2", &ent->damage );
971
972 // first position at start
973 VectorCopy( ent->s.origin, ent->pos1 );
974
975 // calculate second position
976 trap_SetBrushModel( ent, ent->model );
977 G_SetMovedir (ent->s.angles, ent->movedir);
978 abs_movedir[0] = fabs(ent->movedir[0]);
979 abs_movedir[1] = fabs(ent->movedir[1]);
980 abs_movedir[2] = fabs(ent->movedir[2]);
981 VectorSubtract( ent->r.maxs, ent->r.mins, size );
982 distance = DotProduct( abs_movedir, size ) - lip;
983 VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 );
984
985 // if "start_open", reverse position 1 and 2
986 if ( ent->spawnflags & 1 ) {
987 vec3_t temp;
988
989 VectorCopy( ent->pos2, temp );
990 VectorCopy( ent->s.origin, ent->pos2 );
991 VectorCopy( temp, ent->pos1 );
992 }
993
994 InitMover( ent );
995
996 ent->nextthink = level.time + FRAMETIME;
997
998 if ( ! (ent->flags & FL_TEAMSLAVE ) ) {
999 int health;
1000
1001 G_SpawnInt( "health", "0", &health );
1002 if ( health ) {
1003 ent->takedamage = qtrue;
1004 }
1005 if ( ent->targetname || health ) {
1006 // non touch/shoot doors
1007 ent->think = Think_MatchTeam;
1008 } else {
1009 ent->think = Think_SpawnNewDoorTrigger;
1010 }
1011 }
1012
1013
1014 }
1015
1016 /*
1017 ===============================================================================
1018
1019 PLAT
1020
1021 ===============================================================================
1022 */
1023
1024 /*
1025 ==============
1026 Touch_Plat
1027
1028 Don't allow decent if a living player is on it
1029 ===============
1030 */
Touch_Plat(gentity_t * ent,gentity_t * other,trace_t * trace)1031 void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) {
1032 if ( !other->client || other->client->ps.stats[STAT_HEALTH] <= 0 ) {
1033 return;
1034 }
1035
1036 // delay return-to-pos1 by one second
1037 if ( ent->moverState == MOVER_POS2 ) {
1038 ent->nextthink = level.time + 1000;
1039 }
1040 }
1041
1042 /*
1043 ==============
1044 Touch_PlatCenterTrigger
1045
1046 If the plat is at the bottom position, start it going up
1047 ===============
1048 */
Touch_PlatCenterTrigger(gentity_t * ent,gentity_t * other,trace_t * trace)1049 void Touch_PlatCenterTrigger(gentity_t *ent, gentity_t *other, trace_t *trace ) {
1050 if ( !other->client ) {
1051 return;
1052 }
1053
1054 if ( ent->parent->moverState == MOVER_POS1 ) {
1055 Use_BinaryMover( ent->parent, ent, other );
1056 }
1057 }
1058
1059
1060 /*
1061 ================
1062 SpawnPlatTrigger
1063
1064 Spawn a trigger in the middle of the plat's low position
1065 Elevator cars require that the trigger extend through the entire low position,
1066 not just sit on top of it.
1067 ================
1068 */
SpawnPlatTrigger(gentity_t * ent)1069 void SpawnPlatTrigger( gentity_t *ent ) {
1070 gentity_t *trigger;
1071 vec3_t tmin, tmax;
1072
1073 // the middle trigger will be a thin trigger just
1074 // above the starting position
1075 trigger = G_Spawn();
1076 trigger->classname = "plat_trigger";
1077 trigger->touch = Touch_PlatCenterTrigger;
1078 trigger->r.contents = CONTENTS_TRIGGER;
1079 trigger->parent = ent;
1080
1081 tmin[0] = ent->pos1[0] + ent->r.mins[0] + 33;
1082 tmin[1] = ent->pos1[1] + ent->r.mins[1] + 33;
1083 tmin[2] = ent->pos1[2] + ent->r.mins[2];
1084
1085 tmax[0] = ent->pos1[0] + ent->r.maxs[0] - 33;
1086 tmax[1] = ent->pos1[1] + ent->r.maxs[1] - 33;
1087 tmax[2] = ent->pos1[2] + ent->r.maxs[2] + 8;
1088
1089 if ( tmax[0] <= tmin[0] ) {
1090 tmin[0] = ent->pos1[0] + (ent->r.mins[0] + ent->r.maxs[0]) *0.5;
1091 tmax[0] = tmin[0] + 1;
1092 }
1093 if ( tmax[1] <= tmin[1] ) {
1094 tmin[1] = ent->pos1[1] + (ent->r.mins[1] + ent->r.maxs[1]) *0.5;
1095 tmax[1] = tmin[1] + 1;
1096 }
1097
1098 VectorCopy (tmin, trigger->r.mins);
1099 VectorCopy (tmax, trigger->r.maxs);
1100
1101 trap_LinkEntity (trigger);
1102 }
1103
1104
1105 /*QUAKED func_plat (0 .5 .8) ?
1106 Plats are always drawn in the extended position so they will light correctly.
1107
1108 "lip" default 8, protrusion above rest position
1109 "height" total height of movement, defaults to model height
1110 "speed" overrides default 200.
1111 "dmg" overrides default 2
1112 "model2" .md3 model to also draw
1113 "color" constantLight color
1114 "light" constantLight radius
1115 */
SP_func_plat(gentity_t * ent)1116 void SP_func_plat (gentity_t *ent) {
1117 float lip, height;
1118
1119 ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/plats/pt1_strt.wav");
1120 ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/plats/pt1_end.wav");
1121
1122 VectorClear (ent->s.angles);
1123
1124 G_SpawnFloat( "speed", "200", &ent->speed );
1125 G_SpawnInt( "dmg", "2", &ent->damage );
1126 G_SpawnFloat( "wait", "1", &ent->wait );
1127 G_SpawnFloat( "lip", "8", &lip );
1128
1129 ent->wait = 1000;
1130
1131 // create second position
1132 trap_SetBrushModel( ent, ent->model );
1133
1134 if ( !G_SpawnFloat( "height", "0", &height ) ) {
1135 height = (ent->r.maxs[2] - ent->r.mins[2]) - lip;
1136 }
1137
1138 // pos1 is the rest (bottom) position, pos2 is the top
1139 VectorCopy( ent->s.origin, ent->pos2 );
1140 VectorCopy( ent->pos2, ent->pos1 );
1141 ent->pos1[2] -= height;
1142
1143 InitMover( ent );
1144
1145 // touch function keeps the plat from returning while
1146 // a live player is standing on it
1147 ent->touch = Touch_Plat;
1148
1149 ent->blocked = Blocked_Door;
1150
1151 ent->parent = ent; // so it can be treated as a door
1152
1153 // spawn the trigger if one hasn't been custom made
1154 if ( !ent->targetname ) {
1155 SpawnPlatTrigger(ent);
1156 }
1157 }
1158
1159
1160 /*
1161 ===============================================================================
1162
1163 BUTTON
1164
1165 ===============================================================================
1166 */
1167
1168 /*
1169 ==============
1170 Touch_Button
1171
1172 ===============
1173 */
Touch_Button(gentity_t * ent,gentity_t * other,trace_t * trace)1174 void Touch_Button(gentity_t *ent, gentity_t *other, trace_t *trace ) {
1175 if ( !other->client ) {
1176 return;
1177 }
1178
1179 if ( ent->moverState == MOVER_POS1 ) {
1180 Use_BinaryMover( ent, other, other );
1181 }
1182 }
1183
1184
1185 /*QUAKED func_button (0 .5 .8) ?
1186 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.
1187
1188 "model2" .md3 model to also draw
1189 "angle" determines the opening direction
1190 "target" all entities with a matching targetname will be used
1191 "speed" override the default 40 speed
1192 "wait" override the default 1 second wait (-1 = never return)
1193 "lip" override the default 4 pixel lip remaining at end of move
1194 "health" if set, the button must be killed instead of touched
1195 "color" constantLight color
1196 "light" constantLight radius
1197 */
SP_func_button(gentity_t * ent)1198 void SP_func_button( gentity_t *ent ) {
1199 vec3_t abs_movedir;
1200 float distance;
1201 vec3_t size;
1202 float lip;
1203
1204 ent->sound1to2 = G_SoundIndex("sound/movers/switches/butn2.wav");
1205
1206 if ( !ent->speed ) {
1207 ent->speed = 40;
1208 }
1209
1210 if ( !ent->wait ) {
1211 ent->wait = 1;
1212 }
1213 ent->wait *= 1000;
1214
1215 // first position
1216 VectorCopy( ent->s.origin, ent->pos1 );
1217
1218 // calculate second position
1219 trap_SetBrushModel( ent, ent->model );
1220
1221 G_SpawnFloat( "lip", "4", &lip );
1222
1223 G_SetMovedir( ent->s.angles, ent->movedir );
1224 abs_movedir[0] = fabs(ent->movedir[0]);
1225 abs_movedir[1] = fabs(ent->movedir[1]);
1226 abs_movedir[2] = fabs(ent->movedir[2]);
1227 VectorSubtract( ent->r.maxs, ent->r.mins, size );
1228 distance = abs_movedir[0] * size[0] + abs_movedir[1] * size[1] + abs_movedir[2] * size[2] - lip;
1229 VectorMA (ent->pos1, distance, ent->movedir, ent->pos2);
1230
1231 if (ent->health) {
1232 // shootable button
1233 ent->takedamage = qtrue;
1234 } else {
1235 // touchable button
1236 ent->touch = Touch_Button;
1237 }
1238
1239 InitMover( ent );
1240 }
1241
1242
1243
1244 /*
1245 ===============================================================================
1246
1247 TRAIN
1248
1249 ===============================================================================
1250 */
1251
1252
1253 #define TRAIN_START_ON 1
1254 #define TRAIN_TOGGLE 2
1255 #define TRAIN_BLOCK_STOPS 4
1256
1257 /*
1258 ===============
1259 Think_BeginMoving
1260
1261 The wait time at a corner has completed, so start moving again
1262 ===============
1263 */
Think_BeginMoving(gentity_t * ent)1264 void Think_BeginMoving( gentity_t *ent ) {
1265 ent->s.pos.trTime = level.time;
1266 ent->s.pos.trType = TR_LINEAR_STOP;
1267 }
1268
1269 /*
1270 ===============
1271 Reached_Train
1272 ===============
1273 */
Reached_Train(gentity_t * ent)1274 void Reached_Train( gentity_t *ent ) {
1275 gentity_t *next;
1276 float speed;
1277 vec3_t move;
1278 float length;
1279
1280 // copy the apropriate values
1281 next = ent->nextTrain;
1282 if ( !next || !next->nextTrain ) {
1283 return; // just stop
1284 }
1285
1286 // fire all other targets
1287 G_UseTargets( next, NULL );
1288
1289 // set the new trajectory
1290 ent->nextTrain = next->nextTrain;
1291 VectorCopy( next->s.origin, ent->pos1 );
1292 VectorCopy( next->nextTrain->s.origin, ent->pos2 );
1293
1294 // if the path_corner has a speed, use that
1295 if ( next->speed ) {
1296 speed = next->speed;
1297 } else {
1298 // otherwise use the train's speed
1299 speed = ent->speed;
1300 }
1301 if ( speed < 1 ) {
1302 speed = 1;
1303 }
1304
1305 // calculate duration
1306 VectorSubtract( ent->pos2, ent->pos1, move );
1307 length = VectorLength( move );
1308
1309 ent->s.pos.trDuration = length * 1000 / speed;
1310
1311 // looping sound
1312 ent->s.loopSound = next->soundLoop;
1313
1314 // start it going
1315 SetMoverState( ent, MOVER_1TO2, level.time );
1316
1317 // if there is a "wait" value on the target, don't start moving yet
1318 if ( next->wait ) {
1319 ent->nextthink = level.time + next->wait * 1000;
1320 ent->think = Think_BeginMoving;
1321 ent->s.pos.trType = TR_STATIONARY;
1322 }
1323 }
1324
1325
1326 /*
1327 ===============
1328 Think_SetupTrainTargets
1329
1330 Link all the corners together
1331 ===============
1332 */
Think_SetupTrainTargets(gentity_t * ent)1333 void Think_SetupTrainTargets( gentity_t *ent ) {
1334 gentity_t *path, *next, *start;
1335
1336 ent->nextTrain = G_Find( NULL, FOFS(targetname), ent->target );
1337 if ( !ent->nextTrain ) {
1338 G_Printf( "func_train at %s with an unfound target\n",
1339 vtos(ent->r.absmin) );
1340 return;
1341 }
1342
1343 start = NULL;
1344 for ( path = ent->nextTrain ; path != start ; path = next ) {
1345 if ( !start ) {
1346 start = path;
1347 }
1348
1349 if ( !path->target ) {
1350 G_Printf( "Train corner at %s without a target\n",
1351 vtos(path->s.origin) );
1352 return;
1353 }
1354
1355 // find a path_corner among the targets
1356 // there may also be other targets that get fired when the corner
1357 // is reached
1358 next = NULL;
1359 do {
1360 next = G_Find( next, FOFS(targetname), path->target );
1361 if ( !next ) {
1362 G_Printf( "Train corner at %s without a target path_corner\n",
1363 vtos(path->s.origin) );
1364 return;
1365 }
1366 } while ( strcmp( next->classname, "path_corner" ) );
1367
1368 path->nextTrain = next;
1369 }
1370
1371 // start the train moving from the first corner
1372 Reached_Train( ent );
1373 }
1374
1375
1376
1377 /*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8)
1378 Train path corners.
1379 Target: next path corner and other targets to fire
1380 "speed" speed to move to the next corner
1381 "wait" seconds to wait before behining move to next corner
1382 */
SP_path_corner(gentity_t * self)1383 void SP_path_corner( gentity_t *self ) {
1384 if ( !self->targetname ) {
1385 G_Printf ("path_corner with no targetname at %s\n", vtos(self->s.origin));
1386 G_FreeEntity( self );
1387 return;
1388 }
1389 // path corners don't need to be linked in
1390 }
1391
1392
1393
1394 /*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
1395 A train is a mover that moves between path_corner target points.
1396 Trains MUST HAVE AN ORIGIN BRUSH.
1397 The train spawns at the first target it is pointing at.
1398 "model2" .md3 model to also draw
1399 "speed" default 100
1400 "dmg" default 2
1401 "noise" looping sound to play when the train is in motion
1402 "target" next path corner
1403 "color" constantLight color
1404 "light" constantLight radius
1405 */
SP_func_train(gentity_t * self)1406 void SP_func_train (gentity_t *self) {
1407 VectorClear (self->s.angles);
1408
1409 if (self->spawnflags & TRAIN_BLOCK_STOPS) {
1410 self->damage = 0;
1411 } else {
1412 if (!self->damage) {
1413 self->damage = 2;
1414 }
1415 }
1416
1417 if ( !self->speed ) {
1418 self->speed = 100;
1419 }
1420
1421 if ( !self->target ) {
1422 G_Printf ("func_train without a target at %s\n", vtos(self->r.absmin));
1423 G_FreeEntity( self );
1424 return;
1425 }
1426
1427 trap_SetBrushModel( self, self->model );
1428 InitMover( self );
1429
1430 self->reached = Reached_Train;
1431
1432 // start trains on the second frame, to make sure their targets have had
1433 // a chance to spawn
1434 self->nextthink = level.time + FRAMETIME;
1435 self->think = Think_SetupTrainTargets;
1436 }
1437
1438 /*
1439 ===============================================================================
1440
1441 STATIC
1442
1443 ===============================================================================
1444 */
1445
1446
1447 /*QUAKED func_static (0 .5 .8) ?
1448 A bmodel that just sits there, doing nothing. Can be used for conditional walls and models.
1449 "model2" .md3 model to also draw
1450 "color" constantLight color
1451 "light" constantLight radius
1452 */
SP_func_static(gentity_t * ent)1453 void SP_func_static( gentity_t *ent ) {
1454 trap_SetBrushModel( ent, ent->model );
1455 InitMover( ent );
1456 VectorCopy( ent->s.origin, ent->s.pos.trBase );
1457 VectorCopy( ent->s.origin, ent->r.currentOrigin );
1458 }
1459
1460
1461 /*
1462 ===============================================================================
1463
1464 ROTATING
1465
1466 ===============================================================================
1467 */
1468
1469
1470 /*QUAKED func_rotating (0 .5 .8) ? START_ON - X_AXIS Y_AXIS
1471 You need to have an origin brush as part of this entity. The center of that brush will be
1472 the point around which it is rotated. It will rotate around the Z axis by default. You can
1473 check either the X_AXIS or Y_AXIS box to change that.
1474
1475 "model2" .md3 model to also draw
1476 "speed" determines how fast it moves; default value is 100.
1477 "dmg" damage to inflict when blocked (2 default)
1478 "color" constantLight color
1479 "light" constantLight radius
1480 */
SP_func_rotating(gentity_t * ent)1481 void SP_func_rotating (gentity_t *ent) {
1482 if ( !ent->speed ) {
1483 ent->speed = 100;
1484 }
1485
1486 // set the axis of rotation
1487 ent->s.apos.trType = TR_LINEAR;
1488 if ( ent->spawnflags & 4 ) {
1489 ent->s.apos.trDelta[2] = ent->speed;
1490 } else if ( ent->spawnflags & 8 ) {
1491 ent->s.apos.trDelta[0] = ent->speed;
1492 } else {
1493 ent->s.apos.trDelta[1] = ent->speed;
1494 }
1495
1496 if (!ent->damage) {
1497 ent->damage = 2;
1498 }
1499
1500 trap_SetBrushModel( ent, ent->model );
1501 InitMover( ent );
1502
1503 VectorCopy( ent->s.origin, ent->s.pos.trBase );
1504 VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
1505 VectorCopy( ent->s.apos.trBase, ent->r.currentAngles );
1506
1507 trap_LinkEntity( ent );
1508 }
1509
1510
1511 /*
1512 ===============================================================================
1513
1514 BOBBING
1515
1516 ===============================================================================
1517 */
1518
1519
1520 /*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
1521 Normally bobs on the Z axis
1522 "model2" .md3 model to also draw
1523 "height" amplitude of bob (32 default)
1524 "speed" seconds to complete a bob cycle (4 default)
1525 "phase" the 0.0 to 1.0 offset in the cycle to start at
1526 "dmg" damage to inflict when blocked (2 default)
1527 "color" constantLight color
1528 "light" constantLight radius
1529 */
SP_func_bobbing(gentity_t * ent)1530 void SP_func_bobbing (gentity_t *ent) {
1531 float height;
1532 float phase;
1533
1534 G_SpawnFloat( "speed", "4", &ent->speed );
1535 G_SpawnFloat( "height", "32", &height );
1536 G_SpawnInt( "dmg", "2", &ent->damage );
1537 G_SpawnFloat( "phase", "0", &phase );
1538
1539 trap_SetBrushModel( ent, ent->model );
1540 InitMover( ent );
1541
1542 VectorCopy( ent->s.origin, ent->s.pos.trBase );
1543 VectorCopy( ent->s.origin, ent->r.currentOrigin );
1544
1545 ent->s.pos.trDuration = ent->speed * 1000;
1546 ent->s.pos.trTime = ent->s.pos.trDuration * phase;
1547 ent->s.pos.trType = TR_SINE;
1548
1549 // set the axis of bobbing
1550 if ( ent->spawnflags & 1 ) {
1551 ent->s.pos.trDelta[0] = height;
1552 } else if ( ent->spawnflags & 2 ) {
1553 ent->s.pos.trDelta[1] = height;
1554 } else {
1555 ent->s.pos.trDelta[2] = height;
1556 }
1557 }
1558
1559 /*
1560 ===============================================================================
1561
1562 PENDULUM
1563
1564 ===============================================================================
1565 */
1566
1567
1568 /*QUAKED func_pendulum (0 .5 .8) ?
1569 You need to have an origin brush as part of this entity.
1570 Pendulums always swing north / south on unrotated models. Add an angles field to the model to allow rotation in other directions.
1571 Pendulum frequency is a physical constant based on the length of the beam and gravity.
1572 "model2" .md3 model to also draw
1573 "speed" the number of degrees each way the pendulum swings, (30 default)
1574 "phase" the 0.0 to 1.0 offset in the cycle to start at
1575 "dmg" damage to inflict when blocked (2 default)
1576 "color" constantLight color
1577 "light" constantLight radius
1578 */
SP_func_pendulum(gentity_t * ent)1579 void SP_func_pendulum(gentity_t *ent) {
1580 float freq;
1581 float length;
1582 float phase;
1583 float speed;
1584
1585 G_SpawnFloat( "speed", "30", &speed );
1586 G_SpawnInt( "dmg", "2", &ent->damage );
1587 G_SpawnFloat( "phase", "0", &phase );
1588
1589 trap_SetBrushModel( ent, ent->model );
1590
1591 // find pendulum length
1592 length = fabs( ent->r.mins[2] );
1593 if ( length < 8 ) {
1594 length = 8;
1595 }
1596
1597 freq = 1 / ( M_PI * 2 ) * sqrt( g_gravity.value / ( 3 * length ) );
1598
1599 ent->s.pos.trDuration = ( 1000 / freq );
1600
1601 InitMover( ent );
1602
1603 VectorCopy( ent->s.origin, ent->s.pos.trBase );
1604 VectorCopy( ent->s.origin, ent->r.currentOrigin );
1605
1606 VectorCopy( ent->s.angles, ent->s.apos.trBase );
1607
1608 ent->s.apos.trDuration = 1000 / freq;
1609 ent->s.apos.trTime = ent->s.apos.trDuration * phase;
1610 ent->s.apos.trType = TR_SINE;
1611 ent->s.apos.trDelta[2] = speed;
1612 }
1613