1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
8
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 Doom 3 Source Code 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 Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 #include "sys/platform.h"
30 #include "gamesys/SysCvar.h"
31 #include "Entity.h"
32
33 #include "physics/Physics_Player.h"
34
35 CLASS_DECLARATION( idPhysics_Actor, idPhysics_Player )
36 END_CLASS
37
38 // movement parameters
39 const float PM_STOPSPEED = 100.0f;
40 const float PM_SWIMSCALE = 0.5f;
41 const float PM_LADDERSPEED = 100.0f;
42 const float PM_STEPSCALE = 1.0f;
43
44 const float PM_ACCELERATE = 10.0f;
45 const float PM_AIRACCELERATE = 1.0f;
46 const float PM_WATERACCELERATE = 4.0f;
47 const float PM_FLYACCELERATE = 8.0f;
48
49 const float PM_FRICTION = 6.0f;
50 const float PM_AIRFRICTION = 0.0f;
51 const float PM_WATERFRICTION = 1.0f;
52 const float PM_FLYFRICTION = 3.0f;
53 const float PM_NOCLIPFRICTION = 12.0f;
54
55 const float MIN_WALK_NORMAL = 0.7f; // can't walk on very steep slopes
56 const float OVERCLIP = 1.001f;
57
58 // movementFlags
59 const int PMF_DUCKED = 1; // set when ducking
60 const int PMF_JUMPED = 2; // set when the player jumped this frame
61 const int PMF_STEPPED_UP = 4; // set when the player stepped up this frame
62 const int PMF_STEPPED_DOWN = 8; // set when the player stepped down this frame
63 const int PMF_JUMP_HELD = 16; // set when jump button is held down
64 const int PMF_TIME_LAND = 32; // movementTime is time before rejump
65 const int PMF_TIME_KNOCKBACK = 64; // movementTime is an air-accelerate only time
66 const int PMF_TIME_WATERJUMP = 128; // movementTime is waterjump
67 const int PMF_ALL_TIMES = (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK);
68
69 int c_pmove = 0;
70
71 /*
72 ============
73 idPhysics_Player::CmdScale
74
75 Returns the scale factor to apply to cmd movements
76 This allows the clients to use axial -127 to 127 values for all directions
77 without getting a sqrt(2) distortion in speed.
78 ============
79 */
CmdScale(const usercmd_t & cmd) const80 float idPhysics_Player::CmdScale( const usercmd_t &cmd ) const {
81 int max;
82 float total;
83 float scale;
84 int forwardmove;
85 int rightmove;
86 int upmove;
87
88 forwardmove = cmd.forwardmove;
89 rightmove = cmd.rightmove;
90
91 // since the crouch key doubles as downward movement, ignore downward movement when we're on the ground
92 // otherwise crouch speed will be lower than specified
93 if ( walking ) {
94 upmove = 0;
95 } else {
96 upmove = cmd.upmove;
97 }
98
99 max = abs( forwardmove );
100 if ( abs( rightmove ) > max ) {
101 max = abs( rightmove );
102 }
103 if ( abs( upmove ) > max ) {
104 max = abs( upmove );
105 }
106
107 if ( !max ) {
108 return 0.0f;
109 }
110
111 total = idMath::Sqrt( (float) forwardmove * forwardmove + rightmove * rightmove + upmove * upmove );
112 scale = (float) playerSpeed * max / ( 127.0f * total );
113
114 return scale;
115 }
116
117 /*
118 ==============
119 idPhysics_Player::Accelerate
120
121 Handles user intended acceleration
122 ==============
123 */
Accelerate(const idVec3 & wishdir,const float wishspeed,const float accel)124 void idPhysics_Player::Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ) {
125 #if 1
126 // q2 style
127 float addspeed, accelspeed, currentspeed;
128
129 currentspeed = current.velocity * wishdir;
130 addspeed = wishspeed - currentspeed;
131 if (addspeed <= 0) {
132 return;
133 }
134 accelspeed = accel * frametime * wishspeed;
135 if (accelspeed > addspeed) {
136 accelspeed = addspeed;
137 }
138
139 current.velocity += accelspeed * wishdir;
140 #else
141 // proper way (avoids strafe jump maxspeed bug), but feels bad
142 idVec3 wishVelocity;
143 idVec3 pushDir;
144 float pushLen;
145 float canPush;
146
147 wishVelocity = wishdir * wishspeed;
148 pushDir = wishVelocity - current.velocity;
149 pushLen = pushDir.Normalize();
150
151 canPush = accel * frametime * wishspeed;
152 if (canPush > pushLen) {
153 canPush = pushLen;
154 }
155
156 current.velocity += canPush * pushDir;
157 #endif
158 }
159
160 /*
161 ==================
162 idPhysics_Player::SlideMove
163
164 Returns true if the velocity was clipped in some way
165 ==================
166 */
167 #define MAX_CLIP_PLANES 5
168
SlideMove(bool gravity,bool stepUp,bool stepDown,bool push)169 bool idPhysics_Player::SlideMove( bool gravity, bool stepUp, bool stepDown, bool push ) {
170 int i, j, k, pushFlags;
171 int bumpcount, numbumps, numplanes;
172 float d, time_left, into, totalMass;
173 idVec3 dir, planes[MAX_CLIP_PLANES];
174 idVec3 end, stepEnd, primal_velocity, endVelocity, endClipVelocity, clipVelocity;
175 trace_t trace, stepTrace, downTrace;
176 bool nearGround, stepped, pushed;
177
178 numbumps = 4;
179
180 primal_velocity = current.velocity;
181
182 if ( gravity ) {
183 endVelocity = current.velocity + gravityVector * frametime;
184 current.velocity = ( current.velocity + endVelocity ) * 0.5f;
185 primal_velocity = endVelocity;
186 if ( groundPlane ) {
187 // slide along the ground plane
188 current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
189 }
190 }
191 else {
192 endVelocity = current.velocity;
193 }
194
195 time_left = frametime;
196
197 // never turn against the ground plane
198 if ( groundPlane ) {
199 numplanes = 1;
200 planes[0] = groundTrace.c.normal;
201 } else {
202 numplanes = 0;
203 }
204
205 // never turn against original velocity
206 planes[numplanes] = current.velocity;
207 planes[numplanes].Normalize();
208 numplanes++;
209
210 for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) {
211
212 // calculate position we are trying to move to
213 end = current.origin + time_left * current.velocity;
214
215 // see if we can make it there
216 gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
217
218 time_left -= time_left * trace.fraction;
219 current.origin = trace.endpos;
220
221 // if moved the entire distance
222 if ( trace.fraction >= 1.0f ) {
223 break;
224 }
225
226 stepped = pushed = false;
227
228 // if we are allowed to step up
229 if ( stepUp ) {
230
231 nearGround = groundPlane | ladder;
232
233 if ( !nearGround ) {
234 // trace down to see if the player is near the ground
235 // step checking when near the ground allows the player to move up stairs smoothly while jumping
236 stepEnd = current.origin + maxStepHeight * gravityNormal;
237 gameLocal.clip.Translation( downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
238 nearGround = ( downTrace.fraction < 1.0f && (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL );
239 }
240
241 // may only step up if near the ground or on a ladder
242 if ( nearGround ) {
243
244 // step up
245 stepEnd = current.origin - maxStepHeight * gravityNormal;
246 gameLocal.clip.Translation( downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
247
248 // trace along velocity
249 stepEnd = downTrace.endpos + time_left * current.velocity;
250 gameLocal.clip.Translation( stepTrace, downTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
251
252 // step down
253 stepEnd = stepTrace.endpos + maxStepHeight * gravityNormal;
254 gameLocal.clip.Translation( downTrace, stepTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
255
256 if ( downTrace.fraction >= 1.0f || (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL ) {
257
258 // if moved the entire distance
259 if ( stepTrace.fraction >= 1.0f ) {
260 time_left = 0;
261 current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal;
262 current.origin = downTrace.endpos;
263 current.movementFlags |= PMF_STEPPED_UP;
264 current.velocity *= PM_STEPSCALE;
265 break;
266 }
267
268 // if the move is further when stepping up
269 if ( stepTrace.fraction > trace.fraction ) {
270 time_left -= time_left * stepTrace.fraction;
271 current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal;
272 current.origin = downTrace.endpos;
273 current.movementFlags |= PMF_STEPPED_UP;
274 current.velocity *= PM_STEPSCALE;
275 trace = stepTrace;
276 stepped = true;
277 }
278 }
279 }
280 }
281
282 // if we can push other entities and not blocked by the world
283 if ( push && trace.c.entityNum != ENTITYNUM_WORLD ) {
284
285 clipModel->SetPosition( current.origin, clipModel->GetAxis() );
286
287 // clip movement, only push idMoveables, don't push entities the player is standing on
288 // apply impact to pushed objects
289 pushFlags = PUSHFL_CLIP|PUSHFL_ONLYMOVEABLE|PUSHFL_NOGROUNDENTITIES|PUSHFL_APPLYIMPULSE;
290
291 // clip & push
292 totalMass = gameLocal.push.ClipTranslationalPush( trace, self, pushFlags, end, end - current.origin );
293
294 if ( totalMass > 0.0f ) {
295 // decrease velocity based on the total mass of the objects being pushed ?
296 current.velocity *= 1.0f - idMath::ClampFloat( 0.0f, 1000.0f, totalMass - 20.0f ) * ( 1.0f / 950.0f );
297 pushed = true;
298 }
299
300 current.origin = trace.endpos;
301 time_left -= time_left * trace.fraction;
302
303 // if moved the entire distance
304 if ( trace.fraction >= 1.0f ) {
305 break;
306 }
307 }
308
309 if ( !stepped ) {
310 // let the entity know about the collision
311 self->Collide( trace, current.velocity );
312 }
313
314 if ( numplanes >= MAX_CLIP_PLANES ) {
315 // MrElusive: I think we have some relatively high poly LWO models with a lot of slanted tris
316 // where it may hit the max clip planes
317 current.velocity = vec3_origin;
318 return true;
319 }
320
321 //
322 // if this is the same plane we hit before, nudge velocity
323 // out along it, which fixes some epsilon issues with
324 // non-axial planes
325 //
326 for ( i = 0; i < numplanes; i++ ) {
327 if ( ( trace.c.normal * planes[i] ) > 0.999f ) {
328 current.velocity += trace.c.normal;
329 break;
330 }
331 }
332 if ( i < numplanes ) {
333 continue;
334 }
335 planes[numplanes] = trace.c.normal;
336 numplanes++;
337
338 //
339 // modify velocity so it parallels all of the clip planes
340 //
341
342 // find a plane that it enters
343 for ( i = 0; i < numplanes; i++ ) {
344 into = current.velocity * planes[i];
345 if ( into >= 0.1f ) {
346 continue; // move doesn't interact with the plane
347 }
348
349 // slide along the plane
350 clipVelocity = current.velocity;
351 clipVelocity.ProjectOntoPlane( planes[i], OVERCLIP );
352
353 // slide along the plane
354 endClipVelocity = endVelocity;
355 endClipVelocity.ProjectOntoPlane( planes[i], OVERCLIP );
356
357 // see if there is a second plane that the new move enters
358 for ( j = 0; j < numplanes; j++ ) {
359 if ( j == i ) {
360 continue;
361 }
362 if ( ( clipVelocity * planes[j] ) >= 0.1f ) {
363 continue; // move doesn't interact with the plane
364 }
365
366 // try clipping the move to the plane
367 clipVelocity.ProjectOntoPlane( planes[j], OVERCLIP );
368 endClipVelocity.ProjectOntoPlane( planes[j], OVERCLIP );
369
370 // see if it goes back into the first clip plane
371 if ( ( clipVelocity * planes[i] ) >= 0 ) {
372 continue;
373 }
374
375 // slide the original velocity along the crease
376 dir = planes[i].Cross( planes[j] );
377 dir.Normalize();
378 d = dir * current.velocity;
379 clipVelocity = d * dir;
380
381 dir = planes[i].Cross( planes[j] );
382 dir.Normalize();
383 d = dir * endVelocity;
384 endClipVelocity = d * dir;
385
386 // see if there is a third plane the the new move enters
387 for ( k = 0; k < numplanes; k++ ) {
388 if ( k == i || k == j ) {
389 continue;
390 }
391 if ( ( clipVelocity * planes[k] ) >= 0.1f ) {
392 continue; // move doesn't interact with the plane
393 }
394
395 // stop dead at a tripple plane interaction
396 current.velocity = vec3_origin;
397 return true;
398 }
399 }
400
401 // if we have fixed all interactions, try another move
402 current.velocity = clipVelocity;
403 endVelocity = endClipVelocity;
404 break;
405 }
406 }
407
408 // step down
409 if ( stepDown && groundPlane ) {
410 stepEnd = current.origin + gravityNormal * maxStepHeight;
411 gameLocal.clip.Translation( downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self );
412 if ( downTrace.fraction > 1e-4f && downTrace.fraction < 1.0f ) {
413 current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal;
414 current.origin = downTrace.endpos;
415 current.movementFlags |= PMF_STEPPED_DOWN;
416 current.velocity *= PM_STEPSCALE;
417 }
418 }
419
420 if ( gravity ) {
421 current.velocity = endVelocity;
422 }
423
424 // come to a dead stop when the velocity orthogonal to the gravity flipped
425 clipVelocity = current.velocity - gravityNormal * current.velocity * gravityNormal;
426 endClipVelocity = endVelocity - gravityNormal * endVelocity * gravityNormal;
427 if ( clipVelocity * endClipVelocity < 0.0f ) {
428 current.velocity = gravityNormal * current.velocity * gravityNormal;
429 }
430
431 return (bool)( bumpcount == 0 );
432 }
433
434 /*
435 ==================
436 idPhysics_Player::Friction
437
438 Handles both ground friction and water friction
439 ==================
440 */
Friction(void)441 void idPhysics_Player::Friction( void ) {
442 idVec3 vel;
443 float speed, newspeed, control;
444 float drop;
445
446 vel = current.velocity;
447 if ( walking ) {
448 // ignore slope movement, remove all velocity in gravity direction
449 vel += (vel * gravityNormal) * gravityNormal;
450 }
451
452 speed = vel.Length();
453 if ( speed < 1.0f ) {
454 // remove all movement orthogonal to gravity, allows for sinking underwater
455 if ( fabs( current.velocity * gravityNormal ) < 1e-5f ) {
456 current.velocity.Zero();
457 } else {
458 current.velocity = (current.velocity * gravityNormal) * gravityNormal;
459 }
460 // FIXME: still have z friction underwater?
461 return;
462 }
463
464 drop = 0;
465
466 // spectator friction
467 if ( current.movementType == PM_SPECTATOR ) {
468 drop += speed * PM_FLYFRICTION * frametime;
469 }
470 // apply ground friction
471 else if ( walking && waterLevel <= WATERLEVEL_FEET ) {
472 // no friction on slick surfaces
473 if ( !(groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK) ) {
474 // if getting knocked back, no friction
475 if ( !(current.movementFlags & PMF_TIME_KNOCKBACK) ) {
476 control = speed < PM_STOPSPEED ? PM_STOPSPEED : speed;
477 drop += control * PM_FRICTION * frametime;
478 }
479 }
480 }
481 // apply water friction even if just wading
482 else if ( waterLevel ) {
483 drop += speed * PM_WATERFRICTION * waterLevel * frametime;
484 }
485 // apply air friction
486 else {
487 drop += speed * PM_AIRFRICTION * frametime;
488 }
489
490 // scale the velocity
491 newspeed = speed - drop;
492 if (newspeed < 0) {
493 newspeed = 0;
494 }
495 current.velocity *= ( newspeed / speed );
496 }
497
498 /*
499 ===================
500 idPhysics_Player::WaterJumpMove
501
502 Flying out of the water
503 ===================
504 */
WaterJumpMove(void)505 void idPhysics_Player::WaterJumpMove( void ) {
506
507 // waterjump has no control, but falls
508 idPhysics_Player::SlideMove( true, true, false, false );
509
510 // add gravity
511 current.velocity += gravityNormal * frametime;
512 // if falling down
513 if ( current.velocity * gravityNormal > 0.0f ) {
514 // cancel as soon as we are falling down again
515 current.movementFlags &= ~PMF_ALL_TIMES;
516 current.movementTime = 0;
517 }
518 }
519
520 /*
521 ===================
522 idPhysics_Player::WaterMove
523 ===================
524 */
WaterMove(void)525 void idPhysics_Player::WaterMove( void ) {
526 idVec3 wishvel;
527 float wishspeed;
528 idVec3 wishdir;
529 float scale;
530 float vel;
531
532 if ( idPhysics_Player::CheckWaterJump() ) {
533 idPhysics_Player::WaterJumpMove();
534 return;
535 }
536
537 idPhysics_Player::Friction();
538
539 scale = idPhysics_Player::CmdScale( command );
540
541 // user intentions
542 if ( !scale ) {
543 wishvel = gravityNormal * 60; // sink towards bottom
544 } else {
545 wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove);
546 wishvel -= scale * gravityNormal * command.upmove;
547 }
548
549 wishdir = wishvel;
550 wishspeed = wishdir.Normalize();
551
552 if ( wishspeed > playerSpeed * PM_SWIMSCALE ) {
553 wishspeed = playerSpeed * PM_SWIMSCALE;
554 }
555
556 idPhysics_Player::Accelerate( wishdir, wishspeed, PM_WATERACCELERATE );
557
558 // make sure we can go up slopes easily under water
559 if ( groundPlane && ( current.velocity * groundTrace.c.normal ) < 0.0f ) {
560 vel = current.velocity.Length();
561 // slide along the ground plane
562 current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
563
564 current.velocity.Normalize();
565 current.velocity *= vel;
566 }
567
568 idPhysics_Player::SlideMove( false, true, false, false );
569 }
570
571 /*
572 ===================
573 idPhysics_Player::FlyMove
574 ===================
575 */
FlyMove(void)576 void idPhysics_Player::FlyMove( void ) {
577 idVec3 wishvel;
578 float wishspeed;
579 idVec3 wishdir;
580 float scale;
581
582 // normal slowdown
583 idPhysics_Player::Friction();
584
585 scale = idPhysics_Player::CmdScale( command );
586
587 if ( !scale ) {
588 wishvel = vec3_origin;
589 } else {
590 wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove);
591 wishvel -= scale * gravityNormal * command.upmove;
592 }
593
594 wishdir = wishvel;
595 wishspeed = wishdir.Normalize();
596
597 idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE );
598
599 idPhysics_Player::SlideMove( false, false, false, false );
600 }
601
602 /*
603 ===================
604 idPhysics_Player::AirMove
605 ===================
606 */
AirMove(void)607 void idPhysics_Player::AirMove( void ) {
608 idVec3 wishvel;
609 idVec3 wishdir;
610 float wishspeed;
611 float scale;
612
613 idPhysics_Player::Friction();
614
615 scale = idPhysics_Player::CmdScale( command );
616
617 // project moves down to flat plane
618 viewForward -= (viewForward * gravityNormal) * gravityNormal;
619 viewRight -= (viewRight * gravityNormal) * gravityNormal;
620 viewForward.Normalize();
621 viewRight.Normalize();
622
623 wishvel = viewForward * command.forwardmove + viewRight * command.rightmove;
624 wishvel -= (wishvel * gravityNormal) * gravityNormal;
625 wishdir = wishvel;
626 wishspeed = wishdir.Normalize();
627 wishspeed *= scale;
628
629 // not on ground, so little effect on velocity
630 idPhysics_Player::Accelerate( wishdir, wishspeed, PM_AIRACCELERATE );
631
632 // we may have a ground plane that is very steep, even
633 // though we don't have a groundentity
634 // slide along the steep plane
635 if ( groundPlane ) {
636 current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
637 }
638
639 idPhysics_Player::SlideMove( true, false, false, false );
640 }
641
642 /*
643 ===================
644 idPhysics_Player::WalkMove
645 ===================
646 */
WalkMove(void)647 void idPhysics_Player::WalkMove( void ) {
648 idVec3 wishvel;
649 idVec3 wishdir;
650 float wishspeed;
651 float scale;
652 float accelerate;
653 idVec3 oldVelocity, vel;
654 float oldVel, newVel;
655
656 if ( waterLevel > WATERLEVEL_WAIST && ( viewForward * groundTrace.c.normal ) > 0.0f ) {
657 // begin swimming
658 idPhysics_Player::WaterMove();
659 return;
660 }
661
662 if ( idPhysics_Player::CheckJump() ) {
663 // jumped away
664 if ( waterLevel > WATERLEVEL_FEET ) {
665 idPhysics_Player::WaterMove();
666 }
667 else {
668 idPhysics_Player::AirMove();
669 }
670 return;
671 }
672
673 idPhysics_Player::Friction();
674
675 scale = idPhysics_Player::CmdScale( command );
676
677 // project moves down to flat plane
678 viewForward -= (viewForward * gravityNormal) * gravityNormal;
679 viewRight -= (viewRight * gravityNormal) * gravityNormal;
680
681 // project the forward and right directions onto the ground plane
682 viewForward.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
683 viewRight.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
684 //
685 viewForward.Normalize();
686 viewRight.Normalize();
687
688 wishvel = viewForward * command.forwardmove + viewRight * command.rightmove;
689 wishdir = wishvel;
690 wishspeed = wishdir.Normalize();
691 wishspeed *= scale;
692
693 // clamp the speed lower if wading or walking on the bottom
694 if ( waterLevel ) {
695 float waterScale;
696
697 waterScale = waterLevel / 3.0f;
698 waterScale = 1.0f - ( 1.0f - PM_SWIMSCALE ) * waterScale;
699 if ( wishspeed > playerSpeed * waterScale ) {
700 wishspeed = playerSpeed * waterScale;
701 }
702 }
703
704 // when a player gets hit, they temporarily lose full control, which allows them to be moved a bit
705 if ( ( groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK ) || current.movementFlags & PMF_TIME_KNOCKBACK ) {
706 accelerate = PM_AIRACCELERATE;
707 }
708 else {
709 accelerate = PM_ACCELERATE;
710 }
711
712 idPhysics_Player::Accelerate( wishdir, wishspeed, accelerate );
713
714 if ( ( groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK ) || current.movementFlags & PMF_TIME_KNOCKBACK ) {
715 current.velocity += gravityVector * frametime;
716 }
717
718 oldVelocity = current.velocity;
719
720 // slide along the ground plane
721 current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP );
722
723 // if not clipped into the opposite direction
724 if ( oldVelocity * current.velocity > 0.0f ) {
725 newVel = current.velocity.LengthSqr();
726 if ( newVel > 1.0f ) {
727 oldVel = oldVelocity.LengthSqr();
728 if ( oldVel > 1.0f ) {
729 // don't decrease velocity when going up or down a slope
730 current.velocity *= idMath::Sqrt( oldVel / newVel );
731 }
732 }
733 }
734
735 // don't do anything if standing still
736 vel = current.velocity - (current.velocity * gravityNormal) * gravityNormal;
737 if ( !vel.LengthSqr() ) {
738 return;
739 }
740
741 gameLocal.push.InitSavingPushedEntityPositions();
742
743 idPhysics_Player::SlideMove( false, true, true, true );
744 }
745
746 /*
747 ==============
748 idPhysics_Player::DeadMove
749 ==============
750 */
DeadMove(void)751 void idPhysics_Player::DeadMove( void ) {
752 float forward;
753
754 if ( !walking ) {
755 return;
756 }
757
758 // extra friction
759 forward = current.velocity.Length();
760 forward -= 20;
761 if ( forward <= 0 ) {
762 current.velocity = vec3_origin;
763 }
764 else {
765 current.velocity.Normalize();
766 current.velocity *= forward;
767 }
768 }
769
770 /*
771 ===============
772 idPhysics_Player::NoclipMove
773 ===============
774 */
NoclipMove(void)775 void idPhysics_Player::NoclipMove( void ) {
776 float speed, drop, friction, newspeed, stopspeed;
777 float scale, wishspeed;
778 idVec3 wishdir;
779
780 // friction
781 speed = current.velocity.Length();
782 if ( speed < 20.0f ) {
783 current.velocity = vec3_origin;
784 }
785 else {
786 stopspeed = playerSpeed * 0.3f;
787 if ( speed < stopspeed ) {
788 speed = stopspeed;
789 }
790 friction = PM_NOCLIPFRICTION;
791 drop = speed * friction * frametime;
792
793 // scale the velocity
794 newspeed = speed - drop;
795 if (newspeed < 0) {
796 newspeed = 0;
797 }
798
799 current.velocity *= newspeed / speed;
800 }
801
802 // accelerate
803 scale = idPhysics_Player::CmdScale( command );
804
805 wishdir = scale * (viewForward * command.forwardmove + viewRight * command.rightmove);
806 wishdir -= scale * gravityNormal * command.upmove;
807 wishspeed = wishdir.Normalize();
808 wishspeed *= scale;
809
810 idPhysics_Player::Accelerate( wishdir, wishspeed, PM_ACCELERATE );
811
812 // move
813 current.origin += frametime * current.velocity;
814 }
815
816 /*
817 ===============
818 idPhysics_Player::SpectatorMove
819 ===============
820 */
SpectatorMove(void)821 void idPhysics_Player::SpectatorMove( void ) {
822 idVec3 wishvel;
823 float wishspeed;
824 idVec3 wishdir;
825 float scale;
826
827 idVec3 end;
828
829 // fly movement
830
831 idPhysics_Player::Friction();
832
833 scale = idPhysics_Player::CmdScale( command );
834
835 if ( !scale ) {
836 wishvel = vec3_origin;
837 } else {
838 wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove);
839 }
840
841 wishdir = wishvel;
842 wishspeed = wishdir.Normalize();
843
844 idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE );
845
846 idPhysics_Player::SlideMove( false, false, false, false );
847 }
848
849 /*
850 ============
851 idPhysics_Player::LadderMove
852 ============
853 */
LadderMove(void)854 void idPhysics_Player::LadderMove( void ) {
855 idVec3 wishdir, wishvel, right;
856 float wishspeed, scale;
857 float upscale;
858
859 // stick to the ladder
860 wishvel = -100.0f * ladderNormal;
861 current.velocity = (gravityNormal * current.velocity) * gravityNormal + wishvel;
862
863 upscale = (-gravityNormal * viewForward + 0.5f) * 2.5f;
864 if ( upscale > 1.0f ) {
865 upscale = 1.0f;
866 }
867 else if ( upscale < -1.0f ) {
868 upscale = -1.0f;
869 }
870
871 scale = idPhysics_Player::CmdScale( command );
872 wishvel = -0.9f * gravityNormal * upscale * scale * (float)command.forwardmove;
873
874 // strafe
875 if ( command.rightmove ) {
876 // right vector orthogonal to gravity
877 right = viewRight - (gravityNormal * viewRight) * gravityNormal;
878 // project right vector into ladder plane
879 right = right - (ladderNormal * right) * ladderNormal;
880 right.Normalize();
881
882 // if we are looking away from the ladder, reverse the right vector
883 if ( ladderNormal * viewForward > 0.0f ) {
884 right = -right;
885 }
886 wishvel += 2.0f * right * scale * (float) command.rightmove;
887 }
888
889 // up down movement
890 if ( command.upmove ) {
891 wishvel += -0.5f * gravityNormal * scale * (float) command.upmove;
892 }
893
894 // do strafe friction
895 idPhysics_Player::Friction();
896
897 // accelerate
898 wishspeed = wishvel.Normalize();
899 idPhysics_Player::Accelerate( wishvel, wishspeed, PM_ACCELERATE );
900
901 // cap the vertical velocity
902 upscale = current.velocity * -gravityNormal;
903 if ( upscale < -PM_LADDERSPEED ) {
904 current.velocity += gravityNormal * (upscale + PM_LADDERSPEED);
905 }
906 else if ( upscale > PM_LADDERSPEED ) {
907 current.velocity += gravityNormal * (upscale - PM_LADDERSPEED);
908 }
909
910 if ( (wishvel * gravityNormal) == 0.0f ) {
911 if ( current.velocity * gravityNormal < 0.0f ) {
912 current.velocity += gravityVector * frametime;
913 if ( current.velocity * gravityNormal > 0.0f ) {
914 current.velocity -= (gravityNormal * current.velocity) * gravityNormal;
915 }
916 }
917 else {
918 current.velocity -= gravityVector * frametime;
919 if ( current.velocity * gravityNormal < 0.0f ) {
920 current.velocity -= (gravityNormal * current.velocity) * gravityNormal;
921 }
922 }
923 }
924
925 idPhysics_Player::SlideMove( false, ( command.forwardmove > 0 ), false, false );
926 }
927
928 /*
929 =============
930 idPhysics_Player::CorrectAllSolid
931 =============
932 */
CorrectAllSolid(trace_t & trace,int contents)933 void idPhysics_Player::CorrectAllSolid( trace_t &trace, int contents ) {
934 if ( debugLevel ) {
935 gameLocal.Printf( "%i:allsolid\n", c_pmove );
936 }
937
938 // FIXME: jitter around to find a free spot ?
939
940 if ( trace.fraction >= 1.0f ) {
941 memset( &trace, 0, sizeof( trace ) );
942 trace.endpos = current.origin;
943 trace.endAxis = clipModelAxis;
944 trace.fraction = 0.0f;
945 trace.c.dist = current.origin.z;
946 trace.c.normal.Set( 0, 0, 1 );
947 trace.c.point = current.origin;
948 trace.c.entityNum = ENTITYNUM_WORLD;
949 trace.c.id = 0;
950 trace.c.type = CONTACT_TRMVERTEX;
951 trace.c.material = NULL;
952 trace.c.contents = contents;
953 }
954 }
955
956 /*
957 =============
958 idPhysics_Player::CheckGround
959 =============
960 */
CheckGround(void)961 void idPhysics_Player::CheckGround( void ) {
962 int i, contents;
963 idVec3 point;
964 bool hadGroundContacts;
965
966 hadGroundContacts = HasGroundContacts();
967
968 // set the clip model origin before getting the contacts
969 clipModel->SetPosition( current.origin, clipModel->GetAxis() );
970
971 EvaluateContacts();
972
973 // setup a ground trace from the contacts
974 groundTrace.endpos = current.origin;
975 groundTrace.endAxis = clipModel->GetAxis();
976 if ( contacts.Num() ) {
977 groundTrace.fraction = 0.0f;
978 groundTrace.c = contacts[0];
979 for ( i = 1; i < contacts.Num(); i++ ) {
980 groundTrace.c.normal += contacts[i].normal;
981 }
982 groundTrace.c.normal.Normalize();
983 } else {
984 groundTrace.fraction = 1.0f;
985 }
986
987 contents = gameLocal.clip.Contents( current.origin, clipModel, clipModel->GetAxis(), -1, self );
988 if ( contents & MASK_SOLID ) {
989 // do something corrective if stuck in solid
990 idPhysics_Player::CorrectAllSolid( groundTrace, contents );
991 }
992
993 // if the trace didn't hit anything, we are in free fall
994 if ( groundTrace.fraction == 1.0f ) {
995 groundPlane = false;
996 walking = false;
997 groundEntityPtr = NULL;
998 return;
999 }
1000
1001 groundMaterial = groundTrace.c.material;
1002 groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ];
1003
1004 // check if getting thrown off the ground
1005 if ( (current.velocity * -gravityNormal) > 0.0f && ( current.velocity * groundTrace.c.normal ) > 10.0f ) {
1006 if ( debugLevel ) {
1007 gameLocal.Printf( "%i:kickoff\n", c_pmove );
1008 }
1009
1010 groundPlane = false;
1011 walking = false;
1012 return;
1013 }
1014
1015 // slopes that are too steep will not be considered onground
1016 if ( ( groundTrace.c.normal * -gravityNormal ) < MIN_WALK_NORMAL ) {
1017 if ( debugLevel ) {
1018 gameLocal.Printf( "%i:steep\n", c_pmove );
1019 }
1020
1021 // FIXME: if they can't slide down the slope, let them walk (sharp crevices)
1022
1023 // make sure we don't die from sliding down a steep slope
1024 if ( current.velocity * gravityNormal > 150.0f ) {
1025 current.velocity -= ( current.velocity * gravityNormal - 150.0f ) * gravityNormal;
1026 }
1027
1028 groundPlane = true;
1029 walking = false;
1030 return;
1031 }
1032
1033 groundPlane = true;
1034 walking = true;
1035
1036 // hitting solid ground will end a waterjump
1037 if ( current.movementFlags & PMF_TIME_WATERJUMP ) {
1038 current.movementFlags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND );
1039 current.movementTime = 0;
1040 }
1041
1042 // if the player didn't have ground contacts the previous frame
1043 if ( !hadGroundContacts ) {
1044
1045 // don't do landing time if we were just going down a slope
1046 if ( (current.velocity * -gravityNormal) < -200.0f ) {
1047 // don't allow another jump for a little while
1048 current.movementFlags |= PMF_TIME_LAND;
1049 current.movementTime = 250;
1050 }
1051 }
1052
1053 // let the entity know about the collision
1054 self->Collide( groundTrace, current.velocity );
1055
1056 if ( groundEntityPtr.GetEntity() ) {
1057 impactInfo_t info;
1058 groundEntityPtr.GetEntity()->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info );
1059 if ( info.invMass != 0.0f ) {
1060 groundEntityPtr.GetEntity()->ApplyImpulse( self, groundTrace.c.id, groundTrace.c.point, current.velocity / ( info.invMass * 10.0f ) );
1061 }
1062 }
1063 }
1064
1065 /*
1066 ==============
1067 idPhysics_Player::CheckDuck
1068
1069 Sets clip model size
1070 ==============
1071 */
CheckDuck(void)1072 void idPhysics_Player::CheckDuck( void ) {
1073 trace_t trace;
1074 idVec3 end;
1075 idBounds bounds;
1076 float maxZ;
1077
1078 if ( current.movementType == PM_DEAD ) {
1079 maxZ = pm_deadheight.GetFloat();
1080 } else {
1081 // stand up when up against a ladder
1082 if ( command.upmove < 0 && !ladder ) {
1083 // duck
1084 current.movementFlags |= PMF_DUCKED;
1085 } else {
1086 // stand up if possible
1087 if ( current.movementFlags & PMF_DUCKED ) {
1088 // try to stand up
1089 end = current.origin - ( pm_normalheight.GetFloat() - pm_crouchheight.GetFloat() ) * gravityNormal;
1090 gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
1091 if ( trace.fraction >= 1.0f ) {
1092 current.movementFlags &= ~PMF_DUCKED;
1093 }
1094 }
1095 }
1096
1097 if ( current.movementFlags & PMF_DUCKED ) {
1098 playerSpeed = crouchSpeed;
1099 maxZ = pm_crouchheight.GetFloat();
1100 } else {
1101 maxZ = pm_normalheight.GetFloat();
1102 }
1103 }
1104 // if the clipModel height should change
1105 if ( clipModel->GetBounds()[1][2] != maxZ ) {
1106
1107 bounds = clipModel->GetBounds();
1108 bounds[1][2] = maxZ;
1109 if ( pm_usecylinder.GetBool() ) {
1110 clipModel->LoadModel( idTraceModel( bounds, 8 ) );
1111 } else {
1112 clipModel->LoadModel( idTraceModel( bounds ) );
1113 }
1114 }
1115 }
1116
1117 /*
1118 ================
1119 idPhysics_Player::CheckLadder
1120 ================
1121 */
CheckLadder(void)1122 void idPhysics_Player::CheckLadder( void ) {
1123 idVec3 forward, start, end;
1124 trace_t trace;
1125 float tracedist;
1126
1127 if ( current.movementTime ) {
1128 return;
1129 }
1130
1131 // if on the ground moving backwards
1132 if ( walking && command.forwardmove <= 0 ) {
1133 return;
1134 }
1135
1136 // forward vector orthogonal to gravity
1137 forward = viewForward - (gravityNormal * viewForward) * gravityNormal;
1138 forward.Normalize();
1139
1140 if ( walking ) {
1141 // don't want to get sucked towards the ladder when still walking
1142 tracedist = 1.0f;
1143 } else {
1144 tracedist = 48.0f;
1145 }
1146
1147 end = current.origin + tracedist * forward;
1148 gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
1149
1150 // if near a surface
1151 if ( trace.fraction < 1.0f ) {
1152
1153 // if a ladder surface
1154 if ( trace.c.material && ( trace.c.material->GetSurfaceFlags() & SURF_LADDER ) ) {
1155
1156 // check a step height higher
1157 end = current.origin - gravityNormal * ( maxStepHeight * 0.75f );
1158 gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self );
1159 start = trace.endpos;
1160 end = start + tracedist * forward;
1161 gameLocal.clip.Translation( trace, start, end, clipModel, clipModel->GetAxis(), clipMask, self );
1162
1163 // if also near a surface a step height higher
1164 if ( trace.fraction < 1.0f ) {
1165
1166 // if it also is a ladder surface
1167 if ( trace.c.material && trace.c.material->GetSurfaceFlags() & SURF_LADDER ) {
1168 ladder = true;
1169 ladderNormal = trace.c.normal;
1170 }
1171 }
1172 }
1173 }
1174 }
1175
1176 /*
1177 =============
1178 idPhysics_Player::CheckJump
1179 =============
1180 */
CheckJump(void)1181 bool idPhysics_Player::CheckJump( void ) {
1182 idVec3 addVelocity;
1183
1184 if ( command.upmove < 10 ) {
1185 // not holding jump
1186 return false;
1187 }
1188
1189 // must wait for jump to be released
1190 if ( current.movementFlags & PMF_JUMP_HELD ) {
1191 return false;
1192 }
1193
1194 // don't jump if we can't stand up
1195 if ( current.movementFlags & PMF_DUCKED ) {
1196 return false;
1197 }
1198
1199 groundPlane = false; // jumping away
1200 walking = false;
1201 current.movementFlags |= PMF_JUMP_HELD | PMF_JUMPED;
1202
1203 addVelocity = 2.0f * maxJumpHeight * -gravityVector;
1204 addVelocity *= idMath::Sqrt( addVelocity.Normalize() );
1205 current.velocity += addVelocity;
1206
1207 return true;
1208 }
1209
1210 /*
1211 =============
1212 idPhysics_Player::CheckWaterJump
1213 =============
1214 */
CheckWaterJump(void)1215 bool idPhysics_Player::CheckWaterJump( void ) {
1216 idVec3 spot;
1217 int cont;
1218 idVec3 flatforward;
1219
1220 if ( current.movementTime ) {
1221 return false;
1222 }
1223
1224 // check for water jump
1225 if ( waterLevel != WATERLEVEL_WAIST ) {
1226 return false;
1227 }
1228
1229 flatforward = viewForward - (viewForward * gravityNormal) * gravityNormal;
1230 flatforward.Normalize();
1231
1232 spot = current.origin + 30.0f * flatforward;
1233 spot -= 4.0f * gravityNormal;
1234 cont = gameLocal.clip.Contents( spot, NULL, mat3_identity, -1, self );
1235 if ( !(cont & CONTENTS_SOLID) ) {
1236 return false;
1237 }
1238
1239 spot -= 16.0f * gravityNormal;
1240 cont = gameLocal.clip.Contents( spot, NULL, mat3_identity, -1, self );
1241 if ( cont ) {
1242 return false;
1243 }
1244
1245 // jump out of water
1246 current.velocity = 200.0f * viewForward - 350.0f * gravityNormal;
1247 current.movementFlags |= PMF_TIME_WATERJUMP;
1248 current.movementTime = 2000;
1249
1250 return true;
1251 }
1252
1253 /*
1254 =============
1255 idPhysics_Player::SetWaterLevel
1256 =============
1257 */
SetWaterLevel(void)1258 void idPhysics_Player::SetWaterLevel( void ) {
1259 idVec3 point;
1260 idBounds bounds;
1261 int contents;
1262
1263 //
1264 // get waterlevel, accounting for ducking
1265 //
1266 waterLevel = WATERLEVEL_NONE;
1267 waterType = 0;
1268
1269 bounds = clipModel->GetBounds();
1270
1271 // check at feet level
1272 point = current.origin - ( bounds[0][2] + 1.0f ) * gravityNormal;
1273 contents = gameLocal.clip.Contents( point, NULL, mat3_identity, -1, self );
1274 if ( contents & MASK_WATER ) {
1275
1276 waterType = contents;
1277 waterLevel = WATERLEVEL_FEET;
1278
1279 // check at waist level
1280 point = current.origin - ( bounds[1][2] - bounds[0][2] ) * 0.5f * gravityNormal;
1281 contents = gameLocal.clip.Contents( point, NULL, mat3_identity, -1, self );
1282 if ( contents & MASK_WATER ) {
1283
1284 waterLevel = WATERLEVEL_WAIST;
1285
1286 // check at head level
1287 point = current.origin - ( bounds[1][2] - 1.0f ) * gravityNormal;
1288 contents = gameLocal.clip.Contents( point, NULL, mat3_identity, -1, self );
1289 if ( contents & MASK_WATER ) {
1290 waterLevel = WATERLEVEL_HEAD;
1291 }
1292 }
1293 }
1294 }
1295
1296 /*
1297 ================
1298 idPhysics_Player::DropTimers
1299 ================
1300 */
DropTimers(void)1301 void idPhysics_Player::DropTimers( void ) {
1302 // drop misc timing counter
1303 if ( current.movementTime ) {
1304 if ( framemsec >= current.movementTime ) {
1305 current.movementFlags &= ~PMF_ALL_TIMES;
1306 current.movementTime = 0;
1307 }
1308 else {
1309 current.movementTime -= framemsec;
1310 }
1311 }
1312 }
1313
1314 /*
1315 ================
1316 idPhysics_Player::MovePlayer
1317 ================
1318 */
MovePlayer(int msec)1319 void idPhysics_Player::MovePlayer( int msec ) {
1320
1321 // this counter lets us debug movement problems with a journal
1322 // by setting a conditional breakpoint for the previous frame
1323 c_pmove++;
1324
1325 walking = false;
1326 groundPlane = false;
1327 ladder = false;
1328
1329 // determine the time
1330 framemsec = msec;
1331 frametime = framemsec * 0.001f;
1332
1333 // default speed
1334 playerSpeed = walkSpeed;
1335
1336 // remove jumped and stepped up flag
1337 current.movementFlags &= ~(PMF_JUMPED|PMF_STEPPED_UP|PMF_STEPPED_DOWN);
1338 current.stepUp = 0.0f;
1339
1340 if ( command.upmove < 10 ) {
1341 // not holding jump
1342 current.movementFlags &= ~PMF_JUMP_HELD;
1343 }
1344
1345 // if no movement at all
1346 if ( current.movementType == PM_FREEZE ) {
1347 return;
1348 }
1349
1350 // move the player velocity into the frame of a pusher
1351 current.velocity -= current.pushVelocity;
1352
1353 // view vectors
1354 viewAngles.ToVectors( &viewForward, NULL, NULL );
1355 viewForward *= clipModelAxis;
1356 viewRight = gravityNormal.Cross( viewForward );
1357 viewRight.Normalize();
1358
1359 // fly in spectator mode
1360 if ( current.movementType == PM_SPECTATOR ) {
1361 SpectatorMove();
1362 idPhysics_Player::DropTimers();
1363 return;
1364 }
1365
1366 // special no clip mode
1367 if ( current.movementType == PM_NOCLIP ) {
1368 idPhysics_Player::NoclipMove();
1369 idPhysics_Player::DropTimers();
1370 return;
1371 }
1372
1373 // no control when dead
1374 if ( current.movementType == PM_DEAD ) {
1375 command.forwardmove = 0;
1376 command.rightmove = 0;
1377 command.upmove = 0;
1378 }
1379
1380 // set watertype and waterlevel
1381 idPhysics_Player::SetWaterLevel();
1382
1383 // check for ground
1384 idPhysics_Player::CheckGround();
1385
1386 // check if up against a ladder
1387 idPhysics_Player::CheckLadder();
1388
1389 // set clip model size
1390 idPhysics_Player::CheckDuck();
1391
1392 // handle timers
1393 idPhysics_Player::DropTimers();
1394
1395 // move
1396 if ( current.movementType == PM_DEAD ) {
1397 // dead
1398 idPhysics_Player::DeadMove();
1399 }
1400 else if ( ladder ) {
1401 // going up or down a ladder
1402 idPhysics_Player::LadderMove();
1403 }
1404 else if ( current.movementFlags & PMF_TIME_WATERJUMP ) {
1405 // jumping out of water
1406 idPhysics_Player::WaterJumpMove();
1407 }
1408 else if ( waterLevel > 1 ) {
1409 // swimming
1410 idPhysics_Player::WaterMove();
1411 }
1412 else if ( walking ) {
1413 // walking on ground
1414 idPhysics_Player::WalkMove();
1415 }
1416 else {
1417 // airborne
1418 idPhysics_Player::AirMove();
1419 }
1420
1421 // set watertype, waterlevel and groundentity
1422 idPhysics_Player::SetWaterLevel();
1423 idPhysics_Player::CheckGround();
1424
1425 // move the player velocity back into the world frame
1426 current.velocity += current.pushVelocity;
1427 current.pushVelocity.Zero();
1428 }
1429
1430 /*
1431 ================
1432 idPhysics_Player::GetWaterLevel
1433 ================
1434 */
GetWaterLevel(void) const1435 waterLevel_t idPhysics_Player::GetWaterLevel( void ) const {
1436 return waterLevel;
1437 }
1438
1439 /*
1440 ================
1441 idPhysics_Player::GetWaterType
1442 ================
1443 */
GetWaterType(void) const1444 int idPhysics_Player::GetWaterType( void ) const {
1445 return waterType;
1446 }
1447
1448 /*
1449 ================
1450 idPhysics_Player::HasJumped
1451 ================
1452 */
HasJumped(void) const1453 bool idPhysics_Player::HasJumped( void ) const {
1454 return ( ( current.movementFlags & PMF_JUMPED ) != 0 );
1455 }
1456
1457 /*
1458 ================
1459 idPhysics_Player::HasSteppedUp
1460 ================
1461 */
HasSteppedUp(void) const1462 bool idPhysics_Player::HasSteppedUp( void ) const {
1463 return ( ( current.movementFlags & ( PMF_STEPPED_UP | PMF_STEPPED_DOWN ) ) != 0 );
1464 }
1465
1466 /*
1467 ================
1468 idPhysics_Player::GetStepUp
1469 ================
1470 */
GetStepUp(void) const1471 float idPhysics_Player::GetStepUp( void ) const {
1472 return current.stepUp;
1473 }
1474
1475 /*
1476 ================
1477 idPhysics_Player::IsCrouching
1478 ================
1479 */
IsCrouching(void) const1480 bool idPhysics_Player::IsCrouching( void ) const {
1481 return ( ( current.movementFlags & PMF_DUCKED ) != 0 );
1482 }
1483
1484 /*
1485 ================
1486 idPhysics_Player::OnLadder
1487 ================
1488 */
OnLadder(void) const1489 bool idPhysics_Player::OnLadder( void ) const {
1490 return ladder;
1491 }
1492
1493 /*
1494 ================
1495 idPhysics_Player::idPhysics_Player
1496 ================
1497 */
idPhysics_Player(void)1498 idPhysics_Player::idPhysics_Player( void ) {
1499 debugLevel = false;
1500 clipModel = NULL;
1501 clipMask = 0;
1502 memset( ¤t, 0, sizeof( current ) );
1503 saved = current;
1504 walkSpeed = 0;
1505 crouchSpeed = 0;
1506 maxStepHeight = 0;
1507 maxJumpHeight = 0;
1508 memset( &command, 0, sizeof( command ) );
1509 viewAngles.Zero();
1510 framemsec = 0;
1511 frametime = 0;
1512 playerSpeed = 0;
1513 viewForward.Zero();
1514 viewRight.Zero();
1515 walking = false;
1516 groundPlane = false;
1517 memset( &groundTrace, 0, sizeof( groundTrace ) );
1518 groundMaterial = NULL;
1519 ladder = false;
1520 ladderNormal.Zero();
1521 waterLevel = WATERLEVEL_NONE;
1522 waterType = 0;
1523 }
1524
1525 /*
1526 ================
1527 idPhysics_Player_SavePState
1528 ================
1529 */
idPhysics_Player_SavePState(idSaveGame * savefile,const playerPState_t & state)1530 void idPhysics_Player_SavePState( idSaveGame *savefile, const playerPState_t &state ) {
1531 savefile->WriteVec3( state.origin );
1532 savefile->WriteVec3( state.velocity );
1533 savefile->WriteVec3( state.localOrigin );
1534 savefile->WriteVec3( state.pushVelocity );
1535 savefile->WriteFloat( state.stepUp );
1536 savefile->WriteInt( state.movementType );
1537 savefile->WriteInt( state.movementFlags );
1538 savefile->WriteInt( state.movementTime );
1539 }
1540
1541 /*
1542 ================
1543 idPhysics_Player_RestorePState
1544 ================
1545 */
idPhysics_Player_RestorePState(idRestoreGame * savefile,playerPState_t & state)1546 void idPhysics_Player_RestorePState( idRestoreGame *savefile, playerPState_t &state ) {
1547 savefile->ReadVec3( state.origin );
1548 savefile->ReadVec3( state.velocity );
1549 savefile->ReadVec3( state.localOrigin );
1550 savefile->ReadVec3( state.pushVelocity );
1551 savefile->ReadFloat( state.stepUp );
1552 savefile->ReadInt( state.movementType );
1553 savefile->ReadInt( state.movementFlags );
1554 savefile->ReadInt( state.movementTime );
1555 }
1556
1557 /*
1558 ================
1559 idPhysics_Player::Save
1560 ================
1561 */
Save(idSaveGame * savefile) const1562 void idPhysics_Player::Save( idSaveGame *savefile ) const {
1563
1564 idPhysics_Player_SavePState( savefile, current );
1565 idPhysics_Player_SavePState( savefile, saved );
1566
1567 savefile->WriteFloat( walkSpeed );
1568 savefile->WriteFloat( crouchSpeed );
1569 savefile->WriteFloat( maxStepHeight );
1570 savefile->WriteFloat( maxJumpHeight );
1571 savefile->WriteInt( debugLevel );
1572
1573 savefile->WriteUsercmd( command );
1574 savefile->WriteAngles( viewAngles );
1575
1576 savefile->WriteInt( framemsec );
1577 savefile->WriteFloat( frametime );
1578 savefile->WriteFloat( playerSpeed );
1579 savefile->WriteVec3( viewForward );
1580 savefile->WriteVec3( viewRight );
1581
1582 savefile->WriteBool( walking );
1583 savefile->WriteBool( groundPlane );
1584 savefile->WriteTrace( groundTrace );
1585 savefile->WriteMaterial( groundMaterial );
1586
1587 savefile->WriteBool( ladder );
1588 savefile->WriteVec3( ladderNormal );
1589
1590 savefile->WriteInt( (int)waterLevel );
1591 savefile->WriteInt( waterType );
1592 }
1593
1594 /*
1595 ================
1596 idPhysics_Player::Restore
1597 ================
1598 */
Restore(idRestoreGame * savefile)1599 void idPhysics_Player::Restore( idRestoreGame *savefile ) {
1600
1601 idPhysics_Player_RestorePState( savefile, current );
1602 idPhysics_Player_RestorePState( savefile, saved );
1603
1604 savefile->ReadFloat( walkSpeed );
1605 savefile->ReadFloat( crouchSpeed );
1606 savefile->ReadFloat( maxStepHeight );
1607 savefile->ReadFloat( maxJumpHeight );
1608 savefile->ReadInt( debugLevel );
1609
1610 savefile->ReadUsercmd( command );
1611 savefile->ReadAngles( viewAngles );
1612
1613 savefile->ReadInt( framemsec );
1614 savefile->ReadFloat( frametime );
1615 savefile->ReadFloat( playerSpeed );
1616 savefile->ReadVec3( viewForward );
1617 savefile->ReadVec3( viewRight );
1618
1619 savefile->ReadBool( walking );
1620 savefile->ReadBool( groundPlane );
1621 savefile->ReadTrace( groundTrace );
1622 savefile->ReadMaterial( groundMaterial );
1623
1624 savefile->ReadBool( ladder );
1625 savefile->ReadVec3( ladderNormal );
1626
1627 savefile->ReadInt( (int &)waterLevel );
1628 savefile->ReadInt( waterType );
1629
1630 /* DG: It can apparently happen that the player saves while the clipModel's axis are
1631 * modified by idPush::TryRotatePushEntity() -> idPhysics_Player::Rotate() -> idClipModel::Link()
1632 * Normally idPush seems to reset them to the identity matrix in the next frame,
1633 * but apparently not when coming from a savegame.
1634 * Usually clipModel->axis is the identity matrix, and if it isn't there's clipping bugs
1635 * like CheckGround() reporting that it's steep even though the player is only trying to
1636 * walk up normal stairs.
1637 * Resetting the axis to mat3_identity when restoring a savegame works around that issue
1638 * and makes sure players can go on playing if their savegame was "corrupted" by saving
1639 * while idPush was active. See https://github.com/dhewm/dhewm3/issues/328 for more details */
1640 if ( clipModel != NULL ) {
1641 clipModel->SetPosition( clipModel->GetOrigin(), mat3_identity );
1642 }
1643 }
1644
1645 /*
1646 ================
1647 idPhysics_Player::SetPlayerInput
1648 ================
1649 */
SetPlayerInput(const usercmd_t & cmd,const idAngles & newViewAngles)1650 void idPhysics_Player::SetPlayerInput( const usercmd_t &cmd, const idAngles &newViewAngles ) {
1651 command = cmd;
1652 viewAngles = newViewAngles; // can't use cmd.angles cause of the delta_angles
1653 }
1654
1655 /*
1656 ================
1657 idPhysics_Player::SetSpeed
1658 ================
1659 */
SetSpeed(const float newWalkSpeed,const float newCrouchSpeed)1660 void idPhysics_Player::SetSpeed( const float newWalkSpeed, const float newCrouchSpeed ) {
1661 walkSpeed = newWalkSpeed;
1662 crouchSpeed = newCrouchSpeed;
1663 }
1664
1665 /*
1666 ================
1667 idPhysics_Player::SetMaxStepHeight
1668 ================
1669 */
SetMaxStepHeight(const float newMaxStepHeight)1670 void idPhysics_Player::SetMaxStepHeight( const float newMaxStepHeight ) {
1671 maxStepHeight = newMaxStepHeight;
1672 }
1673
1674 /*
1675 ================
1676 idPhysics_Player::GetMaxStepHeight
1677 ================
1678 */
GetMaxStepHeight(void) const1679 float idPhysics_Player::GetMaxStepHeight( void ) const {
1680 return maxStepHeight;
1681 }
1682
1683 /*
1684 ================
1685 idPhysics_Player::SetMaxJumpHeight
1686 ================
1687 */
SetMaxJumpHeight(const float newMaxJumpHeight)1688 void idPhysics_Player::SetMaxJumpHeight( const float newMaxJumpHeight ) {
1689 maxJumpHeight = newMaxJumpHeight;
1690 }
1691
1692 /*
1693 ================
1694 idPhysics_Player::SetMovementType
1695 ================
1696 */
SetMovementType(const pmtype_t type)1697 void idPhysics_Player::SetMovementType( const pmtype_t type ) {
1698 current.movementType = type;
1699 }
1700
1701 /*
1702 ================
1703 idPhysics_Player::SetKnockBack
1704 ================
1705 */
SetKnockBack(const int knockBackTime)1706 void idPhysics_Player::SetKnockBack( const int knockBackTime ) {
1707 if ( current.movementTime ) {
1708 return;
1709 }
1710 current.movementFlags |= PMF_TIME_KNOCKBACK;
1711 current.movementTime = knockBackTime;
1712 }
1713
1714 /*
1715 ================
1716 idPhysics_Player::SetDebugLevel
1717 ================
1718 */
SetDebugLevel(bool set)1719 void idPhysics_Player::SetDebugLevel( bool set ) {
1720 debugLevel = set;
1721 }
1722
1723 /*
1724 ================
1725 idPhysics_Player::Evaluate
1726 ================
1727 */
Evaluate(int timeStepMSec,int endTimeMSec)1728 bool idPhysics_Player::Evaluate( int timeStepMSec, int endTimeMSec ) {
1729 idVec3 masterOrigin, oldOrigin;
1730 idMat3 masterAxis;
1731
1732 waterLevel = WATERLEVEL_NONE;
1733 waterType = 0;
1734 oldOrigin = current.origin;
1735
1736 clipModel->Unlink();
1737
1738 // if bound to a master
1739 if ( masterEntity ) {
1740 self->GetMasterPosition( masterOrigin, masterAxis );
1741 current.origin = masterOrigin + current.localOrigin * masterAxis;
1742 clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
1743 current.velocity = ( current.origin - oldOrigin ) / ( timeStepMSec * 0.001f );
1744 masterDeltaYaw = masterYaw;
1745 masterYaw = masterAxis[0].ToYaw();
1746 masterDeltaYaw = masterYaw - masterDeltaYaw;
1747 return true;
1748 }
1749
1750 ActivateContactEntities();
1751
1752 idPhysics_Player::MovePlayer( timeStepMSec );
1753
1754 clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
1755
1756 if ( IsOutsideWorld() ) {
1757 gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) );
1758 }
1759
1760 return true; //( current.origin != oldOrigin );
1761 }
1762
1763 /*
1764 ================
1765 idPhysics_Player::UpdateTime
1766 ================
1767 */
UpdateTime(int endTimeMSec)1768 void idPhysics_Player::UpdateTime( int endTimeMSec ) {
1769 }
1770
1771 /*
1772 ================
1773 idPhysics_Player::GetTime
1774 ================
1775 */
GetTime(void) const1776 int idPhysics_Player::GetTime( void ) const {
1777 return gameLocal.time;
1778 }
1779
1780 /*
1781 ================
1782 idPhysics_Player::GetImpactInfo
1783 ================
1784 */
GetImpactInfo(const int id,const idVec3 & point,impactInfo_t * info) const1785 void idPhysics_Player::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const {
1786 info->invMass = invMass;
1787 info->invInertiaTensor.Zero();
1788 info->position.Zero();
1789 info->velocity = current.velocity;
1790 }
1791
1792 /*
1793 ================
1794 idPhysics_Player::ApplyImpulse
1795 ================
1796 */
ApplyImpulse(const int id,const idVec3 & point,const idVec3 & impulse)1797 void idPhysics_Player::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) {
1798 if ( current.movementType != PM_NOCLIP ) {
1799 current.velocity += impulse * invMass;
1800 }
1801 }
1802
1803 /*
1804 ================
1805 idPhysics_Player::IsAtRest
1806 ================
1807 */
IsAtRest(void) const1808 bool idPhysics_Player::IsAtRest( void ) const {
1809 return false;
1810 }
1811
1812 /*
1813 ================
1814 idPhysics_Player::GetRestStartTime
1815 ================
1816 */
GetRestStartTime(void) const1817 int idPhysics_Player::GetRestStartTime( void ) const {
1818 return -1;
1819 }
1820
1821 /*
1822 ================
1823 idPhysics_Player::SaveState
1824 ================
1825 */
SaveState(void)1826 void idPhysics_Player::SaveState( void ) {
1827 saved = current;
1828 }
1829
1830 /*
1831 ================
1832 idPhysics_Player::RestoreState
1833 ================
1834 */
RestoreState(void)1835 void idPhysics_Player::RestoreState( void ) {
1836 current = saved;
1837
1838 clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
1839
1840 EvaluateContacts();
1841 }
1842
1843 /*
1844 ================
1845 idPhysics_Player::SetOrigin
1846 ================
1847 */
SetOrigin(const idVec3 & newOrigin,int id)1848 void idPhysics_Player::SetOrigin( const idVec3 &newOrigin, int id ) {
1849 idVec3 masterOrigin;
1850 idMat3 masterAxis;
1851
1852 current.localOrigin = newOrigin;
1853 if ( masterEntity ) {
1854 self->GetMasterPosition( masterOrigin, masterAxis );
1855 current.origin = masterOrigin + newOrigin * masterAxis;
1856 }
1857 else {
1858 current.origin = newOrigin;
1859 }
1860
1861 clipModel->Link( gameLocal.clip, self, 0, newOrigin, clipModel->GetAxis() );
1862 }
1863
1864 /*
1865 ================
1866 idPhysics_Player::GetOrigin
1867 ================
1868 */
PlayerGetOrigin(void) const1869 const idVec3 & idPhysics_Player::PlayerGetOrigin( void ) const {
1870 return current.origin;
1871 }
1872
1873 /*
1874 ================
1875 idPhysics_Player::SetAxis
1876 ================
1877 */
SetAxis(const idMat3 & newAxis,int id)1878 void idPhysics_Player::SetAxis( const idMat3 &newAxis, int id ) {
1879 clipModel->Link( gameLocal.clip, self, 0, clipModel->GetOrigin(), newAxis );
1880 }
1881
1882 /*
1883 ================
1884 idPhysics_Player::Translate
1885 ================
1886 */
Translate(const idVec3 & translation,int id)1887 void idPhysics_Player::Translate( const idVec3 &translation, int id ) {
1888
1889 current.localOrigin += translation;
1890 current.origin += translation;
1891
1892 clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
1893 }
1894
1895 /*
1896 ================
1897 idPhysics_Player::Rotate
1898 ================
1899 */
Rotate(const idRotation & rotation,int id)1900 void idPhysics_Player::Rotate( const idRotation &rotation, int id ) {
1901 idVec3 masterOrigin;
1902 idMat3 masterAxis;
1903
1904 current.origin *= rotation;
1905 if ( masterEntity ) {
1906 self->GetMasterPosition( masterOrigin, masterAxis );
1907 current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose();
1908 }
1909 else {
1910 current.localOrigin = current.origin;
1911 }
1912
1913 clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() );
1914 }
1915
1916 /*
1917 ================
1918 idPhysics_Player::SetLinearVelocity
1919 ================
1920 */
SetLinearVelocity(const idVec3 & newLinearVelocity,int id)1921 void idPhysics_Player::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) {
1922 current.velocity = newLinearVelocity;
1923 }
1924
1925 /*
1926 ================
1927 idPhysics_Player::GetLinearVelocity
1928 ================
1929 */
GetLinearVelocity(int id) const1930 const idVec3 &idPhysics_Player::GetLinearVelocity( int id ) const {
1931 return current.velocity;
1932 }
1933
1934 /*
1935 ================
1936 idPhysics_Player::SetPushed
1937 ================
1938 */
SetPushed(int deltaTime)1939 void idPhysics_Player::SetPushed( int deltaTime ) {
1940 idVec3 velocity;
1941 float d;
1942
1943 // velocity with which the player is pushed
1944 velocity = ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC );
1945
1946 // remove any downward push velocity
1947 d = velocity * gravityNormal;
1948 if ( d > 0.0f ) {
1949 velocity -= d * gravityNormal;
1950 }
1951
1952 current.pushVelocity += velocity;
1953 }
1954
1955 /*
1956 ================
1957 idPhysics_Player::GetPushedLinearVelocity
1958 ================
1959 */
GetPushedLinearVelocity(const int id) const1960 const idVec3 &idPhysics_Player::GetPushedLinearVelocity( const int id ) const {
1961 return current.pushVelocity;
1962 }
1963
1964 /*
1965 ================
1966 idPhysics_Player::ClearPushedVelocity
1967 ================
1968 */
ClearPushedVelocity(void)1969 void idPhysics_Player::ClearPushedVelocity( void ) {
1970 current.pushVelocity.Zero();
1971 }
1972
1973 /*
1974 ================
1975 idPhysics_Player::SetMaster
1976
1977 the binding is never orientated
1978 ================
1979 */
SetMaster(idEntity * master,const bool orientated)1980 void idPhysics_Player::SetMaster( idEntity *master, const bool orientated ) {
1981 idVec3 masterOrigin;
1982 idMat3 masterAxis;
1983
1984 if ( master ) {
1985 if ( !masterEntity ) {
1986 // transform from world space to master space
1987 self->GetMasterPosition( masterOrigin, masterAxis );
1988 current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose();
1989 masterEntity = master;
1990 masterYaw = masterAxis[0].ToYaw();
1991 }
1992 ClearContacts();
1993 }
1994 else {
1995 if ( masterEntity ) {
1996 masterEntity = NULL;
1997 }
1998 }
1999 }
2000
2001 const float PLAYER_VELOCITY_MAX = 4000;
2002 const int PLAYER_VELOCITY_TOTAL_BITS = 16;
2003 const int PLAYER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( PLAYER_VELOCITY_MAX ) ) + 1;
2004 const int PLAYER_VELOCITY_MANTISSA_BITS = PLAYER_VELOCITY_TOTAL_BITS - 1 - PLAYER_VELOCITY_EXPONENT_BITS;
2005 const int PLAYER_MOVEMENT_TYPE_BITS = 3;
2006 const int PLAYER_MOVEMENT_FLAGS_BITS = 8;
2007
2008 /*
2009 ================
2010 idPhysics_Player::WriteToSnapshot
2011 ================
2012 */
WriteToSnapshot(idBitMsgDelta & msg) const2013 void idPhysics_Player::WriteToSnapshot( idBitMsgDelta &msg ) const {
2014 msg.WriteFloat( current.origin[0] );
2015 msg.WriteFloat( current.origin[1] );
2016 msg.WriteFloat( current.origin[2] );
2017 msg.WriteFloat( current.velocity[0], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
2018 msg.WriteFloat( current.velocity[1], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
2019 msg.WriteFloat( current.velocity[2], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
2020 msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] );
2021 msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] );
2022 msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] );
2023 msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
2024 msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
2025 msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
2026 msg.WriteDeltaFloat( 0.0f, current.stepUp );
2027 msg.WriteBits( current.movementType, PLAYER_MOVEMENT_TYPE_BITS );
2028 msg.WriteBits( current.movementFlags, PLAYER_MOVEMENT_FLAGS_BITS );
2029 msg.WriteDeltaInt( 0, current.movementTime );
2030 }
2031
2032 /*
2033 ================
2034 idPhysics_Player::ReadFromSnapshot
2035 ================
2036 */
ReadFromSnapshot(const idBitMsgDelta & msg)2037 void idPhysics_Player::ReadFromSnapshot( const idBitMsgDelta &msg ) {
2038 current.origin[0] = msg.ReadFloat();
2039 current.origin[1] = msg.ReadFloat();
2040 current.origin[2] = msg.ReadFloat();
2041 current.velocity[0] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
2042 current.velocity[1] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
2043 current.velocity[2] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
2044 current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] );
2045 current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] );
2046 current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] );
2047 current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
2048 current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
2049 current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS );
2050 current.stepUp = msg.ReadDeltaFloat( 0.0f );
2051 current.movementType = msg.ReadBits( PLAYER_MOVEMENT_TYPE_BITS );
2052 current.movementFlags = msg.ReadBits( PLAYER_MOVEMENT_FLAGS_BITS );
2053 current.movementTime = msg.ReadDeltaInt( 0 );
2054
2055 if ( clipModel ) {
2056 clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
2057 }
2058 }
2059