static bool AI_AttemptWalljump( edict_t *self ) { if( self->ai->path.numNodes >= 1 ) { int n1 = self->ai->current_node; int n2 = self->ai->next_node; vec3_t n1origin, n2origin, origin; if( n1 == n2 ) return false; // we use a wider radius in 2D, and a height range enough so they can't be jumped over AI_GetNodeOrigin( n1, n1origin ); AI_GetNodeOrigin( n2, n2origin ); VectorCopy( self->s.origin, origin ); if( fabs( n1origin[2] - n2origin[2] ) < 32.0f && origin[2] >= n1origin[2] - 4.0f ) { float dist = DistanceFast( n1origin, n2origin ); float n1d, n2d; n1d = DistanceFast( n1origin, origin ); n2d = DistanceFast( n2origin, origin ); if( dist >= 150.0f && n1d >= dist*0.5f && n2d < dist ) { return true; } } } return false; }
bool AI_NodeReached_Generic( edict_t *self ) { bool reached = false; float RADIUS = NODE_REACH_RADIUS; if( !( AI_GetNodeFlags( self->ai->next_node ) & (NODEFLAGS_REACHATTOUCH|NODEFLAGS_ENTITYREACH) ) ) { if( self->ai->path.numNodes >= MIN_BUNNY_NODES ) { int n1 = self->ai->path.nodes[self->ai->path.numNodes]; int n2 = self->ai->path.nodes[self->ai->path.numNodes-1]; vec3_t n1origin, n2origin, origin; // if falling from a jump pad use a taller cylinder if( !self->groundentity && !self->is_step && !self->is_swim && ( AI_CurrentLinkType( self ) & LINK_JUMPPAD ) ) RADIUS = NODE_WIDE_REACH_RADIUS; // we use a wider radius in 2D, and a height range enough so they can't be jumped over AI_GetNodeOrigin( n1, n1origin ); AI_GetNodeOrigin( n2, n2origin ); VectorCopy( self->s.origin, origin ); n1origin[2] = n2origin[2] = origin[2] = 0; // see if reached the second if( n2 != NODE_INVALID && ( ( nodes[n2].origin[2] - 16 ) < self->s.origin[2] ) && ( nodes[n2].origin[2] + RADIUS > self->s.origin[2] ) && ( DistanceFast( n2origin, origin ) < RADIUS ) ) { AI_NodeReached( self ); // advance the first reached = true; // return the second as reached } // see if reached the first else if( ( ( nodes[n1].origin[2] - 16 ) < self->s.origin[2] ) && ( nodes[n1].origin[2] + RADIUS > self->s.origin[2] ) && ( DistanceFast( n1origin, origin ) < RADIUS ) ) { reached = true; // return the first as reached } } else { reached = ( DistanceFast( self->s.origin, nodes[self->ai->next_node].origin ) < RADIUS ) ? true : false; } } return reached; }
/* * PlayersRangeFromSpot * * Returns the distance to the nearest player from the given spot */ float PlayersRangeFromSpot( edict_t *spot, int ignore_team ) { edict_t *player; float bestplayerdistance; int n; float playerdistance; bestplayerdistance = 9999999; for( n = 1; n <= gs.maxclients; n++ ) { player = &game.edicts[n]; if( !player->r.inuse ) continue; if( player->r.solid == SOLID_NOT ) continue; if( ( ignore_team && ignore_team == player->s.team ) || player->s.team == TEAM_SPECTATOR ) continue; playerdistance = DistanceFast( spot->s.origin, player->s.origin ); if( playerdistance < bestplayerdistance ) bestplayerdistance = playerdistance; } return bestplayerdistance; }
/* * CG_ImpactSmokePuff */ void CG_ImpactSmokePuff( vec3_t origin, vec3_t dir, float radius, float alpha, int time, int speed ) { #define SMOKEPUFF_MAXVIEWDIST 700 lentity_t *le; struct shader_s *shader = CG_MediaShader( cgs.media.shaderSmokePuff ); if( CG_PointContents( origin ) & MASK_WATER ) { return; } if( DistanceFast( origin, cg.view.origin ) * cg.view.fracDistFOV > SMOKEPUFF_MAXVIEWDIST ) return; if( !VectorLength( dir ) ) { VectorCopy( cg.view.axis[FORWARD], dir ); VectorInverse( dir ); } VectorNormalize( dir ); //offset the origin by half of the radius VectorMA( origin, radius*0.5f, dir, origin ); le = CG_AllocSprite( LE_SCALE_ALPHA_FADE, origin, radius + crandom(), time, 1, 1, 1, alpha, 0, 0, 0, 0, shader ); le->ent.rotation = rand() % 360; VectorScale( dir, speed, le->velocity ); }
void GS_TraceCurveLaserBeam( trace_t *trace, vec3_t origin, vec3_t angles, vec3_t blendPoint, int ignore, int timeDelta, void ( *impact )( trace_t *tr, vec3_t dir ) ) { float frac, subdivisions = CURVELASERBEAM_SUBDIVISIONS; float range = (float)GS_GetWeaponDef( WEAP_LASERGUN )->firedef_weak.timeout; vec3_t from, dir, end; int passthrough = ignore; int i, j; vec3_t tmpangles, blendAngles; assert( trace ); VectorCopy( origin, from ); VectorSubtract( blendPoint, origin, dir ); VecToAngles( dir, blendAngles ); for( i = 1; i <= (int)subdivisions; i++ ) { frac = ( ( range/subdivisions )*(float)i ) / (float)range; for( j = 0; j < 3; j++ ) tmpangles[j] = LerpAngle( angles[j], blendAngles[j], frac ); AngleVectors( tmpangles, dir, NULL, NULL ); VectorMA( origin, range * frac, dir, end ); GS_TraceLaserBeam( trace, from, tmpangles, DistanceFast( from, end ), passthrough, timeDelta, impact ); if( trace->fraction != 1.0f ) break; passthrough = trace->ent; VectorCopy( end, from ); } }
/* * AI_MoveToShortRangeGoalEntity * A.K.A Item pick magnet */ qboolean AI_MoveToShortRangeGoalEntity( edict_t *self, usercmd_t *ucmd ) { if( !self->movetarget || !self->r.client ) return qfalse; if( self->ai.goalEnt && ( self->ai.goalEnt->ent == self->movetarget ) && ( AI_GetNodeFlags( self->ai.goal_node ) & NODEFLAGS_ENTITYREACH ) ) { // wait VectorSubtract( self->movetarget->s.origin, self->s.origin, self->ai.move_vector ); if( VectorLength( self->ai.move_vector ) < 72 ) ucmd->buttons |= BUTTON_WALK; if( BoundsIntersect( self->movetarget->r.absmin, self->movetarget->r.absmax, self->r.absmin, self->r.absmax ) ) { ucmd->forwardmove = 0; ucmd->sidemove = 0; ucmd->upmove = 0; self->ai.node_timeout = 0; return qtrue; } } if( self->movetarget->r.solid == SOLID_NOT || DistanceFast( self->movetarget->s.origin, self->s.origin ) > AI_GOAL_SR_RADIUS + 72 ) { self->movetarget = NULL; self->ai.shortRangeGoalTimeout = level.time; return qfalse; } // Force movement direction to reach the goal entity VectorSubtract( self->movetarget->s.origin, self->s.origin, self->ai.move_vector ); return qtrue; }
bool AI_NodeReached_PlatformEnd( edict_t *self ) { bool reached = false; if( self->ai->next_node == NODE_INVALID ) return true; if( self->groundentity && self->groundentity->use == Use_Plat ) { reached = ( self->groundentity->moveinfo.state == STATE_TOP || VectorCompare( self->groundentity->s.origin, self->groundentity->moveinfo.dest ) ) ? true : false; } else { vec3_t v1, v2; v1[0] = self->s.origin[0]; v1[1] = self->s.origin[1]; v1[2] = 0; v2[0] = nodes[self->ai->next_node].origin[0]; v2[1] = nodes[self->ai->next_node].origin[1]; v2[2] = 0; if( DistanceFast( v1, v2 ) < NODE_REACH_RADIUS ) reached = ( fabs( nodes[self->ai->next_node].origin[2] - self->s.origin[2] ) < ( AI_JUMPABLE_HEIGHT * 0.5 ) ) ? true : false; } return reached; }
int AI_FindClosestNode( vec3_t origin, float mindist, int range, unsigned int flagsmask ) { int i; float closest; float dist; int node = NODE_INVALID; if( mindist > range ) return -1; closest = range; for( i = 0; i < nav.num_nodes; i++ ) { if( flagsmask == NODE_ALL || nodes[i].flags & flagsmask ) { dist = DistanceFast( nodes[i].origin, origin ); if( dist > mindist && dist < closest ) { node = i; closest = dist; } } } return node; }
bool AI_NodeReached_Special( edict_t *self ) { bool reached = false; if( self->ai->next_node != NODE_INVALID && !( AI_GetNodeFlags( self->ai->next_node ) & (NODEFLAGS_REACHATTOUCH|NODEFLAGS_ENTITYREACH) ) ) { if( self->ai->path.numNodes >= MIN_BUNNY_NODES ) { int n1 = self->ai->path.nodes[self->ai->path.numNodes]; int n2 = self->ai->path.nodes[self->ai->path.numNodes-1]; vec3_t n1origin, n2origin, origin; // we use a wider radius in 2D, and a height range enough so they can't be jumped over AI_GetNodeOrigin( n1, n1origin ); AI_GetNodeOrigin( n2, n2origin ); VectorCopy( self->s.origin, origin ); n1origin[2] = n2origin[2] = origin[2] = 0; // see if reached the second if( ( ( nodes[n2].origin[2] - 16 ) < self->s.origin[2] ) && ( nodes[n2].origin[2] + NODE_WIDE_REACH_RADIUS > self->s.origin[2] ) && ( DistanceFast( n2origin, origin ) < NODE_WIDE_REACH_RADIUS ) && AI_ReachabilityVisible( self, nodes[n2].origin ) ) { AI_NodeReached( self ); // advance the first reached = true; // return the second as reached } // see if reached the first else if( ( ( nodes[n1].origin[2] - 16 ) < self->s.origin[2] ) && ( nodes[n1].origin[2] + NODE_WIDE_REACH_RADIUS > self->s.origin[2] ) && ( DistanceFast( n1origin, origin ) < NODE_WIDE_REACH_RADIUS ) && AI_ReachabilityVisible( self, nodes[n1].origin ) ) { reached = true; // return the first as reached } } else return AI_NodeReached_Generic( self ); } return reached; }
/* * AI_AddNode_Platform_FindLowerLinkableCandidate * helper to AI_AddNode_Platform */ static int AI_AddNode_Platform_FindLowerLinkableCandidate( edict_t *ent ) { trace_t trace; float plat_dist; float platlip; int numtries = 0, maxtries = 10; int candidate; vec3_t candidate_origin, virtualorigin; float mindist = 0; if( ent->flags & FL_TEAMSLAVE ) return NODE_INVALID; plat_dist = ent->moveinfo.start_origin[2] - ent->moveinfo.end_origin[2]; platlip = ( ent->r.maxs[2] - ent->r.mins[2] ) - plat_dist; //find a good candidate for lower candidate_origin[0] = ( ent->r.maxs[0] - ent->r.mins[0] ) / 2 + ent->r.mins[0]; candidate_origin[1] = ( ent->r.maxs[1] - ent->r.mins[1] ) / 2 + ent->r.mins[1]; candidate_origin[2] = ent->r.mins[2] + platlip; //try to find the closer reachable node to the bottom of the plat do { candidate = AI_FindClosestNode( candidate_origin, mindist, NODE_DENSITY * 2, NODE_ALL ); if( candidate != NODE_INVALID ) { mindist = DistanceFast( candidate_origin, nodes[candidate].origin ); //check to see if it would be valid if( fabs( candidate_origin[2] - nodes[candidate].origin[2] ) < ( fabs( platlip ) + AI_JUMPABLE_HEIGHT ) ) { //put at linkable candidate height virtualorigin[0] = candidate_origin[0]; virtualorigin[1] = candidate_origin[1]; virtualorigin[2] = nodes[candidate].origin[2]; G_Trace( &trace, virtualorigin, vec3_origin, vec3_origin, nodes[candidate].origin, ent, MASK_NODESOLID ); //trace = gi.trace( virtualorigin, vec3_origin, vec3_origin, nodes[candidate].origin, ent, MASK_NODESOLID ); if( trace.fraction == 1.0 && !trace.startsolid ) { #ifdef SHOW_JUMPAD_GUESS AI_JumpadGuess_ShowPoint( virtualorigin, "models/objects/grenade/tris.md2" ); #endif return candidate; } } } } while( candidate != NODE_INVALID && numtries++ < maxtries ); return NODE_INVALID; }
//========================================== // BOT_DMclass_PredictProjectileShot // predict target movement //========================================== static void BOT_DMclass_PredictProjectileShot( edict_t *self, vec3_t fire_origin, float projectile_speed, vec3_t target, vec3_t target_velocity ) { vec3_t predictedTarget; vec3_t targetMovedir; float targetSpeed; float predictionTime; float distance; trace_t trace; int contents; if( projectile_speed <= 0.0f ) return; targetSpeed = VectorNormalize2( target_velocity, targetMovedir ); // ok, this is not going to be 100% precise, since we will find the // time our projectile will take to travel to enemy's CURRENT position, // and them find enemy's position given his CURRENT velocity and his CURRENT dir // after prediction time. The result will be much better if the player // is moving to the sides (relative to us) than in depth (relative to us). // And, of course, when the player moves in a curve upwards it will totally miss (ie, jumping). // but in general it does a great job, much better than any human player :) distance = DistanceFast( fire_origin, target ); predictionTime = distance/projectile_speed; VectorMA( target, predictionTime*targetSpeed, targetMovedir, predictedTarget ); // if this position is inside solid, try finding a position at half of the prediction time contents = G_PointContents( predictedTarget ); if( contents & CONTENTS_SOLID && !( contents & CONTENTS_PLAYERCLIP ) ) { VectorMA( target, ( predictionTime * 0.5f )*targetSpeed, targetMovedir, predictedTarget ); contents = G_PointContents( predictedTarget ); if( contents & CONTENTS_SOLID && !( contents & CONTENTS_PLAYERCLIP ) ) return; // INVALID } // if we can see this point, we use it, otherwise we keep the current position G_Trace( &trace, fire_origin, vec3_origin, vec3_origin, predictedTarget, self, MASK_SHOT ); if( trace.fraction == 1.0f || ( trace.ent && game.edicts[trace.ent].takedamage ) ) VectorCopy( predictedTarget, target ); }
/* * R_AliasModelLOD */ static model_t *R_AliasModelLOD( entity_t *e ) { int lod; float dist; if( !e->model->numlods || ( e->flags & RF_FORCENOLOD ) ) return e->model; dist = DistanceFast( e->origin, ri.viewOrigin ); dist *= ri.lod_dist_scale_for_fov; lod = (int)( dist / e->model->radius ); if( r_lodscale->integer ) lod /= r_lodscale->integer; lod += r_lodbias->integer; if( lod < 1 ) return e->model; return e->model->lods[min( lod, e->model->numlods )-1]; }
int AI_FindClosestReachableNode( vec3_t origin, edict_t *passent, int range, unsigned int flagsmask ) { int i; float closest; float dist; int node = -1; trace_t tr; vec3_t maxs, mins; VectorSet( mins, -8, -8, -8 ); VectorSet( maxs, 8, 8, 8 ); // For Ladders, do not worry so much about reachability if( flagsmask & NODEFLAGS_LADDER ) { VectorCopy( vec3_origin, maxs ); VectorCopy( vec3_origin, mins ); } closest = range; for( i = 0; i < nav.num_nodes; i++ ) { if( flagsmask == NODE_ALL || nodes[i].flags & flagsmask ) { dist = DistanceFast( nodes[i].origin, origin ); if( dist < closest ) { // make sure it is visible G_Trace( &tr, origin, mins, maxs, nodes[i].origin, passent, MASK_NODESOLID ); if( tr.fraction == 1.0 ) { node = i; closest = dist; } } } } return node; }
bool AI_NodeReached_PlatformStart( edict_t *self ) { bool reached = false; if( self->ai->next_node == NODE_INVALID ) return true; if( self->groundentity && self->groundentity->use == Use_Plat ) { vec3_t v1, v2; v1[0] = self->s.origin[0]; v1[1] = self->s.origin[1]; v1[2] = 0; v2[0] = nodes[self->ai->next_node].origin[0]; v2[1] = nodes[self->ai->next_node].origin[1]; v2[2] = 0; reached = ( DistanceFast( v1, v2 ) < NODE_REACH_RADIUS ) ? true : false; } return reached; }
void BotTacticalSpotsCache::FindReachableClassEntities( const Vec3 &origin, float radius, const char *classname, BotTacticalSpotsCache::ReachableEntities &result ) { int *triggerEntities; int numEntities = FindNearbyEntities( origin, radius, &triggerEntities ); ReachableEntities candidateEntities; // Copy to locals for faster access (a compiler might be paranoid about aliasing) edict_t *gameEdicts = game.edicts; if( numEntities > (int)candidateEntities.capacity() ) { for( int i = 0; i < numEntities; ++i ) { edict_t *ent = gameEdicts + triggerEntities[i]; // Specify expected strcmp() result explicitly to avoid misinterpreting the condition // (Strings are equal if an strcmp() result is zero) if( strcmp( ent->classname, classname ) != 0 ) { continue; } float distance = DistanceFast( origin.Data(), ent->s.origin ); candidateEntities.push_back( EntAndScore( triggerEntities[i], radius - distance ) ); if( candidateEntities.size() == candidateEntities.capacity() ) { break; } } } else { for( int i = 0; i < numEntities; ++i ) { edict_t *ent = gameEdicts + triggerEntities[i]; if( strcmp( ent->classname, classname ) != 0 ) { continue; } float distance = DistanceFast( origin.Data(), ent->s.origin ); candidateEntities.push_back( EntAndScore( triggerEntities[i], radius - distance ) ); } } const AiAasWorld *aasWorld = AiAasWorld::Instance(); AiAasRouteCache *routeCache = self->ai->botRef->routeCache; bool testTwoCurrAreas = false; int fromAreaNum = 0; // If an origin matches actual bot origin if( ( origin - self->s.origin ).SquaredLength() < WorldState::OriginVar::MAX_ROUNDING_SQUARE_DISTANCE_ERROR ) { // Try testing both areas if( self->ai->botRef->CurrAreaNum() != self->ai->botRef->DroppedToFloorAreaNum() ) { testTwoCurrAreas = true; } else { fromAreaNum = self->ai->botRef->CurrAreaNum(); } } else { fromAreaNum = aasWorld->FindAreaNum( origin ); } if( testTwoCurrAreas ) { int fromAreaNums[2] = { self->ai->botRef->CurrAreaNum(), self->ai->botRef->DroppedToFloorAreaNum() }; for( EntAndScore &candidate: candidateEntities ) { edict_t *ent = gameEdicts + candidate.entNum; int toAreaNum = FindMostFeasibleEntityAasArea( ent, aasWorld ); if( !toAreaNum ) { continue; } int travelTime = 0; for( int i = 0; i < 2; ++i ) { travelTime = routeCache->TravelTimeToGoalArea( fromAreaNums[i], toAreaNum, Bot::ALLOWED_TRAVEL_FLAGS ); if( travelTime ) { break; } } if( !travelTime ) { continue; } // AAS travel time is in seconds^-2 float factor = 1.0f / Q_RSqrt( 1.0001f - BoundedFraction( travelTime, 200 ) ); result.push_back( EntAndScore( candidate.entNum, candidate.score * factor ) ); } } else { for( EntAndScore &candidate: candidateEntities ) { edict_t *ent = gameEdicts + candidate.entNum; int toAreaNum = FindMostFeasibleEntityAasArea( ent, aasWorld ); if( !toAreaNum ) { continue; } int travelTime = routeCache->TravelTimeToGoalArea( fromAreaNum, toAreaNum, Bot::ALLOWED_TRAVEL_FLAGS ); if( !travelTime ) { continue; } float factor = 1.0f / Q_RSqrt( 1.0001f - BoundedFraction( travelTime, 200 ) ); result.push_back( EntAndScore( candidate.entNum, candidate.score * factor ) ); } } // Sort entities so best entities are first std::sort( result.begin(), result.end() ); }
//========================================== // BOT_DMclass_FindEnemy // Scan for enemy (simplifed for now to just pick any visible enemy) //========================================== void BOT_DMclass_FindEnemy( edict_t *self ) { #define WEIGHT_MAXDISTANCE_FACTOR 15000 nav_ents_t *goalEnt; edict_t *bestTarget = NULL; float dist, weight, bestWeight = 9999999; vec3_t forward, vec; int i; if( G_ISGHOSTING( self ) || GS_MatchState() == MATCH_STATE_COUNTDOWN || GS_ShootingDisabled() ) { self->ai->enemyReactionDelay = 0; self->enemy = self->ai->latched_enemy = NULL; return; } // we also latch NULL enemies, so the bot can loose them if( self->ai->enemyReactionDelay > 0 ) { self->ai->enemyReactionDelay -= game.frametime; return; } self->enemy = self->ai->latched_enemy; FOREACH_GOALENT( goalEnt ) { i = goalEnt->id; if( !goalEnt->ent || !goalEnt->ent->r.inuse ) continue; if( !goalEnt->ent->r.client ) // this may be changed, there could be enemies which aren't clients continue; if( G_ISGHOSTING( goalEnt->ent ) ) continue; if( self->ai->status.entityWeights[i] <= 0 || goalEnt->ent->flags & (FL_NOTARGET|FL_BUSY) ) continue; if( GS_TeamBasedGametype() && goalEnt->ent->s.team == self->s.team ) continue; dist = DistanceFast( self->s.origin, goalEnt->ent->s.origin ); // ignore very soft weighted enemies unless they are in your face if( dist > 500 && self->ai->status.entityWeights[i] <= 0.1f ) continue; //if( dist > 700 && dist > WEIGHT_MAXDISTANCE_FACTOR * self->ai->status.entityWeights[i] ) // continue; weight = dist / self->ai->status.entityWeights[i]; if( weight < bestWeight ) { if( trap_inPVS( self->s.origin, goalEnt->ent->s.origin ) && G_Visible( self, goalEnt->ent ) ) { bool close = dist < 2000 || goalEnt->ent == self->ai->last_attacker; if( !close ) { VectorSubtract( goalEnt->ent->s.origin, self->s.origin, vec ); VectorNormalize( vec ); close = DotProduct( vec, forward ) > 0.3; } if( close ) { bestWeight = weight; bestTarget = goalEnt->ent; } } } } AI_NewEnemyInView( self, bestTarget ); #undef WEIGHT_MAXDISTANCE_FACTOR }
//========================================== // BOT_DMclass_CombatMovement // // NOTE: Very simple for now, just a basic move about avoidance. // Change this routine for more advanced attack movement. //========================================== void BOT_DMclass_CombatMovement( edict_t *self, usercmd_t *ucmd ) { float c; float dist; bool rocket = false; vec3_t away_from_rocket = { 0, 0, 0 }; if( !self->enemy || self->ai->rush_item ) { BOT_DMclass_Move( self, ucmd ); return; } if( self->ai->pers.skillLevel >= 0.25f ) rocket = BOT_DMclass_FindRocket( self, away_from_rocket ); dist = DistanceFast( self->s.origin, self->enemy->s.origin ); c = random(); if( level.time > self->ai->combatmovepush_timeout ) { bool canMOVELEFT, canMOVERIGHT, canMOVEFRONT, canMOVEBACK; canMOVELEFT = AI_CanMove( self, BOT_MOVE_LEFT ); canMOVERIGHT = AI_CanMove( self, BOT_MOVE_RIGHT ); canMOVEFRONT = AI_CanMove( self, BOT_MOVE_FORWARD ); canMOVEBACK = AI_CanMove( self, BOT_MOVE_BACK ); self->ai->combatmovepush_timeout = level.time + AI_COMBATMOVE_TIMEOUT; VectorClear( self->ai->combatmovepushes ); if( rocket ) { //VectorScale(away_from_rocket,1,self->ai->combatmovepushes); if( away_from_rocket[0] ) { if( ( away_from_rocket[0] < 0 ) && canMOVEBACK ) self->ai->combatmovepushes[0] = -1; else if( ( away_from_rocket[0] > 0 ) && canMOVEFRONT ) self->ai->combatmovepushes[0] = 1; } if( away_from_rocket[1] ) { if( ( away_from_rocket[1] < 0 ) && canMOVELEFT ) self->ai->combatmovepushes[1] = -1; else if( ( away_from_rocket[1] > 0 ) && canMOVERIGHT ) self->ai->combatmovepushes[1] = 1; } ucmd->buttons |= BUTTON_SPECIAL; } else if( dist < 150 ) // range = AIWEAP_MELEE_RANGE; { if( self->s.weapon == WEAP_GUNBLADE ) // go into him! { ucmd->buttons &= ~BUTTON_ATTACK; // remove pressing fire if( canMOVEFRONT ) // move to your enemy self->ai->combatmovepushes[0] = 1; else if( c <= 0.5 && canMOVELEFT ) self->ai->combatmovepushes[1] = -1; else if( canMOVERIGHT ) self->ai->combatmovepushes[1] = 1; } else { //priorize sides if( canMOVELEFT || canMOVERIGHT ) { if( canMOVELEFT && canMOVERIGHT ) { self->ai->combatmovepushes[1] = c < 0.5 ? -1 : 1; } else if( canMOVELEFT ) { self->ai->combatmovepushes[1] = -1; } else { self->ai->combatmovepushes[1] = 1; } } if( c < 0.3 && canMOVEBACK ) self->ai->combatmovepushes[0] = -1; } } else if( dist < 500 ) //AIWEAP_SHORT_RANGE limit is Grenade Laucher range { if( canMOVELEFT || canMOVERIGHT ) { if( canMOVELEFT && canMOVERIGHT ) { self->ai->combatmovepushes[1] = c < 0.5 ? -1 : 1; } else if( canMOVELEFT ) { self->ai->combatmovepushes[1] = -1; } else { self->ai->combatmovepushes[1] = 1; } } if( c < 0.3 && canMOVEFRONT ) { self->ai->combatmovepushes[0] = 1; } } else if( dist < 900 ) { if( canMOVELEFT || canMOVERIGHT ) { if( canMOVELEFT && canMOVERIGHT ) { self->ai->combatmovepushes[1] = c < 0.5 ? -1 : 1; } else if( canMOVELEFT ) { self->ai->combatmovepushes[1] = -1; } else { self->ai->combatmovepushes[1] = 1; } } } else //range = AIWEAP_LONG_RANGE; { if( c < 0.75 && ( canMOVELEFT || canMOVERIGHT ) ) { if( canMOVELEFT && canMOVERIGHT ) { self->ai->combatmovepushes[1] = c < 0.5 ? -1 : 1; } else if( canMOVELEFT ) { self->ai->combatmovepushes[1] = -1; } else { self->ai->combatmovepushes[1] = 1; } } } } if( !rocket && ( self->health < 25 || ( dist >= 500 && c < 0.2 ) || ( dist >= 1000 && c < 0.5 ) ) ) { BOT_DMclass_Move( self, ucmd ); } if( !self->ai->camp_item ) { ucmd->forwardmove = self->ai->combatmovepushes[0]; } ucmd->sidemove = self->ai->combatmovepushes[1]; ucmd->upmove = self->ai->combatmovepushes[2]; }
void BOT_DMclass_Move( edict_t *self, usercmd_t *ucmd ) { #define BOT_FORWARD_EPSILON 0.5f int i; unsigned int linkType; bool printLink = false; bool nodeReached = false; bool specialMovement = false; vec3_t v1, v2; vec3_t lookdir, pathdir; float lookDot; if( self->ai->next_node == NODE_INVALID || self->ai->goal_node == NODE_INVALID ) { BOT_DMclass_MoveWander( self, ucmd ); return; } linkType = AI_CurrentLinkType( self ); specialMovement = ( self->ai->path.numNodes >= MIN_BUNNY_NODES ) ? true : false; if( AI_GetNodeFlags( self->ai->next_node ) & (NODEFLAGS_REACHATTOUCH|NODEFLAGS_ENTITYREACH) ) specialMovement = false; if( linkType & (LINK_JUMP|LINK_JUMPPAD|LINK_CROUCH|LINK_FALL|LINK_WATER|LINK_LADDER|LINK_ROCKETJUMP) ) specialMovement = false; if( self->ai->pers.skillLevel < 0.33f ) specialMovement = false; if( specialMovement == false || self->groundentity ) self->ai->is_bunnyhop = false; VectorSubtract( nodes[self->ai->next_node].origin, self->s.origin, self->ai->move_vector ); // 2D, normalized versions of look and path directions pathdir[0] = self->ai->move_vector[0]; pathdir[1] = self->ai->move_vector[1]; pathdir[2] = 0.0f; VectorNormalize( pathdir ); AngleVectors( self->s.angles, lookdir, NULL, NULL ); lookdir[2] = 0.0f; VectorNormalize( lookdir ); lookDot = DotProduct( lookdir, pathdir ); // Ladder movement if( self->is_ladder ) { ucmd->forwardmove = 0; ucmd->upmove = 1; ucmd->sidemove = 0; if( nav.debugMode && printLink ) G_PrintChasersf( self, "LINK_LADDER\n" ); nodeReached = AI_NodeReached_Generic( self ); } else if( linkType & LINK_JUMPPAD ) { VectorCopy( self->s.origin, v1 ); VectorCopy( nodes[self->ai->next_node].origin, v2 ); v1[2] = v2[2] = 0; if( DistanceFast( v1, v2 ) > 32 && lookDot > BOT_FORWARD_EPSILON ) { ucmd->forwardmove = 1; // push towards destination ucmd->buttons |= BUTTON_WALK; } nodeReached = self->groundentity != NULL && AI_NodeReached_Generic( self ); } // Platform riding - No move, riding elevator else if( linkType & LINK_PLATFORM ) { VectorCopy( self->s.origin, v1 ); VectorCopy( nodes[self->ai->next_node].origin, v2 ); v1[2] = v2[2] = 0; if( DistanceFast( v1, v2 ) > 32 && lookDot > BOT_FORWARD_EPSILON ) ucmd->forwardmove = 1; // walk to center ucmd->buttons |= BUTTON_WALK; ucmd->upmove = 0; ucmd->sidemove = 0; if( nav.debugMode && printLink ) G_PrintChasersf( self, "LINK_PLATFORM (riding)\n" ); self->ai->move_vector[2] = 0; // put view horizontal nodeReached = AI_NodeReached_PlatformEnd( self ); } // entering platform else if( AI_GetNodeFlags( self->ai->next_node ) & NODEFLAGS_PLATFORM ) { ucmd->forwardmove = 1; ucmd->upmove = 0; ucmd->sidemove = 0; if( lookDot <= BOT_FORWARD_EPSILON ) ucmd->buttons |= BUTTON_WALK; if( nav.debugMode && printLink ) G_PrintChasersf( self, "NODEFLAGS_PLATFORM (moving to plat)\n" ); // is lift down? for( i = 0; i < nav.num_navigableEnts; i++ ) { if( nav.navigableEnts[i].node == self->ai->next_node ) { //testing line //vec3_t tPoint; //int j; //for(j=0; j<3; j++)//center of the ent // tPoint[j] = nav.ents[i].ent->s.origin[j] + 0.5*(nav.ents[i].ent->r.mins[j] + nav.ents[i].ent->r.maxs[j]); //tPoint[2] = nav.ents[i].ent->s.origin[2] + nav.ents[i].ent->r.maxs[2]; //tPoint[2] += 8; //AITools_DrawLine( self->s.origin, tPoint ); //if not reachable, wait for it (only height matters) if( ( nav.navigableEnts[i].ent->s.origin[2] + nav.navigableEnts[i].ent->r.maxs[2] ) > ( self->s.origin[2] + self->r.mins[2] + AI_JUMPABLE_HEIGHT ) && nav.navigableEnts[i].ent->moveinfo.state != STATE_BOTTOM ) { self->ai->blocked_timeout = level.time + 10000; ucmd->forwardmove = 0; } } } nodeReached = AI_NodeReached_PlatformStart( self ); } // Falling off ledge or jumping else if( !self->groundentity && !self->is_step && !self->is_swim && !self->ai->is_bunnyhop ) { ucmd->upmove = 0; ucmd->sidemove = 0; ucmd->forwardmove = 0; if( lookDot > BOT_FORWARD_EPSILON ) { ucmd->forwardmove = 1; // add fake strafe accel if( !(linkType & LINK_FALL) || linkType & (LINK_JUMP|LINK_ROCKETJUMP) ) { if( linkType & LINK_JUMP ) { if( AI_AttemptWalljump( self ) ) { ucmd->buttons |= BUTTON_SPECIAL; } if( VectorLengthFast( tv( self->velocity[0], self->velocity[1], 0 ) ) < 600 ) VectorMA( self->velocity, 6.0f, lookdir, self->velocity ); } else { if( VectorLengthFast( tv( self->velocity[0], self->velocity[1], 0 ) ) < 450 ) VectorMA( self->velocity, 1.0f, lookdir, self->velocity ); } } } else if( lookDot < -BOT_FORWARD_EPSILON ) ucmd->forwardmove = -1; if( nav.debugMode && printLink ) G_PrintChasersf( self, "FLY MOVE\n" ); nodeReached = AI_NodeReached_Generic( self ); } else // standard movement { ucmd->forwardmove = 1; ucmd->upmove = 0; ucmd->sidemove = 0; // starting a jump if( ( linkType & LINK_JUMP ) ) { if( self->groundentity ) { trace_t trace; vec3_t v1, v2; if( nav.debugMode && printLink ) G_PrintChasersf( self, "LINK_JUMP\n" ); //check floor in front, if there's none... Jump! VectorCopy( self->s.origin, v1 ); VectorNormalize2( self->ai->move_vector, v2 ); VectorMA( v1, 18, v2, v1 ); v1[2] += self->r.mins[2]; VectorCopy( v1, v2 ); v2[2] -= AI_JUMPABLE_HEIGHT; G_Trace( &trace, v1, vec3_origin, vec3_origin, v2, self, MASK_AISOLID ); if( !trace.startsolid && trace.fraction == 1.0 ) { //jump! // prevent double jumping on crates VectorCopy( self->s.origin, v1 ); v1[2] += self->r.mins[2]; G_Trace( &trace, v1, tv( -12, -12, -8 ), tv( 12, 12, 0 ), v1, self, MASK_AISOLID ); if( trace.startsolid ) ucmd->upmove = 1; } } nodeReached = AI_NodeReached_Generic( self ); } // starting a rocket jump else if( ( linkType & LINK_ROCKETJUMP ) ) { if( nav.debugMode && printLink ) G_PrintChasersf( self, "LINK_ROCKETJUMP\n" ); if( !self->ai->rj_triggered && self->groundentity && ( self->s.weapon == WEAP_ROCKETLAUNCHER ) ) { self->s.angles[PITCH] = 170; ucmd->upmove = 1; ucmd->buttons |= BUTTON_ATTACK; self->ai->rj_triggered = true; } nodeReached = AI_NodeReached_Generic( self ); } else { // Move To Short Range goal (not following paths) // plats, grapple, etc have higher priority than SR Goals, cause the bot will // drop from them and have to repeat the process from the beginning if( AI_MoveToShortRangeGoalEntity( self, ucmd ) ) { nodeReached = AI_NodeReached_Generic( self ); } else if( specialMovement && !self->is_swim ) // bunny-hopping movement here { BOT_DMclass_SpecialMove( self, lookdir, pathdir, ucmd ); nodeReached = AI_NodeReached_Special( self ); } else { nodeReached = AI_NodeReached_Generic( self ); } } // if static assume blocked and try to get free if( VectorLengthFast( self->velocity ) < 37 && ( ucmd->forwardmove || ucmd->sidemove || ucmd->upmove ) ) { if( random() > 0.1 && AI_SpecialMove( self, ucmd ) ) // jumps, crouches, turns... return; self->s.angles[YAW] += brandom( -90, 90 ); } } // swimming if( self->is_swim ) { if( !( G_PointContents( nodes[self->ai->next_node].origin ) & MASK_WATER ) ) // Exit water ucmd->upmove = 1; } AI_ChangeAngle( self ); if( nodeReached ) AI_NodeReached( self ); #undef BOT_FORWARD_EPSILON }
/* * CG_SpawnDecal */ void CG_SpawnDecal( vec3_t origin, vec3_t dir, float orient, float radius, float r, float g, float b, float a, float die, float fadetime, bool fadealpha, struct shader_s *shader ) { int i, j; cdecal_t *dl; poly_t *poly; vec3_t axis[3]; vec3_t verts[MAX_DECAL_VERTS]; vec3_t v; byte_vec4_t color; fragment_t *fr, fragments[MAX_DECAL_FRAGMENTS]; int numfragments; float dietime, fadefreq; if( !cg_addDecals->integer ) return; // invalid decal if( radius <= 0 || VectorCompare( dir, vec3_origin ) ) return; // we don't spawn decals if too far away (we could move there, but players won't notice there should be a decal by then) if( DistanceFast( origin, cg.view.origin ) * cg.view.fracDistFOV > 2048 ) return; // calculate orientation matrix VectorNormalize2( dir, axis[0] ); PerpendicularVector( axis[1], axis[0] ); RotatePointAroundVector( axis[2], axis[0], axis[1], orient ); CrossProduct( axis[0], axis[2], axis[1] ); numfragments = trap_R_GetClippedFragments( origin, radius, axis, // clip it MAX_DECAL_VERTS, verts, MAX_DECAL_FRAGMENTS, fragments ); // no valid fragments if( !numfragments ) return; // clamp and scale colors if( r < 0 ) r = 0;else if( r > 1 ) r = 255;else r *= 255; if( g < 0 ) g = 0;else if( g > 1 ) g = 255;else g *= 255; if( b < 0 ) b = 0;else if( b > 1 ) b = 255;else b *= 255; if( a < 0 ) a = 0;else if( a > 1 ) a = 255;else a *= 255; color[0] = ( qbyte )( r ); color[1] = ( qbyte )( g ); color[2] = ( qbyte )( b ); color[3] = ( qbyte )( a ); radius = 0.5f / radius; VectorScale( axis[1], radius, axis[1] ); VectorScale( axis[2], radius, axis[2] ); dietime = cg.time + die * 1000; fadefreq = 0.001f / min( fadetime, die ); fadetime = cg.time + ( die - min( fadetime, die ) ) * 1000; for( i = 0, fr = fragments; i < numfragments; i++, fr++ ) { if( fr->numverts > MAX_DECAL_VERTS ) return; else if( fr->numverts <= 0 ) continue; // allocate decal dl = CG_AllocDecal(); dl->die = dietime; dl->fadetime = fadetime; dl->fadefreq = fadefreq; dl->fadealpha = fadealpha; dl->shader = shader; dl->color[0] = r; dl->color[1] = g; dl->color[2] = b; dl->color[3] = a; // setup polygon for drawing poly = dl->poly; poly->shader = shader; poly->numverts = fr->numverts; poly->fognum = fr->fognum; for( j = 0; j < fr->numverts; j++ ) { VectorCopy( verts[fr->firstvert+j], poly->verts[j] ); VectorCopy( fr->normal, poly->normals[j] ); VectorSubtract( poly->verts[j], origin, v ); poly->stcoords[j][0] = DotProduct( v, axis[1] ) + 0.5f; poly->stcoords[j][1] = DotProduct( v, axis[2] ) + 0.5f; *( int * )poly->colors[j] = *( int * )color; } } }
//========================================== // BOT_DMclass_FindEnemy // Scan for enemy (simplifed for now to just pick any visible enemy) //========================================== void BOT_DMclass_FindEnemy( edict_t *self ) { #define WEIGHT_MAXDISTANCE_FACTOR 15000 nav_ents_t *goalEnt; edict_t *bestTarget = NULL; float dist, weight, bestWeight = 9999999; int i; if( G_ISGHOSTING( self ) || GS_MatchState() == MATCH_STATE_COUNTDOWN || GS_ShootingDisabled() ) { self->ai.enemyReactionDelay = 0; self->enemy = self->ai.latched_enemy = NULL; return; } // we also latch NULL enemies, so the bot can loose them if( self->ai.enemyReactionDelay > 0 ) { self->ai.enemyReactionDelay -= game.frametime; return; } self->enemy = self->ai.latched_enemy; for( i = 0; i < nav.num_goalEnts; i++ ) { goalEnt = &nav.goalEnts[i]; if( !goalEnt->ent || !goalEnt->ent->r.inuse ) continue; if( !goalEnt->ent->r.client ) // this may be changed, there could be enemies which aren't clients continue; if( G_ISGHOSTING( goalEnt->ent ) ) continue; if( self->ai.status.entityWeights[i] <= 0 || goalEnt->ent->ai.notarget ) continue; if( GS_TeamBasedGametype() && goalEnt->ent->s.team == self->s.team ) continue; dist = DistanceFast( self->s.origin, goalEnt->ent->s.origin ); // ignore very soft weighted enemies unless they are in your face if( dist > 500 && self->ai.status.entityWeights[i] <= 0.1f ) continue; if( dist > 700 && dist > WEIGHT_MAXDISTANCE_FACTOR * self->ai.status.entityWeights[i] ) continue; if( trap_inPVS( self->s.origin, goalEnt->ent->s.origin ) && G_Visible( self, goalEnt->ent ) ) { weight = dist / self->ai.status.entityWeights[i]; if( ( dist < 350 ) || G_InFront( self, goalEnt->ent ) ) { if( weight < bestWeight ) { bestWeight = weight; bestTarget = goalEnt->ent; } } } } AI_NewEnemyInView( self, bestTarget ); #undef WEIGHT_MAXDISTANCE_FACTOR }
/* * G_SplashFrac */ void G_SplashFrac( const vec3_t origin, const vec3_t mins, const vec3_t maxs, const vec3_t point, float maxradius, vec3_t pushdir, float *kickFrac, float *dmgFrac ) { #define VERTICALBIAS 0.65f // 0...1 #define CAPSULEDISTANCE #define SPLASH_HDIST_CLAMP 53 vec3_t boxcenter = { 0, 0, 0 }; vec3_t hitpoint, vec; float distance; int i; float innerradius; float outerradius; float refdistance; if( maxradius <= 0 ) { if( kickFrac ) *kickFrac = 0; if( dmgFrac ) *dmgFrac = 0; if( pushdir ) VectorClear( pushdir ); return; } VectorCopy( point, hitpoint ); innerradius = ( maxs[0] + maxs[1] - mins[0] - mins[1] ) * 0.25; outerradius = ( sqrt( maxs[0]*maxs[0] + maxs[1]*maxs[1] ) + sqrt( mins[0]*mins[0] + mins[1]*mins[1] ) ) * 0.5; #ifdef CAPSULEDISTANCE // Find the distance to the closest point in the capsule contained in the player bbox // modify the origin so the inner sphere acts as a capsule VectorCopy( origin, boxcenter ); boxcenter[2] = hitpoint[2]; clamp( boxcenter[2], ( origin[2] + mins[2] ) + innerradius, ( origin[2] + maxs[2] ) - innerradius ); #else // find center of the box for( i = 0; i < 3; i++ ) boxcenter[i] = origin[i] + ( 0.5f * ( maxs[i] + mins[i] ) ); #endif // find push intensity distance = DistanceFast( boxcenter, hitpoint ); if( distance >= maxradius ) { if( kickFrac ) *kickFrac = 0; if( dmgFrac ) *dmgFrac = 0; if( pushdir ) VectorClear( pushdir ); return; } refdistance = innerradius; if( refdistance >= maxradius ) { if( kickFrac ) *kickFrac = 0; if( dmgFrac ) *dmgFrac = 0; if( pushdir ) VectorClear( pushdir ); return; } maxradius -= refdistance; distance -= refdistance; if( distance < 0 ) distance = 0; distance = maxradius - distance; clamp( distance, 0, maxradius ); if( dmgFrac ) { // soft sin curve *dmgFrac = sin( DEG2RAD( ( distance / maxradius ) * 80 ) ); clamp( *dmgFrac, 0.0f, 1.0f ); } if( kickFrac ) { // linear kick fraction float kick = ( distance / maxradius ); // half linear half exponential //*kickFrac = ( kick + ( kick * kick ) ) * 0.5f; // linear *kickFrac = kick; clamp( *kickFrac, 0.0f, 1.0f ); } //if( dmgFrac && kickFrac ) // G_Printf( "SPLASH: dmgFrac %.2f kickFrac %.2f\n", *dmgFrac, *kickFrac ); // find push direction if( pushdir ) { #ifdef CAPSULEDISTANCE // find real center of the box again for( i = 0; i < 3; i++ ) boxcenter[i] = origin[i] + ( 0.5f * ( maxs[i] + mins[i] ) ); #endif #ifdef VERTICALBIAS // move the center up for the push direction if( origin[2] + maxs[2] > boxcenter[2] ) boxcenter[2] += VERTICALBIAS * ( ( origin[2] + maxs[2] ) - boxcenter[2] ); #endif // VERTICALBIAS #ifdef SPLASH_HDIST_CLAMP // if pushed from below, hack the hitpoint to limit the side push direction if( hitpoint[2] < boxcenter[2] && SPLASH_HDIST_CLAMP > 0 ) { // do not allow the hitpoint to be further away // than SPLASH_HDIST_CLAMP in the horizontal axis vec[0] = hitpoint[0]; vec[1] = hitpoint[1]; vec[2] = boxcenter[2]; if( DistanceFast( boxcenter, vec ) > SPLASH_HDIST_CLAMP ) { VectorSubtract( vec, boxcenter, pushdir ); VectorNormalizeFast( pushdir ); VectorMA( boxcenter, SPLASH_HDIST_CLAMP, pushdir, hitpoint ); hitpoint[2] = point[2]; // restore the original hitpoint height } } #endif // SPLASH_HDIST_CLAMP VectorSubtract( boxcenter, hitpoint, pushdir ); VectorNormalizeFast( pushdir ); } #undef VERTICALBIAS #undef CAPSULEDISTANCE #undef SPLASH_HDIST_CLAMP }
void CG_LaserBeamEffect( centity_t *cent ) { struct sfx_s *sound = NULL; float range; trace_t trace; orientation_t projectsource; vec4_t color; vec3_t laserOrigin, laserAngles, laserPoint; int i, j; if( cent->localEffects[LOCALEFFECT_LASERBEAM] <= cg.time ) return; laserOwner = cent; if( cg_teamColoredBeams->integer && ( cent->current.team == TEAM_ALPHA || cent->current.team == TEAM_BETA ) ) CG_TeamColor( cent->current.team, color ); else Vector4Set( color, 1, 1, 1, 1 ); // interpolate the positions if( ISVIEWERENTITY( cent->current.number ) && !cg.view.thirdperson ) { VectorCopy( cg.predictedPlayerState.pmove.origin, laserOrigin ); laserOrigin[2] += cg.predictedPlayerState.viewheight; VectorCopy( cg.predictedPlayerState.viewangles, laserAngles ); VectorLerp( cent->laserPointOld, cg.lerpfrac, cent->laserPoint, laserPoint ); } else { VectorLerp( cent->laserOriginOld, cg.lerpfrac, cent->laserOrigin, laserOrigin ); VectorLerp( cent->laserPointOld, cg.lerpfrac, cent->laserPoint, laserPoint ); if( !cent->laserCurved ) { vec3_t dir; // make up the angles from the start and end points (s->angles is not so precise) VectorSubtract( laserPoint, laserOrigin, dir ); VecToAngles( dir, laserAngles ); } else // use player entity angles { for( i = 0; i < 3; i++ ) laserAngles[i] = LerpAngle( cent->prev.angles[i], cent->current.angles[i], cg.lerpfrac ); } } if( !cent->laserCurved ) { range = GS_GetWeaponDef( WEAP_LASERGUN )->firedef.timeout; if( cent->current.effects & EF_QUAD ) sound = CG_MediaSfx( cgs.media.sfxLasergunStrongQuadHum ); else sound = CG_MediaSfx( cgs.media.sfxLasergunStrongHum ); // trace the beam: for tracing we use the real beam origin GS_TraceLaserBeam( &trace, laserOrigin, laserAngles, range, cent->current.number, 0, _LaserImpact ); // draw the beam: for drawing we use the weapon projection source (already handles the case of viewer entity) if( !CG_PModel_GetProjectionSource( cent->current.number, &projectsource ) ) VectorCopy( laserOrigin, projectsource.origin ); CG_KillPolyBeamsByTag( cent->current.number ); CG_LaserGunPolyBeam( projectsource.origin, trace.endpos, color, cent->current.number ); } else { float frac, subdivisions = cg_laserBeamSubdivisions->integer; vec3_t from, dir, end, blendPoint; int passthrough = cent->current.number; vec3_t tmpangles, blendAngles; range = GS_GetWeaponDef( WEAP_LASERGUN )->firedef_weak.timeout; if( cent->current.effects & EF_QUAD ) sound = CG_MediaSfx( cgs.media.sfxLasergunWeakQuadHum ); else sound = CG_MediaSfx( cgs.media.sfxLasergunWeakHum ); // trace the beam: for tracing we use the real beam origin GS_TraceCurveLaserBeam( &trace, laserOrigin, laserAngles, laserPoint, cent->current.number, 0, _LaserImpact ); // draw the beam: for drawing we use the weapon projection source (already handles the case of viewer entity) if( !CG_PModel_GetProjectionSource( cent->current.number, &projectsource ) ) VectorCopy( laserOrigin, projectsource.origin ); if( subdivisions < CURVELASERBEAM_SUBDIVISIONS ) subdivisions = CURVELASERBEAM_SUBDIVISIONS; CG_KillPolyBeamsByTag( cent->current.number ); // we redraw the full beam again, and trace each segment for stop dead impact VectorCopy( laserPoint, blendPoint ); VectorCopy( projectsource.origin, from ); VectorSubtract( blendPoint, projectsource.origin, dir ); VecToAngles( dir, blendAngles ); for( i = 1; i <= (int)subdivisions; i++ ) { frac = ( ( range/subdivisions )*(float)i ) / (float)range; for( j = 0; j < 3; j++ ) tmpangles[j] = LerpAngle( laserAngles[j], blendAngles[j], frac ); AngleVectors( tmpangles, dir, NULL, NULL ); VectorMA( projectsource.origin, range * frac, dir, end ); GS_TraceLaserBeam( &trace, from, tmpangles, DistanceFast( from, end ), passthrough, 0, NULL ); CG_LaserGunPolyBeam( from, trace.endpos, color, cent->current.number ); if( trace.fraction != 1.0f ) break; passthrough = trace.ent; VectorCopy( trace.endpos, from ); } } // enable continuous flash on the weapon owner if( cg_weaponFlashes->integer ) cg_entPModels[cent->current.number].flash_time = cg.time + CG_GetWeaponInfo( WEAP_LASERGUN )->flashTime; if( sound ) { if( ISVIEWERENTITY( cent->current.number ) ) trap_S_AddLoopSound( sound, cent->current.number, 1.0, ATTN_NONE ); else trap_S_AddLoopSound( sound, cent->current.number, 1.0, ATTN_STATIC ); } laserOwner = NULL; }
//========================================== // BOT_DMclass_ChooseWeapon // Choose weapon based on range & weights //========================================== static float BOT_DMclass_ChooseWeapon( edict_t *self ) { float dist; int i; float best_weight = 0.0; gsitem_t *weaponItem; int curweapon, weapon_range = 0, best_weapon = WEAP_NONE; curweapon = self->r.client->ps.stats[STAT_PENDING_WEAPON]; // if no enemy, then what are we doing here? if( !self->enemy ) { weapon_range = AIWEAP_MEDIUM_RANGE; if( curweapon == WEAP_GUNBLADE || curweapon == WEAP_NONE ) self->ai->changeweapon_timeout = level.time; } else // Base weapon selection on distance: { dist = DistanceFast( self->s.origin, self->enemy->s.origin ); if( dist < 150 ) weapon_range = AIWEAP_MELEE_RANGE; else if( dist < 500 ) // Medium range limit is Grenade launcher range weapon_range = AIWEAP_SHORT_RANGE; else if( dist < 900 ) weapon_range = AIWEAP_MEDIUM_RANGE; else weapon_range = AIWEAP_LONG_RANGE; } if( self->ai->changeweapon_timeout > level.time ) return AIWeapons[curweapon].RangeWeight[weapon_range]; for( i = WEAP_GUNBLADE; i < WEAP_TOTAL; i++ ) { float rangeWeight; if( ( weaponItem = GS_FindItemByTag( i ) ) == NULL ) continue; if( !GS_CheckAmmoInWeapon( &self->r.client->ps, i ) ) continue; rangeWeight = AIWeapons[i].RangeWeight[weapon_range] * self->ai->pers.cha.weapon_affinity[i - ( WEAP_GUNBLADE - 1 )]; // weigh up if having strong ammo if( self->r.client->ps.inventory[weaponItem->ammo_tag] ) rangeWeight *= 1.25; // add a small random factor (less random the more skill) rangeWeight += brandom( -( 1.0 - self->ai->pers.skillLevel ), 1.0 - self->ai->pers.skillLevel ); // compare range weights if( rangeWeight > best_weight ) { best_weight = rangeWeight; best_weapon = i; } } // do the change (same weapon, or null best_weapon is covered at ChangeWeapon) if( best_weapon != WEAP_NONE ) BOT_DMClass_ChangeWeapon( self, best_weapon ); return AIWeapons[curweapon].RangeWeight[weapon_range]; // return current }
//========================================== // AI_PickLongRangeGoal // // Evaluate the best long range goal and send the bot on // its way. This is a good time waster, so use it sparingly. // Do not call it for every think cycle. // // jal: I don't think there is any problem by calling it, // now that we have stored the costs at the nav.costs table (I don't do it anyway) //========================================== void AI_PickLongRangeGoal( edict_t *self ) { #define WEIGHT_MAXDISTANCE_FACTOR 20000.0f #define COST_INFLUENCE 0.5f int i; float weight, bestWeight = 0.0; int current_node; float cost; float dist; nav_ents_t *goalEnt, *bestGoalEnt = NULL; AI_ClearGoal( self ); if( G_ISGHOSTING( self ) ) return; if( self->ai->longRangeGoalTimeout > level.time ) return; if( !self->r.client->ps.pmove.stats[PM_STAT_MAXSPEED] ) { return; } self->ai->longRangeGoalTimeout = level.time + AI_LONG_RANGE_GOAL_DELAY + brandom( 0, 1000 ); // look for a target current_node = AI_FindClosestReachableNode( self->s.origin, self, ( ( 1 + self->ai->nearest_node_tries ) * NODE_DENSITY ), NODE_ALL ); self->ai->current_node = current_node; if( current_node == NODE_INVALID ) { if( nav.debugMode && bot_showlrgoal->integer ) G_PrintChasersf( self, "%s: LRGOAL: Closest node not found. Tries:%i\n", self->ai->pers.netname, self->ai->nearest_node_tries ); self->ai->nearest_node_tries++; // extend search radius with each try return; } self->ai->nearest_node_tries = 0; // Run the list of potential goal entities FOREACH_GOALENT( goalEnt ) { i = goalEnt->id; if( !goalEnt->ent ) continue; if( !goalEnt->ent->r.inuse ) { goalEnt->node = NODE_INVALID; continue; } if( goalEnt->ent->r.client ) { if( G_ISGHOSTING( goalEnt->ent ) || goalEnt->ent->flags & (FL_NOTARGET|FL_BUSY) ) goalEnt->node = NODE_INVALID; else goalEnt->node = AI_FindClosestReachableNode( goalEnt->ent->s.origin, goalEnt->ent, NODE_DENSITY, NODE_ALL ); } if( goalEnt->ent->item ) { if( !G_Gametype_CanPickUpItem( goalEnt->ent->item ) ) continue; } if( goalEnt->node == NODE_INVALID ) continue; weight = self->ai->status.entityWeights[i]; if( weight <= 0.0f ) continue; // don't try to find cost for too far away objects dist = DistanceFast( self->s.origin, goalEnt->ent->s.origin ); if( dist > WEIGHT_MAXDISTANCE_FACTOR * weight/* || dist < AI_GOAL_SR_RADIUS*/ ) continue; cost = AI_FindCost( current_node, goalEnt->node, self->ai->status.moveTypesMask ); if( cost == NODE_INVALID ) continue; cost -= brandom( 0, 2000 ); // allow random variations clamp_low( cost, 1 ); weight = ( 1000 * weight ) / ( cost * COST_INFLUENCE ); // Check against cost of getting there if( weight > bestWeight ) { bestWeight = weight; bestGoalEnt = goalEnt; } } if( bestGoalEnt ) { self->ai->goalEnt = bestGoalEnt; AI_SetGoal( self, bestGoalEnt->node ); if( self->ai->goalEnt != NULL && nav.debugMode && bot_showlrgoal->integer ) G_PrintChasersf( self, "%s: selected a %s at node %d for LR goal. (weight %f)\n", self->ai->pers.netname, self->ai->goalEnt->ent->classname, self->ai->goalEnt->node, bestWeight ); return; } if( nav.debugMode && bot_showlrgoal->integer ) G_PrintChasersf( self, "%s: did not find a LR goal.\n", self->ai->pers.netname ); #undef WEIGHT_MAXDISTANCE_FACTOR #undef COST_INFLUENCE }
/* * W_Fire_Electrobolt_Combined */ void W_Fire_Electrobolt_Combined( edict_t *self, vec3_t start, vec3_t angles, float maxdamage, float mindamage, float maxknockback, float minknockback, int stun, int range, int mod, int timeDelta ) { vec3_t from, end, dir; trace_t tr; edict_t *ignore, *event, *hit, *damaged; int mask; qboolean missed = qtrue; int dmgflags = 0; int fireMode; #ifdef ELECTROBOLT_TEST fireMode = FIRE_MODE_WEAK; #else fireMode = FIRE_MODE_STRONG; #endif if( GS_Instagib() ) maxdamage = mindamage = 9999; AngleVectors( angles, dir, NULL, NULL ); VectorMA( start, range, dir, end ); VectorCopy( start, from ); ignore = self; hit = damaged = NULL; mask = MASK_SHOT; if( GS_RaceGametype() ) mask = MASK_SOLID; clamp_high( mindamage, maxdamage ); clamp_high( minknockback, maxknockback ); tr.ent = -1; while( ignore ) { G_Trace4D( &tr, from, NULL, NULL, end, ignore, mask, timeDelta ); VectorCopy( tr.endpos, from ); ignore = NULL; if( tr.ent == -1 ) break; // some entity was touched hit = &game.edicts[tr.ent]; if( hit == world ) // stop dead if hit the world break; if( hit->movetype == MOVETYPE_NONE || hit->movetype == MOVETYPE_PUSH ) break; // allow trail to go through BBOX entities (players, gibs, etc) if( !ISBRUSHMODEL( hit->s.modelindex ) ) ignore = hit; if( ( hit != self ) && ( hit->takedamage ) ) { float frac, damage, knockback; frac = DistanceFast( tr.endpos, start ) / (float)range; clamp( frac, 0.0f, 1.0f ); damage = maxdamage - ( ( maxdamage - mindamage ) * frac ); knockback = maxknockback - ( ( maxknockback - minknockback ) * frac ); G_Damage( hit, self, self, dir, dir, tr.endpos, damage, knockback, stun, dmgflags, mod ); // spawn a impact event on each damaged ent event = G_SpawnEvent( EV_BOLT_EXPLOSION, DirToByte( tr.plane.normal ), tr.endpos ); event->s.firemode = fireMode; if( hit->r.client ) missed = qfalse; damaged = hit; } } if( missed && self->r.client ) G_AwardPlayerMissedElectrobolt( self, mod ); // send the weapon fire effect event = G_SpawnEvent( EV_ELECTROTRAIL, ENTNUM( self ), start ); event->r.svflags = SVF_TRANSMITORIGIN2; VectorScale( dir, 1024, event->s.origin2 ); event->s.firemode = fireMode; if( !GS_Instagib() && tr.ent == -1 ) // didn't touch anything, not even a wall { edict_t *bolt; gs_weapon_definition_t *weapondef = GS_GetWeaponDef( self->s.weapon ); // fire a weak EB from the end position bolt = W_Fire_Electrobolt_Weak( self, end, angles, weapondef->firedef_weak.speed, mindamage, minknockback, minknockback, stun, weapondef->firedef_weak.timeout, mod, timeDelta ); bolt->enemy = damaged; } }
//========================================== // AI_PickShortRangeGoal // Pick best goal based on importance and range. This function // overrides the long range goal selection for items that // are very close to the bot and are reachable. //========================================== static void AI_PickShortRangeGoal( edict_t *self ) { edict_t *bestGoal = NULL; float bestWeight = 0; nav_ents_t *goalEnt; const gsitem_t *item; bool canPickupItems; int i; if( !self->r.client || G_ISGHOSTING( self ) ) return; if( self->ai->state_combat_timeout > level.time ) { self->ai->shortRangeGoalTimeout = self->ai->state_combat_timeout; return; } if( self->ai->shortRangeGoalTimeout > level.time ) return; canPickupItems = (self->r.client->ps.pmove.stats[PM_STAT_FEATURES] & PMFEAT_ITEMPICK) != 0 ? true : false; self->ai->shortRangeGoalTimeout = level.time + AI_SHORT_RANGE_GOAL_DELAY; self->movetarget = NULL; FOREACH_GOALENT( goalEnt ) { float dist; i = goalEnt->id; if( !goalEnt->ent->r.inuse || goalEnt->ent->r.solid == SOLID_NOT ) continue; if( goalEnt->ent->r.client ) continue; if( self->ai->status.entityWeights[i] <= 0.0f ) continue; item = goalEnt->ent->item; if( canPickupItems && item ) { if( !G_Gametype_CanPickUpItem( item ) || !( item->flags & ITFLAG_PICKABLE ) ) { continue; } } dist = DistanceFast( self->s.origin, goalEnt->ent->s.origin ); if( goalEnt == self->ai->goalEnt ) { if( dist > AI_GOAL_SR_LR_RADIUS ) continue; } else { if( dist > AI_GOAL_SR_RADIUS ) continue; } clamp_low( dist, 0.01f ); if( AI_ShortRangeReachable( self, goalEnt->ent->s.origin ) ) { float weight; bool in_front = G_InFront( self, goalEnt->ent ); // Long range goal gets top priority if( in_front && goalEnt == self->ai->goalEnt ) { bestGoal = goalEnt->ent; break; } // get the one with the best weight weight = self->ai->status.entityWeights[i] / dist * (in_front ? 1.0f : 0.5f); if( weight > bestWeight ) { bestWeight = weight; bestGoal = goalEnt->ent; } } } if( bestGoal ) { self->movetarget = bestGoal; if( nav.debugMode && bot_showsrgoal->integer ) G_PrintChasersf( self, "%i %s: selected a %s for SR goal.\n", level.framenum, self->ai->pers.netname, self->movetarget->classname ); } else { // got nothing else to do so keep scanning self->ai->shortRangeGoalTimeout = level.time + AI_SHORT_RANGE_GOAL_DELAY_IDLE; } }
/* * R_DrawShadowmaps */ void R_DrawShadowmaps( void ) { unsigned int i; image_t *shadowmap; int textureWidth, textureHeight; float lodScale; vec3_t lodOrigin; vec3_t viewerOrigin; shadowGroup_t *group; int shadowBits = rn.shadowBits; refdef_t refdef; int lod; float farClip; float dist; if( !rsc.numShadowGroups ) { return; } if( rn.renderFlags & RF_SHADOWMAPVIEW ) { return; } if( rn.refdef.rdflags & RDF_NOWORLDMODEL ) { return; } if( !shadowBits ) { return; } if( !R_PushRefInst() ) { return; } lodScale = rn.lod_dist_scale_for_fov; VectorCopy( rn.lodOrigin, lodOrigin ); VectorCopy( rn.viewOrigin, viewerOrigin ); refdef = rn.refdef; // find lighting group containing entities with same lightingOrigin as ours for( i = 0; i < rsc.numShadowGroups; i++ ) { if( !shadowBits ) { break; } group = rsc.shadowGroups + i; if( !( shadowBits & group->bit ) ) { continue; } shadowBits &= ~group->bit; // make sure we don't render the same shadowmap twice in the same scene frame if( rsc.renderedShadowBits & group->bit ) { continue; } // calculate LOD for shadowmap dist = DistanceFast( group->origin, lodOrigin ); lod = (int)( dist * lodScale ) / group->projDist - SHADOWMAP_LODBIAS; if( lod < 0 ) { lod = 0; } // allocate/resize the texture if needed shadowmap = R_GetShadowmapTexture( i, rsc.refdef.width, rsc.refdef.height, 0 ); assert( shadowmap && shadowmap->upload_width && shadowmap->upload_height ); group->shadowmap = shadowmap; textureWidth = shadowmap->upload_width; textureHeight = shadowmap->upload_height; if( !shadowmap->fbo ) { continue; } farClip = R_SetupShadowmapView( group, &refdef, lod ); if( farClip <= 0.0f ) { continue; } // ignore shadowmaps of very low detail level if( refdef.width < SHADOWMAP_MIN_VIEWPORT_SIZE || refdef.height < SHADOWMAP_MIN_VIEWPORT_SIZE ) { continue; } rn.renderTarget = shadowmap->fbo; rn.farClip = farClip; rn.renderFlags = RF_SHADOWMAPVIEW | RF_FLIPFRONTFACE; if( !( shadowmap->flags & IT_DEPTH ) ) { rn.renderFlags |= RF_SHADOWMAPVIEW_RGB; } rn.clipFlags |= 16; // clip by far plane too rn.meshlist = &r_shadowlist; rn.portalmasklist = NULL; rn.shadowGroup = group; rn.lod_dist_scale_for_fov = lodScale; VectorCopy( viewerOrigin, rn.pvsOrigin ); VectorCopy( lodOrigin, rn.lodOrigin ); // 3 pixels border on each side to prevent nasty stretching/bleeding of shadows, // also accounting for smoothing done in the fragment shader Vector4Set( rn.viewport, refdef.x + 3,refdef.y + textureHeight - refdef.height + 3, refdef.width - 6, refdef.height - 6 ); Vector4Set( rn.scissor, refdef.x, refdef.y, textureWidth, textureHeight ); R_RenderView( &refdef ); Matrix4_Copy( rn.cameraProjectionMatrix, group->cameraProjectionMatrix ); rsc.renderedShadowBits |= group->bit; } R_PopRefInst(); }
void W_Fire_Electrobolt_FullInstant( edict_t *self, vec3_t start, vec3_t angles, float maxdamage, float mindamage, int maxknockback, int minknockback, int stun, int range, int minDamageRange, int mod, int timeDelta ) { vec3_t from, end, dir; trace_t tr; edict_t *ignore, *event, *hit, *damaged; int mask; qboolean missed = qtrue; int dmgflags = 0; #define FULL_DAMAGE_RANGE g_projectile_prestep->value if( GS_Instagib() ) maxdamage = mindamage = 9999; AngleVectors( angles, dir, NULL, NULL ); VectorMA( start, range, dir, end ); VectorCopy( start, from ); ignore = self; hit = damaged = NULL; mask = MASK_SHOT; if( GS_RaceGametype() ) mask = MASK_SOLID; clamp_high( mindamage, maxdamage ); clamp_high( minknockback, maxknockback ); clamp_high( minDamageRange, range ); if( minDamageRange <= FULL_DAMAGE_RANGE ) minDamageRange = FULL_DAMAGE_RANGE + 1; if( range <= FULL_DAMAGE_RANGE + 1 ) range = FULL_DAMAGE_RANGE + 1; tr.ent = -1; while( ignore ) { G_Trace4D( &tr, from, NULL, NULL, end, ignore, mask, timeDelta ); VectorCopy( tr.endpos, from ); ignore = NULL; if( tr.ent == -1 ) break; // some entity was touched hit = &game.edicts[tr.ent]; if( hit == world ) // stop dead if hit the world break; if( hit->movetype == MOVETYPE_NONE || hit->movetype == MOVETYPE_PUSH ) break; // allow trail to go through BBOX entities (players, gibs, etc) if( !ISBRUSHMODEL( hit->s.modelindex ) ) ignore = hit; if( ( hit != self ) && ( hit->takedamage ) ) { float frac, damage, knockback, dist; dist = DistanceFast( tr.endpos, start ); if( dist <= FULL_DAMAGE_RANGE ) frac = 0.0f; else { frac = ( dist - FULL_DAMAGE_RANGE ) / (float)( minDamageRange - FULL_DAMAGE_RANGE ); clamp( frac, 0.0f, 1.0f ); } damage = maxdamage - ( ( maxdamage - mindamage ) * frac ); knockback = maxknockback - ( ( maxknockback - minknockback ) * frac ); //G_Printf( "mindamagerange %i frac %.1f damage %i\n", minDamageRange, 1.0f - frac, (int)damage ); G_Damage( hit, self, self, dir, dir, tr.endpos, damage, knockback, stun, dmgflags, mod ); // spawn a impact event on each damaged ent event = G_SpawnEvent( EV_BOLT_EXPLOSION, DirToByte( tr.plane.normal ), tr.endpos ); event->s.firemode = FIRE_MODE_STRONG; if( hit->r.client ) missed = qfalse; damaged = hit; } } if( missed && self->r.client ) G_AwardPlayerMissedElectrobolt( self, mod ); // send the weapon fire effect event = G_SpawnEvent( EV_ELECTROTRAIL, ENTNUM( self ), start ); event->r.svflags = SVF_TRANSMITORIGIN2; VectorScale( dir, 1024, event->s.origin2 ); event->s.firemode = FIRE_MODE_STRONG; #undef FULL_DAMAGE_RANGE }
/* * AI_PredictJumpadDestity * Make a guess on where a jumpad will send the player. */ static qboolean AI_PredictJumpadDestity( edict_t *ent, vec3_t out ) { int i; edict_t *target; trace_t trace; vec3_t pad_origin, v1, v2; float htime, vtime, tmpfloat, player_factor; vec3_t floor_target_origin, target_origin; vec3_t floor_dist_vec, floor_movedir; VectorClear( out ); if( !ent->target ) return qfalse; // get target entity target = G_Find( NULL, FOFS( targetname ), ent->target ); if( !target ) return qfalse; // find pad origin VectorCopy( ent->r.maxs, v1 ); VectorCopy( ent->r.mins, v2 ); pad_origin[0] = ( v1[0] - v2[0] ) / 2 + v2[0]; pad_origin[1] = ( v1[1] - v2[1] ) / 2 + v2[1]; pad_origin[2] = ent->r.maxs[2]; //make a projection 'on floor' of target origin VectorCopy( target->s.origin, target_origin ); VectorCopy( target->s.origin, floor_target_origin ); floor_target_origin[2] = pad_origin[2]; //put at pad's height //make a guess on how player movement will affect the trajectory tmpfloat = DistanceFast( pad_origin, floor_target_origin ); htime = sqrt( ( tmpfloat ) ); vtime = sqrt( ( target->s.origin[2] - pad_origin[2] ) ); if( !vtime ) return qfalse; htime *= 4; vtime *= 4; if( htime > vtime ) htime = vtime; player_factor = vtime - htime; // find distance vector, on floor, from pad_origin to target origin. for( i = 0; i < 3; i++ ) floor_dist_vec[i] = floor_target_origin[i] - pad_origin[i]; // movement direction on floor VectorCopy( floor_dist_vec, floor_movedir ); VectorNormalize( floor_movedir ); // move both target origin and target origin on floor by player movement factor. VectorMA( target_origin, player_factor, floor_movedir, target_origin ); VectorMA( floor_target_origin, player_factor, floor_movedir, floor_target_origin ); // move target origin on floor by floor distance, and add another player factor step to it VectorMA( floor_target_origin, 1, floor_dist_vec, floor_target_origin ); VectorMA( floor_target_origin, player_factor, floor_movedir, floor_target_origin ); #ifdef SHOW_JUMPAD_GUESS // this is our top of the curve point, and the original target AI_JumpadGuess_ShowPoint( target_origin, PATH_AMMO_BOX_MODEL ); AI_JumpadGuess_ShowPoint( target->s.origin, PATH_AMMO_BOX_MODEL ); #endif //trace from target origin to endPoint. G_Trace( &trace, target_origin, tv( -15, -15, -8 ), tv( 15, 15, 8 ), floor_target_origin, NULL, MASK_NODESOLID ); if( ( trace.fraction == 1.0 && trace.startsolid ) || ( trace.allsolid && trace.startsolid ) ) { G_Printf( "JUMPAD LAND: ERROR: trace was in solid.\n" ); //started inside solid (target should never be inside solid, this is a mapper error) return qfalse; } else if( trace.fraction == 1.0 ) { //didn't find solid. Extend Down (I have to improve this part) vec3_t target_origin2, extended_endpoint, extend_dist_vec; VectorCopy( floor_target_origin, target_origin2 ); for( i = 0; i < 3; i++ ) extend_dist_vec[i] = floor_target_origin[i] - target_origin[i]; VectorMA( target_origin2, 1, extend_dist_vec, extended_endpoint ); G_Trace( &trace, target_origin2, tv( -15, -15, -8 ), tv( 15, 15, 8 ), extended_endpoint, NULL, MASK_NODESOLID ); if( trace.fraction == 1.0 ) return qfalse; //still didn't find solid } #ifdef SHOW_JUMPAD_GUESS // destiny found AI_JumpadGuess_ShowPoint( trace.endpos, PATH_AMMO_BOX_MODEL ); #endif VectorCopy( trace.endpos, out ); return qtrue; }