/* sv_move.c - monsters movement Copyright (C) 2007 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "xash3d_mathlib.h" #include "server.h" #include "const.h" #include "pm_defs.h" #define MOVE_NORMAL 0 // normal move in the direction monster is facing #define MOVE_STRAFE 1 // moves in direction specified, no matter which way monster is facing /* ============= SV_CheckBottom Returns false if any part of the bottom of the entity is off an edge that is not a staircase. ============= */ qboolean SV_CheckBottom( edict_t *ent, int iMode ) { vec3_t mins, maxs, start, stop; float mid, bottom; qboolean monsterClip; trace_t trace; int x, y; monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false; VectorAdd( ent->v.origin, ent->v.mins, mins ); VectorAdd( ent->v.origin, ent->v.maxs, maxs ); // if all of the points under the corners are solid world, don't bother // with the tougher checks // the corners must be within 16 of the midpoint start[2] = mins[2] - 1.0f; for( x = 0; x <= 1; x++ ) { for( y = 0; y <= 1; y++ ) { start[0] = x ? maxs[0] : mins[0]; start[1] = y ? maxs[1] : mins[1]; svs.groupmask = ent->v.groupinfo; if( SV_PointContents( start ) != CONTENTS_SOLID ) goto realcheck; } } return true; // we got out easy realcheck: // check it for real... start[2] = mins[2]; if( !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) start[2] += svgame.movevars.stepsize; // the midpoint must be within 16 of the bottom start[0] = stop[0] = (mins[0] + maxs[0]) * 0.5f; start[1] = stop[1] = (mins[1] + maxs[1]) * 0.5f; stop[2] = start[2] - 2.0f * svgame.movevars.stepsize; if( iMode == WALKMOVE_WORLDONLY ) trace = SV_MoveNoEnts( start, vec3_origin, vec3_origin, stop, MOVE_NOMONSTERS, ent ); else trace = SV_Move( start, vec3_origin, vec3_origin, stop, MOVE_NOMONSTERS, ent, monsterClip ); if( trace.fraction == 1.0f ) return false; mid = bottom = trace.endpos[2]; // the corners must be within 16 of the midpoint for( x = 0; x <= 1; x++ ) { for( y = 0; y <= 1; y++ ) { start[0] = stop[0] = x ? maxs[0] : mins[0]; start[1] = stop[1] = y ? maxs[1] : mins[1]; if( iMode == WALKMOVE_WORLDONLY ) trace = SV_MoveNoEnts( start, vec3_origin, vec3_origin, stop, MOVE_NOMONSTERS, ent ); else trace = SV_Move( start, vec3_origin, vec3_origin, stop, MOVE_NOMONSTERS, ent, monsterClip ); if( trace.fraction != 1.0f && trace.endpos[2] > bottom ) bottom = trace.endpos[2]; if( trace.fraction == 1.0f || mid - trace.endpos[2] > svgame.movevars.stepsize ) return false; } } return true; } void SV_WaterMove( edict_t *ent ) { float drownlevel; int waterlevel; int watertype; int flags; if( ent->v.movetype == MOVETYPE_NOCLIP ) { ent->v.air_finished = sv.time + 12.0f; return; } // no watermove for monsters but pushables if(( ent->v.flags & FL_MONSTER ) && ent->v.health <= 0.0f ) return; drownlevel = (ent->v.deadflag == DEAD_NO) ? 3.0 : 1.0; waterlevel = ent->v.waterlevel; watertype = ent->v.watertype; flags = ent->v.flags; if( !( flags & ( FL_IMMUNE_WATER|FL_GODMODE ))) { if((( flags & FL_SWIM ) && waterlevel > drownlevel ) || waterlevel <= drownlevel ) { if( ent->v.air_finished > sv.time && ent->v.pain_finished > sv.time ) { ent->v.dmg += 2; if( ent->v.dmg < 15 ) ent->v.dmg = 10; // quake1 original code ent->v.pain_finished = sv.time + 1.0f; } } else { ent->v.air_finished = sv.time + 12.0f; ent->v.dmg = 2; } } if( !waterlevel ) { if( flags & FL_INWATER ) { // leave the water. switch( COM_RandomLong( 0, 3 )) { case 0: SV_StartSound( ent, CHAN_BODY, "player/pl_wade1.wav", 1.0f, ATTN_NORM, 0, 100 ); break; case 1: SV_StartSound( ent, CHAN_BODY, "player/pl_wade2.wav", 1.0f, ATTN_NORM, 0, 100 ); break; case 2: SV_StartSound( ent, CHAN_BODY, "player/pl_wade3.wav", 1.0f, ATTN_NORM, 0, 100 ); break; case 3: SV_StartSound( ent, CHAN_BODY, "player/pl_wade4.wav", 1.0f, ATTN_NORM, 0, 100 ); break; } ent->v.flags = flags & ~FL_INWATER; } ent->v.air_finished = sv.time + 12.0f; return; } if( watertype == CONTENTS_LAVA ) { if((!( flags & ( FL_IMMUNE_LAVA|FL_GODMODE ))) && ent->v.dmgtime < sv.time ) { if( ent->v.radsuit_finished < sv.time ) ent->v.dmgtime = sv.time + 0.2f; else ent->v.dmgtime = sv.time + 1.0f; } } else if( watertype == CONTENTS_SLIME ) { if((!( flags & ( FL_IMMUNE_SLIME|FL_GODMODE ))) && ent->v.dmgtime < sv.time ) { if( ent->v.radsuit_finished < sv.time ) ent->v.dmgtime = sv.time + 1.0; // otherwise radsuit is fully protect entity from slime } } if( !( flags & FL_INWATER )) { if( watertype == CONTENTS_WATER ) { // entering the water switch( COM_RandomLong( 0, 3 )) { case 0: SV_StartSound( ent, CHAN_BODY, "player/pl_wade1.wav", 1.0f, ATTN_NORM, 0, 100 ); break; case 1: SV_StartSound( ent, CHAN_BODY, "player/pl_wade2.wav", 1.0f, ATTN_NORM, 0, 100 ); break; case 2: SV_StartSound( ent, CHAN_BODY, "player/pl_wade3.wav", 1.0f, ATTN_NORM, 0, 100 ); break; case 3: SV_StartSound( ent, CHAN_BODY, "player/pl_wade4.wav", 1.0f, ATTN_NORM, 0, 100 ); break; } } ent->v.flags = flags | FL_INWATER; ent->v.dmgtime = 0.0f; } if( !( flags & FL_WATERJUMP )) { VectorMA( ent->v.velocity, ( ent->v.waterlevel * -0.8f * sv.frametime ), ent->v.velocity, ent->v.velocity ); } } /* ============= SV_VecToYaw converts dir to yaw ============= */ float SV_VecToYaw( const vec3_t src ) { float yaw; if( !src ) return 0.0f; if( src[1] == 0.0f && src[0] == 0.0f ) { yaw = 0.0f; } else { yaw = (int)( atan2( src[1], src[0] ) * 180.0 / M_PI ); if( yaw < 0 ) yaw += 360.0f; } return yaw; } //============================================================================ qboolean SV_MoveStep( edict_t *ent, vec3_t move, qboolean relink ) { int i; trace_t trace; vec3_t oldorg, neworg, end; qboolean monsterClip; edict_t *enemy; float dz; VectorCopy( ent->v.origin, oldorg ); VectorAdd( ent->v.origin, move, neworg ); monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false; // well, try it. Flying and swimming monsters are easiest. if( ent->v.flags & ( FL_SWIM|FL_FLY )) { // try one move with vertical motion, then one without for( i = 0; i < 2; i++ ) { VectorAdd( ent->v.origin, move, neworg ); enemy = ent->v.enemy; if( i == 0 && enemy != NULL ) { dz = ent->v.origin[2] - enemy->v.origin[2]; if( dz > 40.0f ) neworg[2] -= 8.0f; else if( dz < 30.0f ) neworg[2] += 8.0f; } trace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, neworg, MOVE_NORMAL, ent, monsterClip ); if( trace.fraction == 1.0f ) { svs.groupmask = ent->v.groupinfo; // that move takes us out of the water. // apparently though, it's okay to travel into solids, lava, sky, etc :) if(( ent->v.flags & FL_SWIM ) && SV_PointContents( trace.endpos ) == CONTENTS_EMPTY ) return 0; VectorCopy( trace.endpos, ent->v.origin ); if( relink ) SV_LinkEdict( ent, true ); return 1; } else { if( !SV_IsValidEdict( enemy )) break; } } return 0; } else { dz = svgame.movevars.stepsize; neworg[2] += dz; VectorCopy( neworg, end ); end[2] -= dz * 2.0f; trace = SV_Move( neworg, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent, monsterClip ); if( trace.allsolid ) return 0; if( trace.startsolid != 0 ) { neworg[2] -= dz; trace = SV_Move( neworg, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent, monsterClip ); if( trace.allsolid != 0 || trace.startsolid != 0 ) return 0; } if( trace.fraction == 1.0f ) { if( ent->v.flags & FL_PARTIALGROUND ) { VectorAdd( ent->v.origin, move, ent->v.origin ); if( relink ) SV_LinkEdict( ent, true ); ent->v.flags &= ~FL_ONGROUND; return 1; } return 0; } else { VectorCopy( trace.endpos, ent->v.origin ); if( SV_CheckBottom( ent, WALKMOVE_NORMAL ) == 0 ) { if( ent->v.flags & FL_PARTIALGROUND ) { if( relink ) SV_LinkEdict( ent, true ); return 1; } VectorCopy( oldorg, ent->v.origin ); return 0; } else { ent->v.flags &= ~FL_PARTIALGROUND; ent->v.groundentity = trace.ent; if( relink ) SV_LinkEdict( ent, true ); return 1; } } } } qboolean SV_MoveTest( edict_t *ent, vec3_t move, qboolean relink ) { float temp; vec3_t oldorg, neworg, end; trace_t trace; VectorCopy( ent->v.origin, oldorg ); VectorAdd( ent->v.origin, move, neworg ); temp = svgame.movevars.stepsize; neworg[2] += temp; VectorCopy( neworg, end ); end[2] -= temp * 2.0f; trace = SV_MoveNoEnts( neworg, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent ); if( trace.allsolid != 0 ) return 0; if( trace.startsolid != 0 ) { neworg[2] -= temp; trace = SV_MoveNoEnts( neworg, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent ); if( trace.allsolid != 0 || trace.startsolid != 0 ) return 0; } if( trace.fraction == 1.0f ) { if( ent->v.flags & FL_PARTIALGROUND ) { VectorAdd( ent->v.origin, move, ent->v.origin ); if( relink ) SV_LinkEdict( ent, true ); ent->v.flags &= ~FL_ONGROUND; return 1; } return 0; } else { VectorCopy( trace.endpos, ent->v.origin ); if( SV_CheckBottom( ent, WALKMOVE_WORLDONLY ) == 0 ) { if( ent->v.flags & FL_PARTIALGROUND ) { if( relink ) SV_LinkEdict( ent, true ); return 1; } VectorCopy( oldorg, ent->v.origin ); return 0; } else { ent->v.flags &= ~FL_PARTIALGROUND; ent->v.groundentity = trace.ent; if( relink ) SV_LinkEdict( ent, true ); return 1; } } } qboolean SV_StepDirection( edict_t *ent, float yaw, float dist ) { int ret; float cSin, cCos; vec3_t move; yaw = yaw * M_PI2 / 360.0f; SinCos( yaw, &cSin, &cCos ); VectorSet( move, cCos * dist, cSin * dist, 0.0f ); ret = SV_MoveStep( ent, move, false ); SV_LinkEdict( ent, true ); return ret; } qboolean SV_FlyDirection( edict_t *ent, vec3_t move ) { int ret; ret = SV_MoveStep( ent, move, false ); SV_LinkEdict( ent, true ); return ret; } void SV_NewChaseDir( edict_t *actor, vec3_t destination, float dist ) { float deltax, deltay; float tempdir, olddir, turnaround; vec3_t d; olddir = anglemod(((int)( actor->v.ideal_yaw / 45.0f )) * 45.0f ); turnaround = anglemod( olddir - 180.0f ); deltax = destination[0] - actor->v.origin[0]; deltay = destination[1] - actor->v.origin[1]; if( deltax > 10.0f ) d[1] = 0.0f; else if( deltax < -10.0f ) d[1] = 180.0f; else d[1] = -1; if( deltay < -10.0f ) d[2] = 270.0f; else if( deltay > 10.0f ) d[2] = 90.0f; else d[2] = -1.0f; // try direct route if( d[1] != -1.0f && d[2] != -1.0f ) { if( d[1] == 0.0f ) tempdir = ( d[2] == 90.0f ) ? 45.0f : 315.0f; else tempdir = ( d[2] == 90.0f ) ? 135.0f : 215.0f; if( tempdir != turnaround && SV_StepDirection( actor, tempdir, dist )) return; } // try other directions if( COM_RandomLong( 0, 1 ) != 0 || fabs( deltay ) > fabs( deltax )) { tempdir = d[1]; d[1] = d[2]; d[2] = tempdir; } if( d[1] != -1.0f && d[1] != turnaround && SV_StepDirection( actor, d[1], dist )) return; if( d[2] != -1.0f && d[2] != turnaround && SV_StepDirection( actor, d[2], dist )) return; // there is no direct path to the player, so pick another direction if( olddir != -1.0f && SV_StepDirection( actor, olddir, dist )) return; // fine, just run somewhere. if( COM_RandomLong( 0, 1 ) != 1 ) { for( tempdir = 0; tempdir <= 315.0f; tempdir += 45.0f ) { if( tempdir != turnaround && SV_StepDirection( actor, tempdir, dist )) return; } } else { for( tempdir = 315.0f; tempdir >= 0.0f; tempdir -= 45.0f ) { if( tempdir != turnaround && SV_StepDirection( actor, tempdir, dist )) return; } } // we tried. run backwards. that ought to work... if( turnaround != -1.0f && SV_StepDirection( actor, turnaround, dist )) return; // well, we're stuck somehow. actor->v.ideal_yaw = olddir; // if a bridge was pulled out from underneath a monster, it may not have // a valid standing position at all. if( !SV_CheckBottom( actor, WALKMOVE_NORMAL )) { actor->v.flags |= FL_PARTIALGROUND; } } void SV_MoveToOrigin( edict_t *ent, const vec3_t pflGoal, float dist, int iMoveType ) { vec3_t vecDist; VectorCopy( pflGoal, vecDist ); if( ent->v.flags & ( FL_FLY|FL_SWIM|FL_ONGROUND )) { if( iMoveType == MOVE_NORMAL ) { if( !SV_StepDirection( ent, ent->v.ideal_yaw, dist )) { SV_NewChaseDir( ent, vecDist, dist ); } } else { vecDist[0] -= ent->v.origin[0]; vecDist[1] -= ent->v.origin[1]; if( ent->v.flags & ( FL_FLY|FL_SWIM )) vecDist[2] -= ent->v.origin[2]; else vecDist[2] = 0.0f; VectorNormalize( vecDist ); VectorScale( vecDist, dist, vecDist ); SV_FlyDirection( ent, vecDist ); } } }