void BubbleShield_Update() { // Shields Go When You Die //------------------------- if (NPC->health<=0) { if (BubbleShield_IsOn()) { BubbleShield_TurnOff(); } return; } // Recharge Shields //------------------ NPC->client->ps.stats[STAT_ARMOR] += 1; if (NPC->client->ps.stats[STAT_ARMOR]>250) { NPC->client->ps.stats[STAT_ARMOR] = 250; } // If We Have Enough Armor And Are Not Shooting Right Now, Kick The Shield On //---------------------------------------------------------------------------- if (NPC->client->ps.stats[STAT_ARMOR]>100 && TIMER_Done(NPC, "ShieldsDown")) { // Check On Timers To Raise And Lower Shields //-------------------------------------------- if ((level.time - NPCInfo->enemyLastSeenTime)<1000 && TIMER_Done(NPC, "ShieldsUp")) { TIMER_Set(NPC, "ShieldsDown", 2000); // Drop Shields TIMER_Set(NPC, "ShieldsUp", Q_irand(4000, 5000)); // Then Bring Them Back Up For At Least 3 sec } BubbleShield_TurnOn(); if (BubbleShield_IsOn()) { // Update Our Shader Value //------------------------- NPC->client->renderInfo.customRGBA[0] = NPC->client->renderInfo.customRGBA[1] = NPC->client->renderInfo.customRGBA[2] = NPC->client->renderInfo.customRGBA[3] = (NPC->client->ps.stats[STAT_ARMOR] - 100); // If Touched By An Enemy, ALWAYS Shove Them //------------------------------------------- if (NPC->enemy && NPCInfo->touchedByPlayer==NPC->enemy) { vec3_t dir; VectorSubtract(NPC->enemy->currentOrigin, NPC->currentOrigin, dir); VectorNormalize(dir); BubbleShield_PushEnt(NPC->enemy, dir); } // Push Anybody Else Near //------------------------ BubbleShield_PushRadiusEnts(); } } // Shields Gone //-------------- else { BubbleShield_TurnOff(); } }
/* qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) Includes aim when determining angles - so they don't always hit... */ qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) { #if 0 float diff; float error; float targetPitch = 0; float targetYaw = 0; qboolean exact = qtrue; if ( level.time < NPCInfo->aimTime ) { if( doPitch ) targetPitch = NPCInfo->lockedDesiredPitch; if( doYaw ) targetYaw = NPCInfo->lockedDesiredYaw; } else { if( doPitch ) { targetPitch = NPCInfo->desiredPitch; NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; } if( doYaw ) { targetYaw = NPCInfo->desiredYaw; NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; } } if( doYaw ) { // add yaw error based on NPCInfo->aim value error = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); if(Q_irand(0, 1)) error *= -1; diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); if ( diff ) exact = qfalse; ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW]; } if( doPitch ) { // add pitch error based on NPCInfo->aim value error = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); if ( diff ) exact = qfalse; ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH]; } ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; return exact; #else float error, diff; float decay; float targetPitch = 0; float targetYaw = 0; qboolean exact = qtrue; // if angle changes are locked; just keep the current angles if ( level.time < NPCInfo->aimTime ) { if(doPitch) targetPitch = NPCInfo->lockedDesiredPitch; if(doYaw) targetYaw = NPCInfo->lockedDesiredYaw; } else { if(doPitch) targetPitch = NPCInfo->desiredPitch; if(doYaw) targetYaw = NPCInfo->desiredYaw; if(doPitch) NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; if(doYaw) NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; } if ( NPCInfo->aimErrorDebounceTime < level.time ) { if ( Q_irand(0, 1 ) ) { NPCInfo->lastAimErrorYaw = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); } if ( Q_irand(0, 1 ) ) { NPCInfo->lastAimErrorPitch = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); } NPCInfo->aimErrorDebounceTime = level.time + Q_irand(250, 2000); } if(doYaw) { // decay yaw diff diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); if ( diff) { exact = qfalse; decay = 60.0 + 80.0; decay *= 50.0f / 1000.0f;//msec if ( diff < 0.0 ) { diff += decay; if ( diff > 0.0 ) { diff = 0.0; } } else { diff -= decay; if ( diff < 0.0 ) { diff = 0.0; } } } // add yaw error based on NPCInfo->aim value error = NPCInfo->lastAimErrorYaw; ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW]; } if(doPitch) { // decay pitch diff diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); if ( diff) { exact = qfalse; decay = 60.0 + 80.0; decay *= 50.0f / 1000.0f;//msec if ( diff < 0.0 ) { diff += decay; if ( diff > 0.0 ) { diff = 0.0; } } else { diff -= decay; if ( diff < 0.0 ) { diff = 0.0; } } } error = NPCInfo->lastAimErrorPitch; ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH]; } ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; return exact; #endif }
/* ------------------------- NAVNEW_MoveToGoal ------------------------- */ int NAVNEW_MoveToGoal( gentity_t *self, navInfo_t &info ) { int bestNode = WAYPOINT_NONE; qboolean foundClearPath = qfalse; vec3_t origin; navInfo_t tempInfo; qboolean setBlockedInfo = qtrue; qboolean inBestWP, inGoalWP, goalWPFailed = qfalse; int numTries = 0; memcpy( &tempInfo, &info, sizeof( tempInfo ) ); //Must have a goal entity to move there if( self->NPC->goalEntity == NULL ) return WAYPOINT_NONE; if ( self->waypoint == WAYPOINT_NONE && self->noWaypointTime > level.time ) { //didn't have a valid one in about the past second, don't look again just yet return WAYPOINT_NONE; } if ( self->NPC->goalEntity->waypoint == WAYPOINT_NONE && self->NPC->goalEntity->noWaypointTime > level.time ) { //didn't have a valid one in about the past second, don't look again just yet return WAYPOINT_NONE; } if ( self->noWaypointTime > level.time && self->NPC->goalEntity->noWaypointTime > level.time ) { //just use current waypoints bestNode = navigator.GetBestNodeAltRoute( self->waypoint, self->NPC->goalEntity->waypoint, bestNode ); } //FIXME!!!!: this is making them wiggle back and forth between waypoints else if ( (bestNode = navigator.GetBestPathBetweenEnts( self, self->NPC->goalEntity, NF_CLEAR_PATH )) == NODE_NONE )//!NAVNEW_GetWaypoints( self, qtrue ) ) { //one of us didn't have a valid waypoint! if ( self->waypoint == NODE_NONE ) { //don't even try to find one again for a bit self->noWaypointTime = level.time + Q_irand( 500, 1500 ); } if ( self->NPC->goalEntity->waypoint == NODE_NONE ) { //don't even try to find one again for a bit self->NPC->goalEntity->noWaypointTime = level.time + Q_irand( 500, 1500 ); } return WAYPOINT_NONE; } else { if ( self->NPC->goalEntity->noWaypointTime < level.time ) { self->NPC->goalEntity->noWaypointTime = level.time + Q_irand( 500, 1500 ); } } while( !foundClearPath ) { inBestWP = inGoalWP = qfalse; /* bestNode = navigator.GetBestNodeAltRoute( self->waypoint, self->NPC->goalEntity->waypoint, bestNode ); */ if ( bestNode == WAYPOINT_NONE ) { goto failed; } //see if we can get directly to the next node off bestNode en route to goal's node... //NOTE: shouldn't be necc. now /* int oldBestNode = bestNode; bestNode = NAV_TestBestNode( self, self->waypoint, bestNode, qtrue );//, self->NPC->goalEntity->waypoint );// //NOTE: Guaranteed to return something if ( bestNode != oldBestNode ) {//we were blocked somehow if ( setBlockedInfo ) { self->NPC->aiFlags |= NPCAI_BLOCKED; navigator.GetNodePosition( oldBestNode, NPCInfo->blockedDest ); } } */ navigator.GetNodePosition( bestNode, origin ); /* if ( !goalWPFailed ) {//we haven't already tried to go straight to goal or goal's wp if ( bestNode == self->NPC->goalEntity->waypoint ) {//our bestNode is the goal's wp if ( NAV_HitNavGoal( self->currentOrigin, self->mins, self->maxs, origin, navigator.GetNodeRadius( bestNode ), FlyingCreature( self ) ) ) {//we're in the goal's wp inGoalWP = qtrue; //we're in the goalEntity's waypoint already //so head for the goalEntity since we know it's clear of architecture //FIXME: this is pretty stupid because the NPCs try to go straight // towards their goal before then even try macro_nav... VectorCopy( self->NPC->goalEntity->currentOrigin, origin ); } } } */ if ( !inGoalWP ) { //not heading straight for goal if ( bestNode == self->waypoint ) { //we know it's clear or architecture //navigator.GetNodePosition( self->waypoint, origin ); /* if ( NAV_HitNavGoal( self->currentOrigin, self->mins, self->maxs, origin, navigator.GetNodeRadius( bestNode ), FlyingCreature( self ) ) ) {//we're in the wp we're heading for already inBestWP = qtrue; } */ } else { //heading to an edge off our confirmed clear waypoint... make sure it's clear //it it's not, bestNode will fall back to our waypoint int oldBestNode = bestNode; bestNode = NAV_TestBestNode( self, self->waypoint, bestNode, qtrue ); if ( bestNode == self->waypoint ) { //we fell back to our waypoint, reset the origin self->NPC->aiFlags |= NPCAI_BLOCKED; navigator.GetNodePosition( oldBestNode, NPCInfo->blockedDest ); navigator.GetNodePosition( bestNode, origin ); } } } //Com_Printf( "goalwp = %d, mywp = %d, node = %d, origin = %s\n", self->NPC->goalEntity->waypoint, self->waypoint, bestNode, vtos(origin) ); memcpy( &tempInfo, &info, sizeof( tempInfo ) ); VectorSubtract( origin, self->currentOrigin, tempInfo.direction ); VectorNormalize( tempInfo.direction ); //NOTE: One very important thing NAVNEW_AvoidCollision does is // it actually CHANGES the value of "direction" - it changes it to // whatever dir you need to go in to avoid the obstacle... foundClearPath = NAVNEW_AvoidCollision( self, self->NPC->goalEntity, tempInfo, setBlockedInfo, 5 ); if ( !foundClearPath ) { //blocked by an ent if ( inGoalWP ) { //we were heading straight for the goal, head for the goal's wp instead navigator.GetNodePosition( bestNode, origin ); foundClearPath = NAVNEW_AvoidCollision( self, self->NPC->goalEntity, tempInfo, setBlockedInfo, 5 ); } } if ( foundClearPath ) { //clear! //If we got set to blocked, clear it NPC_ClearBlocked( self ); //Take the dir memcpy( &info, &tempInfo, sizeof( info ) ); if ( self->s.weapon == WP_SABER ) { //jedi if ( info.direction[2] * info.distance > 64 ) { self->NPC->aiFlags |= NPCAI_BLOCKED; VectorCopy( origin, NPCInfo->blockedDest ); goto failed; } } } else { //blocked by ent! if ( setBlockedInfo ) { self->NPC->aiFlags |= NPCAI_BLOCKED; navigator.GetNodePosition( bestNode, NPCInfo->blockedDest ); } //Only set blocked info first time setBlockedInfo = qfalse; if ( inGoalWP ) { //we headed for our goal and failed and our goal's WP and failed if ( self->waypoint == self->NPC->goalEntity->waypoint ) { //our waypoint is our goal's waypoint, nothing we can do //remember that this node is blocked navigator.AddFailedNode( self, self->waypoint ); goto failed; } else { //try going for our waypoint this time goalWPFailed = qtrue; inGoalWP = qfalse; } } else if ( bestNode != self->waypoint ) { //we headed toward our next waypoint (instead of our waypoint) and failed if ( d_altRoutes->integer ) { //mark this edge failed and try our waypoint //NOTE: don't assume there is something blocking the direct path // between my waypoint and the bestNode... I could be off // that path because of collision avoidance... if ( d_patched->integer &&//use patch-style navigation ( !navigator.NodesAreNeighbors( self->waypoint, bestNode ) || NAVNEW_TestNodeConnectionBlocked( self->waypoint, bestNode, self, self->NPC->goalEntity->s.number, qfalse, qtrue ) ) ) { //the direct path between these 2 nodes is blocked by an ent navigator.AddFailedEdge( self->s.number, self->waypoint, bestNode ); } bestNode = self->waypoint; } else { //we should stop goto failed; } } else { //we headed for *our* waypoint and couldn't get to it if ( d_altRoutes->integer ) { //remember that this node is blocked navigator.AddFailedNode( self, self->waypoint ); //Now we should get our waypoints again //FIXME: cache the trace-data for subsequent calls as only the route info would have changed //if ( (bestNode = navigator.GetBestPathBetweenEnts( self, self->NPC->goalEntity, NF_CLEAR_PATH )) == NODE_NONE )//!NAVNEW_GetWaypoints( self, qfalse ) ) { //one of our waypoints is WAYPOINT_NONE now goto failed; } } else { //we should stop goto failed; } } if ( ++numTries >= 10 ) { goto failed; } } } //finish: //Draw any debug info, if requested if ( NAVDEBUG_showEnemyPath ) { vec3_t dest, start; //Get the positions navigator.GetNodePosition( self->NPC->goalEntity->waypoint, dest ); navigator.GetNodePosition( bestNode, start ); //Draw the route CG_DrawNode( start, NODE_START ); if ( bestNode != self->waypoint ) { vec3_t wpPos; navigator.GetNodePosition( self->waypoint, wpPos ); CG_DrawNode( wpPos, NODE_NAVGOAL ); } CG_DrawNode( dest, NODE_GOAL ); CG_DrawEdge( dest, self->NPC->goalEntity->currentOrigin, EDGE_PATH ); CG_DrawNode( self->NPC->goalEntity->currentOrigin, NODE_GOAL ); navigator.ShowPath( bestNode, self->NPC->goalEntity->waypoint ); } self->NPC->shoveCount = 0; //let me keep this waypoint for a while if ( self->noWaypointTime < level.time ) { self->noWaypointTime = level.time + Q_irand( 500, 1500 ); } return bestNode; failed: //FIXME: What we should really do here is have a list of the goal's and our // closest clearpath waypoints, ranked. If the first set fails, try the rest // until there are no alternatives. navigator.GetNodePosition( self->waypoint, origin ); //do this to avoid ping-ponging? return WAYPOINT_NONE; /* //this was causing ping-ponging if ( DistanceSquared( origin, self->currentOrigin ) < 16 )//woo, magic number {//We're right up on our waypoint, so that won't help, return none //Or maybe find the nextbest here? return WAYPOINT_NONE; } else {//Try going to our waypoint bestNode = self->waypoint; VectorSubtract( origin, self->currentOrigin, info.direction ); VectorNormalize( info.direction ); } goto finish; */ }
/* ------------------------- Interrogator_PartsMove ------------------------- */ void Interrogator_PartsMove(void) { // Syringe if ( TIMER_Done(NPC,"syringeDelay") ) { NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]); if ((NPC->pos1[1] < 60) || (NPC->pos1[1] > 300)) { NPC->pos1[1]+=Q_irand( -20, 20 ); // Pitch } else if (NPC->pos1[1] > 180) { NPC->pos1[1]=Q_irand( 300, 360 ); // Pitch } else { NPC->pos1[1]=Q_irand( 0, 60 ); // Pitch } // gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone1, NPC->pos1, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); NPC_SetBoneAngles(NPC, "left_arm", NPC->pos1); TIMER_Set( NPC, "syringeDelay", Q_irand( 100, 1000 ) ); } // Scalpel if ( TIMER_Done(NPC,"scalpelDelay") ) { // Change pitch if ( NPCInfo->localState == LSTATE_BLADEDOWN ) // Blade is moving down { NPC->pos2[0]-= 30; if (NPC->pos2[0] < 180) { NPC->pos2[0] = 180; NPCInfo->localState = LSTATE_BLADEUP; // Make it move up } } else // Blade is coming back up { NPC->pos2[0]+= 30; if (NPC->pos2[0] >= 360) { NPC->pos2[0] = 360; NPCInfo->localState = LSTATE_BLADEDOWN; // Make it move down TIMER_Set( NPC, "scalpelDelay", Q_irand( 100, 1000 ) ); } } NPC->pos2[0] = AngleNormalize360( NPC->pos2[0]); // gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone2, NPC->pos2, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); NPC_SetBoneAngles(NPC, "right_arm", NPC->pos2); } // Claw NPC->pos3[1] += Q_irand( 10, 30 ); NPC->pos3[1] = AngleNormalize360( NPC->pos3[1]); //gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone3, NPC->pos3, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); NPC_SetBoneAngles(NPC, "claw", NPC->pos3); }
void NPC_BSGrenadier_Patrol( void ) {//FIXME: pick up on bodies of dead buddies? if ( NPCInfo->confusionTime < level.time ) { //Look for any enemies if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) { if ( NPC_CheckPlayerTeamStealth() ) { //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be automatic now //NPC_AngerSound(); NPC_UpdateAngles( qtrue, qtrue ); return; } } if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) { //Is there danger nearby int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS ); if ( NPC_CheckForDanger( alertEvent ) ) { NPC_UpdateAngles( qtrue, qtrue ); return; } else {//check for other alert events //There is an event to look at if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) { //NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID; if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED ) { if ( level.alertEvents[alertEvent].owner && level.alertEvents[alertEvent].owner->client && level.alertEvents[alertEvent].owner->health >= 0 && level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) {//an enemy G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); //NPCInfo->enemyLastSeenTime = level.time; TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); } } else {//FIXME: get more suspicious over time? //Save the position for movement (if necessary) VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal ); NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 ); if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS ) {//suspicious looks longer NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 ); } } } } if ( NPCInfo->investigateDebounceTime > level.time ) {//FIXME: walk over to it, maybe? Not if not chase enemies //NOTE: stops walking or doing anything else below vec3_t dir, angles; float o_yaw, o_pitch; VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir ); vectoangles( dir, angles ); o_yaw = NPCInfo->desiredYaw; o_pitch = NPCInfo->desiredPitch; NPCInfo->desiredYaw = angles[YAW]; NPCInfo->desiredPitch = angles[PITCH]; NPC_UpdateAngles( qtrue, qtrue ); NPCInfo->desiredYaw = o_yaw; NPCInfo->desiredPitch = o_pitch; return; } } } //If we have somewhere to go, then do that if ( UpdateGoal() ) { ucmd.buttons |= BUTTON_WALKING; NPC_MoveToGoal( qtrue ); } NPC_UpdateAngles( qtrue, qtrue ); }
/* ------------------------- Mark1_AttackDecision ------------------------- */ void Mark1_AttackDecision( void ) { int blasterTest,rocketTest; float distance; distance_e distRate; qboolean visible; qboolean advance; //randomly talk if ( TIMER_Done(NPC,"patrolNoise") ) { if (TIMER_Done(NPC,"angerNoise")) { // G_Sound( NPC, G_SoundIndex(va("sound/chars/mark1/misc/talk%d.wav", Q_irand(1, 4)))); TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) ); } } // Enemy is dead or he has no enemy. if ((NPC->enemy->health<1) || ( NPC_CheckEnemyExt(qfalse) == qfalse )) { NPC->enemy = NULL; return; } // Rate our distance to the target and visibility distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE; visible = NPC_ClearLOS4( NPC->enemy ); advance = (qboolean)(distance > MIN_DISTANCE_SQR); // If we cannot see our target, move to see it if ((!visible) || (!NPC_FaceEnemy(qtrue))) { Mark1_Hunt(); return; } // See if the side weapons are there blasterTest = trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "l_arm" ); rocketTest = trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "r_arm" ); // It has both side weapons if (!blasterTest && !rocketTest) { ; // So do nothing. } else if (blasterTest!=-1 &&blasterTest) { distRate = DIST_LONG; } else if (rocketTest!=-1 &&rocketTest) { distRate = DIST_MELEE; } else // It should never get here, but just in case { NPC->health = 0; NPC->client->ps.stats[STAT_HEALTH] = 0; //GEntity_DieFunc(NPC, NPC, NPC, 100, MOD_UNKNOWN); if (NPC->die) { NPC->die(NPC, NPC, NPC, 100, MOD_UNKNOWN); } } // We can see enemy so shoot him if timers let you. NPC_FaceEnemy( qtrue ); if (distRate == DIST_MELEE) { Mark1_BlasterAttack(advance); } else if (distRate == DIST_LONG) { Mark1_RocketAttack(advance); } }
void NPC_BSSaberDroid_Attack( void ) {//attack behavior //Don't do anything if we're hurt if ( NPC->painDebounceTime > level.time ) { NPC_UpdateAngles( qtrue, qtrue ); return; } //NPC_CheckEnemy( qtrue, qfalse ); //If we don't have an enemy, just idle if ( NPC_CheckEnemyExt(qfalse) == qfalse )//!NPC->enemy )// { NPC->enemy = NULL; NPC_BSSaberDroid_Patrol();//FIXME: or patrol? return; } if ( !NPC->enemy ) {//WTF? somehow we lost our enemy? NPC_BSSaberDroid_Patrol();//FIXME: or patrol? return; } enemyLOS = enemyCS = qfalse; move = qtrue; faceEnemy = qfalse; shoot = qfalse; enemyDist = DistanceSquared( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ); //can we see our target? if ( NPC_ClearLOS4( NPC->enemy ) ) { NPCInfo->enemyLastSeenTime = level.time; enemyLOS = qtrue; if ( enemyDist <= 4096 && InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 90, 45 ) )//within 64 & infront { VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); enemyCS = qtrue; } } /* else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) { NPCInfo->enemyLastSeenTime = level.time; faceEnemy = qtrue; } */ if ( enemyLOS ) {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? faceEnemy = qtrue; } if ( !TIMER_Done( NPC, "taunting" ) ) { move = qfalse; } else if ( enemyCS ) { shoot = qtrue; if ( enemyDist < (NPC->r.maxs[0]+NPC->enemy->r.maxs[0]+32)*(NPC->r.maxs[0]+NPC->enemy->r.maxs[0]+32) ) {//close enough move = qfalse; } }//this should make him chase enemy when out of range...? if ( NPC->client->ps.legsTimer && NPC->client->ps.legsAnim != BOTH_A3__L__R )//this one is a running attack {//in the middle of a held, stationary anim, can't move move = qfalse; } if ( move ) {//move toward goal move = SaberDroid_Move(); if ( move ) {//if we had to chase him, be sure to attack as soon as possible TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime ); } } if ( !faceEnemy ) {//we want to face in the dir we're running if ( move ) {//don't run away and shoot NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; NPCInfo->desiredPitch = 0; shoot = qfalse; } NPC_UpdateAngles( qtrue, qtrue ); } else// if ( faceEnemy ) {//face the enemy NPC_FaceEnemy(qtrue); } if ( NPCInfo->scriptFlags&SCF_DONT_FIRE ) { shoot = qfalse; } //FIXME: need predicted blocking? //FIXME: don't shoot right away! if ( shoot ) {//try to shoot if it's time if ( TIMER_Done( NPC, "attackDelay" ) ) { if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here {//attack! NPC_SaberDroid_PickAttack(); //set attac delay for next attack. if ( NPCInfo->rank > RANK_CREWMAN ) { TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime+Q_irand(0, 1000) ); } else { TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime+Q_irand( 0, 1000 )+(Q_irand( 0, (3-g_spskill.integer)*2 )*500) ); } } } } }
/* ------------------------- Remote_MaintainHeight ------------------------- */ void Remote_MaintainHeight( void ) { float dif; // Update our angles regardless NPC_UpdateAngles( qtrue, qtrue ); if ( NPCS.NPC->client->ps.velocity[2] ) { NPCS.NPC->client->ps.velocity[2] *= VELOCITY_DECAY; if ( fabs( NPCS.NPC->client->ps.velocity[2] ) < 2 ) { NPCS.NPC->client->ps.velocity[2] = 0; } } // If we have an enemy, we should try to hover at or a little below enemy eye level if ( NPCS.NPC->enemy ) { if (TIMER_Done( NPCS.NPC, "heightChange")) { TIMER_Set( NPCS.NPC,"heightChange",Q_irand( 1000, 3000 )); // Find the height difference dif = (NPCS.NPC->enemy->r.currentOrigin[2] + Q_irand( 0, NPCS.NPC->enemy->r.maxs[2]+8 )) - NPCS.NPC->r.currentOrigin[2]; // cap to prevent dramatic height shifts if ( fabs( dif ) > 2 ) { if ( fabs( dif ) > 24 ) { dif = ( dif < 0 ? -24 : 24 ); } dif *= 10; NPCS.NPC->client->ps.velocity[2] = (NPCS.NPC->client->ps.velocity[2]+dif)/2; // NPC->fx_time = level.time; G_Sound( NPCS.NPC, CHAN_AUTO, G_SoundIndex("sound/chars/remote/misc/hiss.wav")); } } } else { gentity_t *goal = NULL; if ( NPCS.NPCInfo->goalEntity ) // Is there a goal? { goal = NPCS.NPCInfo->goalEntity; } else { goal = NPCS.NPCInfo->lastGoalEntity; } if ( goal ) { dif = goal->r.currentOrigin[2] - NPCS.NPC->r.currentOrigin[2]; if ( fabs( dif ) > 24 ) { dif = ( dif < 0 ? -24 : 24 ); NPCS.NPC->client->ps.velocity[2] = (NPCS.NPC->client->ps.velocity[2]+dif)/2; } } } // Apply friction if ( NPCS.NPC->client->ps.velocity[0] ) { NPCS.NPC->client->ps.velocity[0] *= VELOCITY_DECAY; if ( fabs( NPCS.NPC->client->ps.velocity[0] ) < 1 ) { NPCS.NPC->client->ps.velocity[0] = 0; } } if ( NPCS.NPC->client->ps.velocity[1] ) { NPCS.NPC->client->ps.velocity[1] *= VELOCITY_DECAY; if ( fabs( NPCS.NPC->client->ps.velocity[1] ) < 1 ) { NPCS.NPC->client->ps.velocity[1] = 0; } } }
inline void Pick(int& V) { V = Q_irand(mMin, mMax); }
void RT_Flying_Hunt( qboolean visible, qboolean advance ) { float distance, speed; vec3_t forward; if ( NPC->forcePushTime >= level.time ) //|| (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) ) {//if being pushed, we don't have control over our movement NPC->delay = 0; return; } NPC_FaceEnemy( qtrue ); // If we're not supposed to stand still, pursue the player if ( NPCInfo->standTime < level.time ) { // Only strafe when we can see the player if ( visible ) { NPC->delay = 0; RT_Flying_Strafe(); return; } } // If we don't want to advance, stop here if ( advance ) { // Only try and navigate if the player is visible if ( visible == qfalse ) { // Move towards our goal NPCInfo->goalEntity = NPC->enemy; NPCInfo->goalRadius = 24; NPC->delay = 0; NPC_MoveToGoal(qtrue); return; } } //else move straight at/away from him VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward ); forward[2] *= 0.1f; distance = VectorNormalize( forward ); speed = RT_FLYING_FORWARD_BASE_SPEED + RT_FLYING_FORWARD_MULTIPLIER * g_spskill->integer; if ( advance && distance < Q_flrand( 256, 3096 ) ) { NPC->delay = 0; VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); } else if ( distance < Q_flrand( 0, 128 ) ) { if ( NPC->health <= 50 ) {//always back off NPC->delay = 0; } else if ( !TIMER_Done( NPC, "backoffTime" ) ) {//still backing off from end of last delay NPC->delay = 0; } else if ( !NPC->delay ) {//start a new delay NPC->delay = Q_irand( 0, 10+(20*(2-g_spskill->integer)) ); } else {//continue the current delay NPC->delay--; } if ( !NPC->delay ) {//delay done, now back off for a few seconds! TIMER_Set( NPC, "backoffTime", Q_irand( 2000, 5000 ) ); VectorMA( NPC->client->ps.velocity, speed*-2, forward, NPC->client->ps.velocity ); } } else { NPC->delay = 0; } }
void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) { int respawn = 0; if (!other->client) return; if (other->health < 1) return; // dead people can't pickup if ( other->client->ps.pm_time > 0 ) {//cant pick up when out of control return; } // Only monsters can pick it up if ((ent->spawnflags & ITMSF_MONSTER) && (other->client->playerTeam == TEAM_PLAYER)) { return; } // Only starfleet can pick it up if ((ent->spawnflags & ITMSF_TEAM) && (other->client->playerTeam != TEAM_PLAYER)) { return; } if ( other->client->NPC_class == CLASS_ATST || other->client->NPC_class == CLASS_GONK || other->client->NPC_class == CLASS_MARK1 || other->client->NPC_class == CLASS_MARK2 || other->client->NPC_class == CLASS_MOUSE || other->client->NPC_class == CLASS_PROBE || other->client->NPC_class == CLASS_PROTOCOL || other->client->NPC_class == CLASS_R2D2 || other->client->NPC_class == CLASS_R5D2 || other->client->NPC_class == CLASS_SEEKER || other->client->NPC_class == CLASS_REMOTE || other->client->NPC_class == CLASS_SENTRY ) {//FIXME: some flag would be better //droids can't pick up items/weapons! return; } //FIXME: need to make them run toward a dropped weapon when fleeing without one? //FIXME: need to make them come out of flee mode when pick up their old weapon? if ( CheckItemCanBePickedUpByNPC( ent, other ) ) { if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity->enemy == ent ) {//they were running to pick me up, they did, so clear goal other->NPC->goalEntity = NULL; other->NPC->squadState = SQUAD_STAND_AND_SHOOT; } } else if (!(ent->spawnflags & ITMSF_TEAM) && !(ent->spawnflags & ITMSF_MONSTER)) {// Only player can pick it up if ( other->s.number != 0 ) // Not the player? { return; } } // the same pickup rules are used for client side and server side if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) { return; } if ( other->client ) { if ( other->client->ps.eFlags&EF_FORCE_GRIPPED ) {//can't pick up anything while being gripped return; } if ( PM_InKnockDown( &other->client->ps ) && !PM_InGetUp( &other->client->ps ) ) {//can't pick up while in a knockdown return; } } if (!ent->item) { //not an item! gi.Printf( "Touch_Item: %s is not an item!\n", ent->classname); return; } qboolean bHadWeapon = qfalse; // call the item-specific pickup function switch( ent->item->giType ) { case IT_WEAPON: if ( other->NPC && other->s.weapon == WP_NONE ) {//Make them duck and sit here for a few seconds int pickUpTime = Q_irand( 1000, 3000 ); TIMER_Set( other, "duck", pickUpTime ); TIMER_Set( other, "roamTime", pickUpTime ); TIMER_Set( other, "stick", pickUpTime ); TIMER_Set( other, "verifyCP", pickUpTime ); TIMER_Set( other, "attackDelay", 600 ); respawn = 0; } if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) ) { bHadWeapon = qtrue; } respawn = Pickup_Weapon(ent, other); break; case IT_AMMO: respawn = Pickup_Ammo(ent, other); break; case IT_ARMOR: respawn = Pickup_Armor(ent, other); break; case IT_HEALTH: respawn = Pickup_Health(ent, other); break; case IT_HOLDABLE: respawn = Pickup_Holdable(ent, other); break; case IT_BATTERY: respawn = Pickup_Battery( ent, other ); break; case IT_HOLOCRON: respawn = Pickup_Holocron( ent, other ); break; default: return; } if ( !respawn ) { return; } // play the normal pickup sound if ( !other->s.number && g_timescale->value < 1.0f ) {//SIGH... with timescale on, you lose events left and right extern void CG_ItemPickup( int itemNum, qboolean bHadItem ); // but we're SP so we'll cheat cgi_S_StartSound( NULL, other->s.number, CHAN_AUTO, cgi_S_RegisterSound( ent->item->pickup_sound ) ); // show icon and name on status bar CG_ItemPickup( ent->s.modelindex, bHadWeapon ); } else { if ( bHadWeapon ) { G_AddEvent( other, EV_ITEM_PICKUP, -ent->s.modelindex ); } else { G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex ); } } // fire item targets G_UseTargets (ent, other); // wait of -1 will not respawn // if ( ent->wait == -1 ) { //why not just remove me? G_FreeEntity( ent ); /* //NOTE: used to do this: (for respawning?) ent->svFlags |= SVF_NOCLIENT; ent->s.eFlags |= EF_NODRAW; ent->contents = 0; ent->unlinkAfterEvent = qtrue; */ return; } }
void RT_Flying_Strafe( void ) { int side; vec3_t end, right, dir; trace_t tr; if ( random() > 0.7f || !NPC->enemy || !NPC->enemy->client ) { // Do a regular style strafe AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); // Pick a random strafe direction, then check to see if doing a strafe would be // reasonably valid side = ( rand() & 1 ) ? -1 : 1; VectorMA( NPC->currentOrigin, RT_FLYING_STRAFE_DIS * side, right, end ); gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); // Close enough if ( tr.fraction > 0.9f ) { float vel = RT_FLYING_STRAFE_VEL+Q_flrand(-20,20); VectorMA( NPC->client->ps.velocity, vel*side, right, NPC->client->ps.velocity ); if ( !Q_irand( 0, 3 ) ) { // Add a slight upward push float upPush = RT_FLYING_UPWARD_PUSH; if ( NPC->client->ps.velocity[2] < 300 ) { if ( NPC->client->ps.velocity[2] < 300+upPush ) { NPC->client->ps.velocity[2] += upPush; } else { NPC->client->ps.velocity[2] = 300; } } } // NPCInfo->standTime = level.time + 1000 + random() * 500; // Original // NPCInfo->standTime = level.time + 2000 + random() * 500; // Revision 1 NPCInfo->standTime = level.time + 1500 + random() * 500; // Revision 2 } } else { // Do a strafe to try and keep on the side of their enemy AngleVectors( NPC->enemy->client->renderInfo.eyeAngles, dir, right, NULL ); // Pick a random side side = ( rand() & 1 ) ? -1 : 1; float stDis = RT_FLYING_STRAFE_DIS*2.0f; VectorMA( NPC->enemy->currentOrigin, stDis * side, right, end ); // then add a very small bit of random in front of/behind the player action VectorMA( end, crandom() * 25, dir, end ); gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); // Close enough if ( tr.fraction > 0.9f ) { float vel = (RT_FLYING_STRAFE_VEL*4)+Q_flrand(-20,20); VectorSubtract( tr.endpos, NPC->currentOrigin, dir ); dir[2] *= 0.25; // do less upward change float dis = VectorNormalize( dir ); if ( dis > vel ) { dis = vel; } // Try to move the desired enemy side VectorMA( NPC->client->ps.velocity, dis, dir, NPC->client->ps.velocity ); if ( !Q_irand( 0, 3 ) ) { float upPush = RT_FLYING_UPWARD_PUSH; // Add a slight upward push if ( NPC->client->ps.velocity[2] < 300 ) { if ( NPC->client->ps.velocity[2] < 300+upPush ) { NPC->client->ps.velocity[2] += upPush; } else { NPC->client->ps.velocity[2] = 300; } } else if ( NPC->client->ps.velocity[2] > 300 ) { NPC->client->ps.velocity[2] = 300; } } // NPCInfo->standTime = level.time + 2500 + random() * 500; // Original // NPCInfo->standTime = level.time + 5000 + random() * 500; // Revision 1 NPCInfo->standTime = level.time + 3500 + random() * 500; // Revision 2 } } }
void RT_FireDecide( void ) { qboolean enemyLOS = qfalse; qboolean enemyCS = qfalse; qboolean enemyInFOV = qfalse; //qboolean move = qtrue; qboolean faceEnemy = qfalse; qboolean shoot = qfalse; qboolean hitAlly = qfalse; vec3_t impactPos; float enemyDist; if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE && NPC->client->ps.forceJumpZStart && !PM_FlippingAnim( NPC->client->ps.legsAnim ) && !Q_irand( 0, 10 ) ) {//take off RT_FlyStart( NPC ); } if ( !NPC->enemy ) { return; } VectorClear( impactPos ); enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); vec3_t enemyDir, shootDir; VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, enemyDir ); VectorNormalize( enemyDir ); AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL ); float dot = DotProduct( enemyDir, shootDir ); if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 ) {//enemy is in front of me or they're very close and not behind me enemyInFOV = qtrue; } if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128 {//enemy within 128 if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) && (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//shooting an explosive, but enemy too close, switch to primary fire NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not... } } //can we see our target? if ( TIMER_Done( NPC, "nextAttackDelay" ) && TIMER_Done( NPC, "flameTime" ) ) { if ( NPC_ClearLOS( NPC->enemy ) ) { NPCInfo->enemyLastSeenTime = level.time; enemyLOS = qtrue; if ( NPC->client->ps.weapon == WP_NONE ) { enemyCS = qfalse;//not true, but should stop us from firing } else {//can we shoot our target? if ( (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER || (NPC->client->ps.weapon == WP_CONCUSSION && !(NPCInfo->scriptFlags&SCF_ALT_FIRE)) || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )//128*128 { enemyCS = qfalse;//not true, but should stop us from firing hitAlly = qtrue;//us! //FIXME: if too close, run away! } else if ( enemyInFOV ) {//if enemy is FOV, go ahead and check for shooting int hit = NPC_ShotEntity( NPC->enemy, impactPos ); gentity_t *hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage && ((hitEnt->svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) ) {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway enemyCS = qtrue; //NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); } else {//Hmm, have to get around this bastard //NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) {//would hit an ally, don't fire!!! hitAlly = qtrue; } else {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire } } } else { enemyCS = qfalse;//not true, but should stop us from firing } } } else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) { NPCInfo->enemyLastSeenTime = level.time; faceEnemy = qtrue; //NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy } if ( NPC->client->ps.weapon == WP_NONE ) { faceEnemy = qfalse; shoot = qfalse; } else { if ( enemyLOS ) {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? faceEnemy = qtrue; } if ( enemyCS ) { shoot = qtrue; } } if ( !enemyCS ) {//if have a clear shot, always try //See if we should continue to fire on their last position //!TIMER_Done( NPC, "stick" ) || if ( !hitAlly //we're not going to hit an ally && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS? && NPCInfo->enemyLastSeenTime > 0 )//we've seen the enemy { if ( level.time - NPCInfo->enemyLastSeenTime < 10000 )//we have seem the enemy in the last 10 seconds { if ( !Q_irand( 0, 10 ) ) { //Fire on the last known position vec3_t muzzle, dir, angles; qboolean tooClose = qfalse; qboolean tooFar = qfalse; CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); if ( VectorCompare( impactPos, vec3_origin ) ) {//never checked ShotEntity this frame, so must do a trace... trace_t tr; //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2}; vec3_t forward, end; AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL ); VectorMA( muzzle, 8192, forward, end ); gi.trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); VectorCopy( tr.endpos, impactPos ); } //see if impact would be too close to me float distThreshold = 16384/*128*128*/;//default switch ( NPC->s.weapon ) { case WP_ROCKET_LAUNCHER: case WP_FLECHETTE: case WP_THERMAL: case WP_TRIP_MINE: case WP_DET_PACK: distThreshold = 65536/*256*256*/; break; case WP_REPEATER: if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) { distThreshold = 65536/*256*256*/; } break; case WP_CONCUSSION: if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) { distThreshold = 65536/*256*256*/; } break; default: break; } float dist = DistanceSquared( impactPos, muzzle ); if ( dist < distThreshold ) {//impact would be too close to me tooClose = qtrue; } else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 || (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 )) {//we've haven't seen them in the last 5 seconds //see if it's too far from where he is distThreshold = 65536/*256*256*/;//default switch ( NPC->s.weapon ) { case WP_ROCKET_LAUNCHER: case WP_FLECHETTE: case WP_THERMAL: case WP_TRIP_MINE: case WP_DET_PACK: distThreshold = 262144/*512*512*/; break; case WP_REPEATER: if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) { distThreshold = 262144/*512*512*/; } break; case WP_CONCUSSION: if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) { distThreshold = 262144/*512*512*/; } break; default: break; } dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation ); if ( dist > distThreshold ) {//impact would be too far from enemy tooFar = qtrue; } } if ( !tooClose && !tooFar ) {//okay too shoot at last pos VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); VectorNormalize( dir ); vectoangles( dir, angles ); NPCInfo->desiredYaw = angles[YAW]; NPCInfo->desiredPitch = angles[PITCH]; shoot = qtrue; faceEnemy = qfalse; } } } } } //FIXME: don't shoot right away! if ( NPC->client->fireDelay ) { if ( NPC->s.weapon == WP_ROCKET_LAUNCHER || (NPC->s.weapon == WP_CONCUSSION&&!(NPCInfo->scriptFlags&SCF_ALT_FIRE)) ) { if ( !enemyLOS || !enemyCS ) {//cancel it NPC->client->fireDelay = 0; } else {//delay our next attempt TIMER_Set( NPC, "nextAttackDelay", Q_irand( 1000, 3000 ) );//FIXME: base on g_spskill } } } else if ( shoot ) {//try to shoot if it's time if ( TIMER_Done( NPC, "nextAttackDelay" ) ) { if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here { WeaponThink( qtrue ); } //NASTY int altChance = 6;//FIXME: base on g_spskill if ( NPC->s.weapon == WP_ROCKET_LAUNCHER ) { if ( (ucmd.buttons&BUTTON_ATTACK) && !Q_irand( 0, altChance ) ) {//every now and then, shoot a homing rocket ucmd.buttons &= ~BUTTON_ATTACK; ucmd.buttons |= BUTTON_ALT_ATTACK; NPC->client->fireDelay = Q_irand( 1000, 3000 );//FIXME: base on g_spskill } } else if ( NPC->s.weapon == WP_CONCUSSION ) { if ( (ucmd.buttons&BUTTON_ATTACK) && Q_irand( 0, altChance*5 ) ) {//fire the beam shot ucmd.buttons &= ~BUTTON_ATTACK; ucmd.buttons |= BUTTON_ALT_ATTACK; TIMER_Set( NPC, "nextAttackDelay", Q_irand( 1500, 2500 ) );//FIXME: base on g_spskill } else {//fire the rocket-like shot TIMER_Set( NPC, "nextAttackDelay", Q_irand( 3000, 5000 ) );//FIXME: base on g_spskill } } } } } }
void RT_Flying_MaintainHeight( void ) { float dif = 0; // Update our angles regardless NPC_UpdateAngles( qtrue, qtrue ); if ( NPC->forcePushTime > level.time ) {//if being pushed, we don't have control over our movement return; } if ( (NPC->client->ps.pm_flags&PMF_TIME_KNOCKBACK) ) {//don't slow down for a bit if ( NPC->client->ps.pm_time > 0 ) { VectorScale( NPC->client->ps.velocity, 0.9f, NPC->client->ps.velocity ); return; } } /* if ( (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) ) { RT_Flying_ApplyFriction( 3.0f ); return; } */ // If we have an enemy, we should try to hover at or a little below enemy eye level if ( NPC->enemy && (!Q3_TaskIDPending( NPC, TID_MOVE_NAV ) || !NPCInfo->goalEntity ) ) { if (TIMER_Done( NPC, "heightChange" )) { TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); float enemyZHeight = NPC->enemy->currentOrigin[2]; if ( NPC->enemy->client && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE && (NPC->enemy->client->ps.forcePowersActive&(1<<FP_LEVITATION)) ) {//so we don't go up when they force jump up at us enemyZHeight = NPC->enemy->client->ps.forceJumpZStart; } // Find the height difference dif = (enemyZHeight + Q_flrand( NPC->enemy->maxs[2]/2, NPC->enemy->maxs[2]+8 )) - NPC->currentOrigin[2]; float difFactor = 10.0f; // cap to prevent dramatic height shifts if ( fabs( dif ) > 2*difFactor ) { if ( fabs( dif ) > 20*difFactor ) { dif = ( dif < 0 ? -20*difFactor : 20*difFactor ); } NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; } NPC->client->ps.velocity[2] *= Q_flrand( 0.85f, 1.25f ); } else {//don't get too far away from height of enemy... float enemyZHeight = NPC->enemy->currentOrigin[2]; if ( NPC->enemy->client && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE && (NPC->enemy->client->ps.forcePowersActive&(1<<FP_LEVITATION)) ) {//so we don't go up when they force jump up at us enemyZHeight = NPC->enemy->client->ps.forceJumpZStart; } dif = NPC->currentOrigin[2] - (enemyZHeight+64); float maxHeight = 200; float hDist = DistanceHorizontal( NPC->enemy->currentOrigin, NPC->currentOrigin ); if ( hDist < 512 ) { maxHeight *= hDist/512; } if ( dif > maxHeight ) { if ( NPC->client->ps.velocity[2] > 0 )//FIXME: or: we can't see him anymore {//slow down if ( NPC->client->ps.velocity[2] ) { NPC->client->ps.velocity[2] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) { NPC->client->ps.velocity[2] = 0; } } } else {//start coming back down NPC->client->ps.velocity[2] -= 4; } } else if ( dif < -200 && NPC->client->ps.velocity[2] < 0 )//we're way below him { if ( NPC->client->ps.velocity[2] < 0 )//FIXME: or: we can't see him anymore {//slow down if ( NPC->client->ps.velocity[2] ) { NPC->client->ps.velocity[2] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[2] ) > -2 ) { NPC->client->ps.velocity[2] = 0; } } } else {//start going back up NPC->client->ps.velocity[2] += 4; } } } } else { gentity_t *goal = NULL; if ( NPCInfo->goalEntity ) // Is there a goal? { goal = NPCInfo->goalEntity; } else { goal = NPCInfo->lastGoalEntity; } if ( goal ) { dif = goal->currentOrigin[2] - NPC->currentOrigin[2]; } else if ( VectorCompare( NPC->pos1, vec3_origin ) ) {//have a starting position as a reference point dif = NPC->pos1[2] - NPC->currentOrigin[2]; } if ( fabs( dif ) > 24 ) { ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); } else { if ( NPC->client->ps.velocity[2] ) { NPC->client->ps.velocity[2] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) { NPC->client->ps.velocity[2] = 0; } } } } // Apply friction RT_Flying_ApplyFriction( 1.0f ); }
/* ------------------------- Mark1_dying ------------------------- */ void Mark1_dying( gentity_t *self ) { int num,newBolt; if (self->client->ps.torsoTimer>0) { if (TIMER_Done(self,"dyingExplosion")) { num = Q_irand( 1, 3); // Find place to generate explosion if (num == 1) { num = Q_irand( 8, 10); newBolt = trap_G2API_AddBolt( self->ghoul2, 0, va("*flash%d",num) ); NPC_Mark1_Part_Explode(self,newBolt); } else { num = Q_irand( 1, 6); newBolt = trap_G2API_AddBolt( self->ghoul2, 0, va("*torso_tube%d",num) ); NPC_Mark1_Part_Explode(self,newBolt); NPC_SetSurfaceOnOff( self, va("torso_tube%d",num), TURN_OFF ); } TIMER_Set( self, "dyingExplosion", Q_irand( 300, 1000 ) ); } // int dir; // vec3_t right; // Shove to the side // AngleVectors( self->client->renderInfo.eyeAngles, NULL, right, NULL ); // VectorMA( self->client->ps.velocity, -80, right, self->client->ps.velocity ); // See which weapons are there // Randomly fire blaster if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_arm" )) // Is the blaster still on the model? { if (Q_irand( 1, 5) == 1) { SaveNPCGlobals(); SetNPCGlobals( self ); Mark1Dead_FireBlaster(); RestoreNPCGlobals(); } } // Randomly fire rocket if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "r_arm" )) // Is the rocket still on the model? { if (Q_irand( 1, 10) == 1) { SaveNPCGlobals(); SetNPCGlobals( self ); Mark1Dead_FireRocket(); RestoreNPCGlobals(); } } } }
/* ------------------------- Droid_Patrol ------------------------- */ void Droid_Patrol( void ) { NPCS.NPC->pos1[1] = AngleNormalize360( NPCS.NPC->pos1[1]); if ( NPCS.NPC->client && NPCS.NPC->client->NPC_class != CLASS_GONK ) { if (NPCS.NPC->client->NPC_class != CLASS_R5D2) { //he doesn't have an eye. R2D2_PartsMove(); // Get his eye moving. } R2D2_TurnAnims(); } //If we have somewhere to go, then do that if ( UpdateGoal() ) { NPCS.ucmd.buttons |= BUTTON_WALKING; NPC_MoveToGoal( qtrue ); if( NPCS.NPC->client && NPCS.NPC->client->NPC_class == CLASS_MOUSE ) { NPCS.NPCInfo->desiredYaw += sin(level.time*.5) * 25; // Weaves side to side a little if (TIMER_Done(NPCS.NPC,"patrolNoise")) { G_SoundOnEnt( NPCS.NPC, CHAN_AUTO, va("sound/chars/mouse/misc/mousego%d.wav", Q_irand(1, 3)) ); TIMER_Set( NPCS.NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } else if( NPCS.NPC->client && NPCS.NPC->client->NPC_class == CLASS_R2D2 ) { if (TIMER_Done(NPCS.NPC,"patrolNoise")) { G_SoundOnEnt( NPCS.NPC, CHAN_AUTO, va("sound/chars/r2d2/misc/r2d2talk0%d.wav", Q_irand(1, 3)) ); TIMER_Set( NPCS.NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } else if( NPCS.NPC->client && NPCS.NPC->client->NPC_class == CLASS_R5D2 ) { if (TIMER_Done(NPCS.NPC,"patrolNoise")) { G_SoundOnEnt( NPCS.NPC, CHAN_AUTO, va("sound/chars/r5d2/misc/r5talk%d.wav", Q_irand(1, 4)) ); TIMER_Set( NPCS.NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } if( NPCS.NPC->client && NPCS.NPC->client->NPC_class == CLASS_GONK ) { if (TIMER_Done(NPCS.NPC,"patrolNoise")) { G_SoundOnEnt( NPCS.NPC, CHAN_AUTO, va("sound/chars/gonk/misc/gonktalk%d.wav", Q_irand(1, 2)) ); TIMER_Set( NPCS.NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } // else // { // R5D2_LookAround(); // } } NPC_UpdateAngles( qtrue, qtrue ); }
/* ------------------------- NPC_Mark1_Pain - look at what was hit and see if it should be removed from the model. ------------------------- */ void NPC_Mark1_Pain(gentity_t *self, gentity_t *attacker, int damage) { int newBolt,i,chance; int hitLoc = gPainHitLoc; NPC_Pain( self, attacker, damage ); G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/chars/mark1/misc/mark1_pain")); // Hit in the CHEST??? if (hitLoc==HL_CHEST) { chance = Q_irand( 1, 4); if ((chance == 1) && (damage > 5)) { NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } // Hit in the left arm? else if ((hitLoc==HL_ARM_LT) && (self->locationDamage[HL_ARM_LT] > LEFT_ARM_HEALTH)) { if (self->locationDamage[hitLoc] >= LEFT_ARM_HEALTH) // Blow it up? { newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*flash3" ); if ( newBolt != -1 ) { NPC_Mark1_Part_Explode(self,newBolt); } NPC_SetSurfaceOnOff( self, "l_arm", TURN_OFF ); } } // Hit in the right arm? else if ((hitLoc==HL_ARM_RT) && (self->locationDamage[HL_ARM_RT] > RIGHT_ARM_HEALTH)) // Blow it up? { if (self->locationDamage[hitLoc] >= RIGHT_ARM_HEALTH) { newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*flash4" ); if ( newBolt != -1 ) { // G_PlayEffect( "small_chunks", self->playerModel, self->genericBolt2, self->s.number); NPC_Mark1_Part_Explode( self, newBolt ); } NPC_SetSurfaceOnOff( self, "r_arm", TURN_OFF ); } } // Check ammo pods else { for (i=0;i<6;i++) { if ((hitLoc==HL_GENERIC1+i) && (self->locationDamage[HL_GENERIC1+i] > AMMO_POD_HEALTH)) // Blow it up? { if (self->locationDamage[hitLoc] >= AMMO_POD_HEALTH) { newBolt = trap_G2API_AddBolt( self->ghoul2, 0, va("*torso_tube%d",(i+1)) ); if ( newBolt != -1 ) { NPC_Mark1_Part_Explode(self,newBolt); } NPC_SetSurfaceOnOff( self, va("torso_tube%d",(i+1)), TURN_OFF ); NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); break; } } } } // Are both guns shot off? if ((trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_arm" )>0) && (trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "r_arm" )>0)) { G_Damage(self,NULL,NULL,NULL,NULL,self->health,0,MOD_UNKNOWN); } }
/* ------------------------- NPC_BSDroid_Pain ------------------------- */ void NPC_Droid_Pain(gentity_t *self, gentity_t *attacker, int damage) { gentity_t *other = attacker; int anim; int mod = gPainMOD; float pain_chance; VectorCopy( self->NPC->lastPathAngles, self->s.angles ); if ( self->client->NPC_class == CLASS_R5D2 ) { pain_chance = NPC_GetPainChance( self, damage ); // Put it in pain if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this { // Health is between 0-30 or was hit by a DEMP2 so pop his head if ( !self->s.m_iVehicleNum && ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) ) { if (!(self->spawnflags & 2)) // Doesn't have to ALWAYSDIE { if ((self->NPC->localState != LSTATE_SPINNING) && (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" ))) { NPC_SetSurfaceOnOff( self, "head", TURN_OFF ); if ( self->client->ps.m_iVehicleNum ) { vec3_t up; AngleVectors( self->r.currentAngles, NULL, NULL, up ); G_PlayEffectID( G_EffectIndex("chunks/r5d2head_veh"), self->r.currentOrigin, up ); } else { G_PlayEffectID( G_EffectIndex("small_chunks") , self->r.currentOrigin, vec3_origin ); G_PlayEffectID( G_EffectIndex("chunks/r5d2head"), self->r.currentOrigin, vec3_origin ); } //self->s.powerups |= ( 1 << PW_SHOCKED ); //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; self->client->ps.electrifyTime = level.time + 3000; TIMER_Set( self, "droidsmoketotal", 5000); TIMER_Set( self, "droidspark", 100); self->NPC->localState = LSTATE_SPINNING; } } } // Just give him normal pain for a little while else { anim = self->client->ps.legsAnim; if ( anim == BOTH_STAND2 ) // On two legs? { anim = BOTH_PAIN1; } else // On three legs { anim = BOTH_PAIN2; } NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); // Spin around in pain self->NPC->localState = LSTATE_SPINNING; TIMER_Set( self, "roam", Q_irand(1000,2000)); } } } else if (self->client->NPC_class == CLASS_MOUSE) { if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) { self->NPC->localState = LSTATE_SPINNING; //self->s.powerups |= ( 1 << PW_SHOCKED ); //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; self->client->ps.electrifyTime = level.time + 3000; } else { self->NPC->localState = LSTATE_BACKINGUP; } self->NPC->scriptFlags &= ~SCF_LOOK_FOR_ENEMIES; } else if (self->client->NPC_class == CLASS_R2D2) { pain_chance = NPC_GetPainChance( self, damage ); if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this { // Health is between 0-30 or was hit by a DEMP2 so pop his head if ( !self->s.m_iVehicleNum && ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) ) { if (!(self->spawnflags & 2)) // Doesn't have to ALWAYSDIE { if ((self->NPC->localState != LSTATE_SPINNING) && (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" ))) { NPC_SetSurfaceOnOff( self, "head", TURN_OFF ); if ( self->client->ps.m_iVehicleNum ) { vec3_t up; AngleVectors( self->r.currentAngles, NULL, NULL, up ); G_PlayEffectID( G_EffectIndex("chunks/r2d2head_veh"), self->r.currentOrigin, up ); } else { G_PlayEffectID( G_EffectIndex("small_chunks") , self->r.currentOrigin, vec3_origin ); G_PlayEffectID( G_EffectIndex("chunks/r2d2head"), self->r.currentOrigin, vec3_origin ); } //self->s.powerups |= ( 1 << PW_SHOCKED ); //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; self->client->ps.electrifyTime = level.time + 3000; TIMER_Set( self, "droidsmoketotal", 5000); TIMER_Set( self, "droidspark", 100); self->NPC->localState = LSTATE_SPINNING; } } } // Just give him normal pain for a little while else { anim = self->client->ps.legsAnim; if ( anim == BOTH_STAND2 ) // On two legs? { anim = BOTH_PAIN1; } else // On three legs { anim = BOTH_PAIN2; } NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); // Spin around in pain self->NPC->localState = LSTATE_SPINNING; TIMER_Set( self, "roam", Q_irand(1000,2000)); } } } else if ( self->client->NPC_class == CLASS_INTERROGATOR && ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) && other ) { vec3_t dir; VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir ); VectorNormalize( dir ); VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity ); self->client->ps.velocity[2] -= 127; } NPC_Pain( self, attacker, damage); }
void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs, float speed, int numChunks, material_t chunkType, int customChunk, float baseScale, int customSound = 0 ) { localEntity_t *le; refEntity_t *re; vec3_t dir; int i, j, k; int chunkModel = 0; leBounceSound_t bounce = LEBS_NONE; float r, speedMod = 1.0f; qboolean chunk = qfalse; if ( chunkType == MAT_NONE ) { // Well, we should do nothing return; } if ( customSound ) { if ( cgs.sound_precache[customSound] ) { cgi_S_StartSound( NULL, owner, CHAN_BODY, cgs.sound_precache[customSound] ); } } // Set up our chunk sound info...breaking sounds are done here so they are done once on breaking..some return instantly because the chunks are done with effects instead of models switch( chunkType ) { case MAT_GLASS: if ( !customSound ) { cgi_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.glassChunkSound ); } return; break; case MAT_GRATE1: if ( !customSound ) { cgi_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.grateSound ); } return; break; case MAT_ELECTRICAL:// (sparks) if ( !customSound ) { cgi_S_StartSound( NULL, owner, CHAN_BODY, cgi_S_RegisterSound (va("sound/ambience/spark%d.wav", Q_irand(1, 6))) ); } return; break; case MAT_DRK_STONE: case MAT_LT_STONE: case MAT_GREY_STONE: case MAT_WHITE_METAL: // not quite sure what this stuff is supposed to be...it's for Stu if ( !customSound ) { cgi_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.rockBreakSound ); bounce = LEBS_ROCK; } speedMod = 0.5f; // rock blows up less break; case MAT_GLASS_METAL: if ( !customSound ) { cgi_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.glassChunkSound ); // FIXME: should probably have a custom sound bounce = LEBS_METAL; } break; case MAT_CRATE1: case MAT_CRATE2: if ( !customSound ) { cgi_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.crateBreakSound[Q_irand(0,1)] ); } break; case MAT_METAL: case MAT_METAL2: case MAT_METAL3: case MAT_ELEC_METAL:// FIXME: maybe have its own sound? if ( !customSound ) { cgi_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.chunkSound ); bounce = LEBS_METAL; } speedMod = 0.8f; // metal blows up a bit more break; case MAT_ROPE: /* if ( !customSound ) { cgi_S_StartSound( NULL, owner, CHAN_BODY, cgi_S_RegisterSound( "" )); FIXME: needs a sound } */ return; default: break; } if ( baseScale <= 0.0f ) { baseScale = 1.0f; } // Chunks for( i = 0; i < numChunks; i++ ) { if ( customChunk > 0 ) { // Try to use a custom chunk. if ( cgs.model_draw[customChunk] ) { chunk = qtrue; chunkModel = cgs.model_draw[customChunk]; } } if ( !chunk ) { // No custom chunk. Pick a random chunk type at run-time so we don't get the same chunks switch( chunkType ) { case MAT_METAL2: //bluegrey chunkModel = cgs.media.chunkModels[CHUNK_METAL2][Q_irand(0, 3)]; break; case MAT_GREY_STONE://gray chunkModel = cgs.media.chunkModels[CHUNK_ROCK1][Q_irand(0, 3)]; break; case MAT_LT_STONE: //tan chunkModel = cgs.media.chunkModels[CHUNK_ROCK2][Q_irand(0, 3)]; break; case MAT_DRK_STONE://brown chunkModel = cgs.media.chunkModels[CHUNK_ROCK3][Q_irand(0, 3)]; break; case MAT_WHITE_METAL: chunkModel = cgs.media.chunkModels[CHUNK_WHITE_METAL][Q_irand(0, 3)]; break; case MAT_CRATE1://yellow multi-colored crate chunks chunkModel = cgs.media.chunkModels[CHUNK_CRATE1][Q_irand(0, 3)]; break; case MAT_CRATE2://red multi-colored crate chunks chunkModel = cgs.media.chunkModels[CHUNK_CRATE2][Q_irand(0, 3)]; break; case MAT_ELEC_METAL: case MAT_GLASS_METAL: case MAT_METAL://grey chunkModel = cgs.media.chunkModels[CHUNK_METAL1][Q_irand(0, 3)]; break; case MAT_METAL3: if ( rand() & 1 ) { chunkModel = cgs.media.chunkModels[CHUNK_METAL1][Q_irand(0, 3)]; } else { chunkModel = cgs.media.chunkModels[CHUNK_METAL2][Q_irand(0, 3)]; } break; default: break; } } // It wouldn't look good to throw a bunch of RGB axis models...so make sure we have something to work with. if ( chunkModel ) { le = CG_AllocLocalEntity(); re = &le->refEntity; re->hModel = chunkModel; le->leType = LE_FRAGMENT; le->endTime = cg.time + 1300 + random() * 900; // spawn chunk roughly in the bbox of the thing...bias towards center in case thing blowing up doesn't complete fill its bbox. for( j = 0; j < 3; j++ ) { r = random() * 0.8f + 0.1f; re->origin[j] = ( r * mins[j] + ( 1 - r ) * maxs[j] ); } VectorCopy( re->origin, le->pos.trBase ); // Move out from center of thing, otherwise you can end up things moving across the brush in an undesirable direction. Visually looks wrong VectorSubtract( re->origin, origin, dir ); VectorNormalize( dir ); VectorScale( dir, Q_flrand( speed * 0.5f, speed * 1.25f ) * speedMod, le->pos.trDelta ); // Angular Velocity VectorSet( le->angles.trBase, random() * 360, random() * 360, random() * 360 ); le->angles.trDelta[0] = crandom(); le->angles.trDelta[1] = crandom(); le->angles.trDelta[2] = 0; // don't do roll VectorScale( le->angles.trDelta, random() * 600.0f + 200.0f, le->angles.trDelta ); le->pos.trType = TR_GRAVITY; le->angles.trType = TR_LINEAR; le->pos.trTime = le->angles.trTime = cg.time; le->bounceFactor = 0.2f + random() * 0.2f; le->leFlags |= LEF_TUMBLE; le->ownerGentNum = owner; le->leBounceSoundType = bounce; // Make sure that we have the desired start size set le->radius = Q_flrand( baseScale * 0.75f, baseScale * 1.25f ); re->nonNormalizedAxes = qtrue; AxisCopy( axisDefault, re->axis ); // could do an angles to axis, but this is cheaper and works ok for( k = 0; k < 3; k++ ) { VectorScale( re->axis[k], le->radius, re->axis[k] ); } } } }
void target_random_use(gentity_t *self, gentity_t *other, gentity_t *activator) { int t_count = 0, pick; gentity_t *t = NULL; //gi.Printf("target_random %s used by %s (entnum %d)\n", self->targetname, activator->targetname, activator->s.number ); G_ActivateBehavior(self,BSET_USE); if(self->spawnflags & 1) { self->e_UseFunc = useF_NULL; } while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL ) { if (t != self) { t_count++; } } if(!t_count) { return; } if(t_count == 1) { G_UseTargets (self, activator); return; } //FIXME: need a seed pick = Q_irand(1, t_count); t_count = 0; while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL ) { if (t != self) { t_count++; } else { continue; } if (t == self) { // gi.Printf ("WARNING: Entity used itself.\n"); } else if(t_count == pick) { if (t->e_UseFunc != useF_NULL) // check can be omitted { GEntity_UseFunc(t, self, activator); return; } } if (!self->inuse) { gi.Printf("entity was removed while using targets\n"); return; } } }
void NPC_BSSaberDroid_Patrol( void ) {//FIXME: pick up on bodies of dead buddies? if ( NPCInfo->confusionTime < level.time ) {//not confused by mindtrick //Look for any enemies if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) { if ( NPC_CheckPlayerTeamStealth() ) {//found an enemy //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be automatic now //NPC_AngerSound(); NPC_UpdateAngles( qtrue, qtrue ); return; } } if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) {//alert reaction behavior. //Is there danger nearby //[CoOp] int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS, qfalse ); //int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS ); //[/CoOp] //There is an event to look at if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) { //NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID; if ( level.alertEvents[alertEvent].level >= AEL_DISCOVERED ) { if ( level.alertEvents[alertEvent].owner && level.alertEvents[alertEvent].owner->client && level.alertEvents[alertEvent].owner->health >= 0 && level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) {//an enemy G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); //NPCInfo->enemyLastSeenTime = level.time; TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); } } else {//FIXME: get more suspicious over time? //Save the position for movement (if necessary) VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal ); NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 ); if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS ) {//suspicious looks longer NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 ); } } } if ( NPCInfo->investigateDebounceTime > level.time ) {//FIXME: walk over to it, maybe? Not if not chase enemies //NOTE: stops walking or doing anything else below vec3_t dir, angles; float o_yaw, o_pitch; VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir ); vectoangles( dir, angles ); o_yaw = NPCInfo->desiredYaw; o_pitch = NPCInfo->desiredPitch; NPCInfo->desiredYaw = angles[YAW]; NPCInfo->desiredPitch = angles[PITCH]; NPC_UpdateAngles( qtrue, qtrue ); NPCInfo->desiredYaw = o_yaw; NPCInfo->desiredPitch = o_pitch; return; } } } //If we have somewhere to go, then do that if ( UpdateGoal() ) { ucmd.buttons |= BUTTON_WALKING; NPC_MoveToGoal( qtrue ); } else if ( !NPC->client->ps.weaponTime && TIMER_Done( NPC, "attackDelay" ) && TIMER_Done( NPC, "inactiveDelay" ) ) {//we want to turn off our saber if we need to. if ( !NPC->client->ps.saberHolstered ) {//saber is on. WP_DeactivateSaber( NPC, qfalse ); NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURNOFF, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } NPC_UpdateAngles( qtrue, qtrue ); }
/* ------------------------- FX_BlasterWeaponHitPlayer ------------------------- */ void FX_BlasterWeaponHitPlayer( gentity_t *hit, vec3_t origin, vec3_t normal, qboolean humanoid ) { //temporary? just testing out the damage skin stuff -rww if ( hit && hit->client && hit->ghoul2.size() ) { CG_AddGhoul2Mark(cgs.media.bdecal_burnmark1, flrand(3.5, 4.0), origin, normal, hit->s.number, hit->client->ps.origin, hit->client->renderInfo.legsYaw, hit->ghoul2, hit->s.modelScale, Q_irand(10000, 13000)); } theFxScheduler.PlayEffect( cgs.effects.blasterFleshImpactEffect, origin, normal ); }
//--------------------------------------------------------- void FireWeapon( gentity_t *ent, qboolean alt_fire ) //--------------------------------------------------------- { float alert = 256; Vehicle_t *pVeh = NULL; // track shots taken for accuracy tracking. ent->client->ps.persistant[PERS_ACCURACY_SHOTS]++; // If this is a vehicle, fire it's weapon and we're done. if ( ent && ent->client && ent->client->NPC_class == CLASS_VEHICLE ) { FireVehicleWeapon( ent, alt_fire ); return; } // set aiming directions if ( ent->s.weapon == WP_DISRUPTOR && alt_fire ) { if ( ent->NPC ) { //snipers must use the angles they actually did their shot trace with AngleVectors( ent->lastAngles, forwardVec, vrightVec, up ); } } else if ( ent->s.weapon == WP_ATST_SIDE || ent->s.weapon == WP_ATST_MAIN ) { vec3_t delta1, enemy_org1, muzzle1; vec3_t angleToEnemy1; VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle1 ); if ( !ent->s.number ) {//player driving an AT-ST //SIGH... because we can't anticipate alt-fire, must calc muzzle here and now mdxaBone_t boltMatrix; int bolt; if ( ent->client->ps.weapon == WP_ATST_MAIN ) {//FIXME: alt_fire should fire both barrels, but slower? if ( ent->alt_fire ) { bolt = ent->handRBolt; } else { bolt = ent->handLBolt; } } else {// ATST SIDE weapons if ( ent->alt_fire ) { if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_light_blaster_cann" ) ) {//don't have it! return; } bolt = ent->genericBolt2; } else { if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_concussion_charger" ) ) {//don't have it! return; } bolt = ent->genericBolt1; } } vec3_t yawOnlyAngles = {0, ent->currentAngles[YAW], 0}; if ( ent->currentAngles[YAW] != ent->client->ps.legsYaw ) { yawOnlyAngles[YAW] = ent->client->ps.legsYaw; } gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, bolt, &boltMatrix, yawOnlyAngles, ent->currentOrigin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale ); // work the matrix axis stuff into the original axis and origins used. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->renderInfo.muzzlePoint ); gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, ent->client->renderInfo.muzzleDir ); ent->client->renderInfo.mPCalcTime = level.time; AngleVectors( ent->client->ps.viewangles, forwardVec, vrightVec, up ); //CalcMuzzlePoint( ent, forwardVec, vrightVec, up, muzzle, 0 ); } else if ( !ent->enemy ) {//an NPC with no enemy to auto-aim at VectorCopy( ent->client->renderInfo.muzzleDir, forwardVec ); } else {//NPC, auto-aim at enemy CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 ); VectorSubtract (enemy_org1, muzzle1, delta1); vectoangles ( delta1, angleToEnemy1 ); AngleVectors (angleToEnemy1, forwardVec, vrightVec, up); } } else if ( ent->s.weapon == WP_BOT_LASER && ent->enemy ) { vec3_t delta1, enemy_org1, muzzle1; vec3_t angleToEnemy1; CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 ); CalcEntitySpot( ent, SPOT_WEAPON, muzzle1 ); VectorSubtract (enemy_org1, muzzle1, delta1); vectoangles ( delta1, angleToEnemy1 ); AngleVectors (angleToEnemy1, forwardVec, vrightVec, up); } else { if ( (pVeh = G_IsRidingVehicle( ent )) != NULL) //riding a vehicle {//use our muzzleDir, can't use viewangles or vehicle m_vOrientation because we may be animated to shoot left or right... if ((ent->s.eFlags&EF_NODRAW))//we're inside it { vec3_t aimAngles; VectorCopy( ent->client->renderInfo.muzzleDir, forwardVec ); vectoangles( forwardVec, aimAngles ); //we're only keeping the yaw aimAngles[PITCH] = ent->client->ps.viewangles[PITCH]; aimAngles[ROLL] = 0; AngleVectors( aimAngles, forwardVec, vrightVec, up ); } else { vec3_t actorRight; vec3_t actorFwd; VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle ); AngleVectors(ent->currentAngles, actorFwd, actorRight, 0); // Aiming Left //------------- if (ent->client->ps.torsoAnim==BOTH_VT_ATL_G || ent->client->ps.torsoAnim==BOTH_VS_ATL_G) { VectorScale(actorRight, -1.0f, forwardVec); } // Aiming Right //-------------- else if (ent->client->ps.torsoAnim==BOTH_VT_ATR_G || ent->client->ps.torsoAnim==BOTH_VS_ATR_G) { VectorCopy(actorRight, forwardVec); } // Aiming Forward //---------------- else { VectorCopy(actorFwd, forwardVec); } // If We Have An Enemy, Fudge The Aim To Hit The Enemy if (ent->enemy) { vec3_t toEnemy; VectorSubtract(ent->enemy->currentOrigin, ent->currentOrigin, toEnemy); VectorNormalize(toEnemy); if (DotProduct(toEnemy, forwardVec)>0.75f && ((ent->s.number==0 && !Q_irand(0,2)) || // the player has a 1 in 3 chance (ent->s.number!=0 && !Q_irand(0,5)))) // other guys have a 1 in 6 chance { VectorCopy(toEnemy, forwardVec); } else { forwardVec[0] += Q_flrand(-0.1f, 0.1f); forwardVec[1] += Q_flrand(-0.1f, 0.1f); forwardVec[2] += Q_flrand(-0.1f, 0.1f); } } } } else { AngleVectors( ent->client->ps.viewangles, forwardVec, vrightVec, up ); } } ent->alt_fire = alt_fire; if (!pVeh) { if (ent->NPC && (ent->NPC->scriptFlags&SCF_FIRE_WEAPON_NO_ANIM)) { VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle ); VectorCopy( ent->client->renderInfo.muzzleDir, forwardVec ); MakeNormalVectors(forwardVec, vrightVec, up); } else { CalcMuzzlePoint ( ent, forwardVec, vrightVec, up, muzzle , 0); } } // fire the specific weapon switch( ent->s.weapon ) { // Player weapons //----------------- case WP_SABER: return; break; case WP_BRYAR_PISTOL: case WP_BLASTER_PISTOL: WP_FireBryarPistol( ent, alt_fire ); break; case WP_BLASTER: WP_FireBlaster( ent, alt_fire ); break; case WP_TUSKEN_RIFLE: if ( alt_fire ) { WP_FireTuskenRifle( ent ); } else { WP_Melee( ent ); } break; case WP_DISRUPTOR: alert = 50; // if you want it to alert enemies, remove this WP_FireDisruptor( ent, alt_fire ); break; case WP_BOWCASTER: WP_FireBowcaster( ent, alt_fire ); break; case WP_REPEATER: WP_FireRepeater( ent, alt_fire ); break; case WP_DEMP2: WP_FireDEMP2( ent, alt_fire ); break; case WP_FLECHETTE: WP_FireFlechette( ent, alt_fire ); break; case WP_ROCKET_LAUNCHER: WP_FireRocket( ent, alt_fire ); break; case WP_CONCUSSION: WP_Concussion( ent, alt_fire ); break; case WP_THERMAL: WP_FireThermalDetonator( ent, alt_fire ); break; case WP_TRIP_MINE: alert = 0; // if you want it to alert enemies, remove this WP_PlaceLaserTrap( ent, alt_fire ); break; case WP_DET_PACK: alert = 0; // if you want it to alert enemies, remove this WP_FireDetPack( ent, alt_fire ); break; case WP_BOT_LASER: WP_BotLaser( ent ); break; case WP_EMPLACED_GUN: // doesn't care about whether it's alt-fire or not. We can do an alt-fire if needed WP_EmplacedFire( ent ); break; case WP_MELEE: alert = 0; // if you want it to alert enemies, remove this if ( !alt_fire || !g_debugMelee->integer ) { WP_Melee( ent ); } break; case WP_ATST_MAIN: WP_ATSTMainFire( ent ); break; case WP_ATST_SIDE: // TEMP if ( alt_fire ) { // WP_FireRocket( ent, qfalse ); WP_ATSTSideAltFire(ent); } else { // FIXME! /* if ( ent->s.number == 0 && ent->client->NPC_class == CLASS_VEHICLE && vehicleData[((CVehicleNPC *)ent->NPC)->m_iVehicleTypeID].type == VH_FIGHTER ) { WP_ATSTMainFire( ent ); } else*/ { WP_ATSTSideFire(ent); } } break; case WP_TIE_FIGHTER: // TEMP WP_EmplacedFire( ent ); break; case WP_RAPID_FIRE_CONC: // TEMP if ( alt_fire ) { WP_FireRepeater( ent, alt_fire ); } else { WP_EmplacedFire( ent ); } break; case WP_STUN_BATON: WP_FireStunBaton( ent, alt_fire ); break; // case WP_BLASTER_PISTOL: case WP_JAWA: WP_FireBryarPistol( ent, qfalse ); // never an alt-fire? break; case WP_SCEPTER: WP_FireScepter( ent, alt_fire ); break; case WP_NOGHRI_STICK: if ( !alt_fire ) { WP_FireNoghriStick( ent ); } //else does melee attack/damage/func break; case WP_TUSKEN_STAFF: default: return; break; } if ( !ent->s.number ) { if ( ent->s.weapon == WP_FLECHETTE || (ent->s.weapon == WP_BOWCASTER && !alt_fire) ) {//these can fire multiple shots, count them individually within the firing functions } else if ( W_AccuracyLoggableWeapon( ent->s.weapon, alt_fire, MOD_UNKNOWN ) ) { ent->client->sess.missionStats.shotsFired++; } } // We should probably just use this as a default behavior, in special cases, just set alert to false. if ( ent->s.number == 0 && alert > 0 ) { if ( ent->client->ps.groundEntityNum == ENTITYNUM_WORLD//FIXME: check for sand contents type? && ent->s.weapon != WP_STUN_BATON && ent->s.weapon != WP_MELEE && ent->s.weapon != WP_TUSKEN_STAFF && ent->s.weapon != WP_THERMAL && ent->s.weapon != WP_TRIP_MINE && ent->s.weapon != WP_DET_PACK ) {//the vibration of the shot carries through your feet into the ground AddSoundEvent( ent, muzzle, alert, AEL_DISCOVERED, qfalse, qtrue ); } else {//an in-air alert AddSoundEvent( ent, muzzle, alert, AEL_DISCOVERED ); } AddSightEvent( ent, muzzle, alert*2, AEL_DISCOVERED, 20 ); } }
void NPC_BuildRandom( gentity_t *NPC ) { int sex, color, head; sex = Q_irand(0, 2); color = Q_irand(0, 2); switch( sex ) { case 0://female head = Q_irand(0, 2); switch( head ) { default: case 0: Q_strncpyz( NPC->client->renderInfo.headModelName, "garren", sizeof(NPC->client->renderInfo.headModelName), qtrue ); break; case 1: Q_strncpyz( NPC->client->renderInfo.headModelName, "garren/salma", sizeof(NPC->client->renderInfo.headModelName), qtrue ); break; case 2: Q_strncpyz( NPC->client->renderInfo.headModelName, "garren/mackey", sizeof(NPC->client->renderInfo.headModelName), qtrue ); color = Q_irand(3, 5);//torso needs to be afam break; } switch( color ) { default: case 0: Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/gold", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); break; case 1: Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); break; case 2: Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/blue", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); break; case 3: Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframG", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); break; case 4: Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframR", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); break; case 5: Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframB", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); break; } Q_strncpyz( NPC->client->renderInfo.legsModelName, "crewfemale", sizeof(NPC->client->renderInfo.legsModelName), qtrue ); break; default: case 1://male case 2://male head = Q_irand(0, 4); switch( head ) { default: case 0: Q_strncpyz( NPC->client->renderInfo.headModelName, "chakotay/nelson", sizeof(NPC->client->renderInfo.headModelName), qtrue ); break; case 1: Q_strncpyz( NPC->client->renderInfo.headModelName, "paris/chase", sizeof(NPC->client->renderInfo.headModelName), qtrue ); break; case 2: Q_strncpyz( NPC->client->renderInfo.headModelName, "doctor/pasty", sizeof(NPC->client->renderInfo.headModelName), qtrue ); break; case 3: Q_strncpyz( NPC->client->renderInfo.headModelName, "kim/durk", sizeof(NPC->client->renderInfo.headModelName), qtrue ); break; case 4: Q_strncpyz( NPC->client->renderInfo.headModelName, "paris/kray", sizeof(NPC->client->renderInfo.headModelName), qtrue ); break; } switch( color ) { default: case 0: Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin/red", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); break; case 1: Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); break; case 2: Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin/blue", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); break; //NOTE: 3 - 5 should be red, gold & blue, afram hands } Q_strncpyz( NPC->client->renderInfo.legsModelName, "crewthin", sizeof(NPC->client->renderInfo.legsModelName), qtrue ); break; } NPC->s.modelScale[0] = NPC->s.modelScale[1] = NPC->s.modelScale[2] = Q_irand(87, 102)/100.0f; // NPC->client->race = RACE_HUMAN; NPC->NPC->rank = RANK_CREWMAN; NPC->client->playerTeam = TEAM_PLAYER; NPC->client->clientInfo.customBasicSoundDir = "kyle"; }
static void Grenadier_CheckMoveState( void ) { //See if we're a scout if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//behaviorState == BS_STAND_AND_SHOOT ) { if ( NPCInfo->goalEntity == NPC->enemy ) { move = qfalse; return; } } //See if we're running away else if ( NPCInfo->squadState == SQUAD_RETREAT ) { if ( TIMER_Done( NPC, "flee" ) ) { NPCInfo->squadState = SQUAD_IDLE; } else { faceEnemy = qfalse; } } /* else if ( NPCInfo->squadState == SQUAD_IDLE ) { if ( !NPCInfo->goalEntity ) { move = qfalse; return; } //Should keep moving toward player when we're out of range... right? } */ //See if we're moving towards a goal, not the enemy if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) { //Did we make it? if ( STEER::Reached(NPC, NPCInfo->goalEntity, 16, !!FlyingCreature(NPC)) || ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS && enemyDist <= 10000 ) ) { int newSquadState = SQUAD_STAND_AND_SHOOT; //we got where we wanted to go, set timers based on why we were running switch ( NPCInfo->squadState ) { case SQUAD_RETREAT://was running away TIMER_Set( NPC, "duck", (NPC->max_health - NPC->health) * 100 ); TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) ); newSquadState = SQUAD_COVER; break; case SQUAD_TRANSITION://was heading for a combat point TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) ); break; case SQUAD_SCOUT://was running after player break; default: break; } NPC_ReachedGoal(); //don't attack right away TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); //FIXME: Slant for difficulty levels //don't do something else just yet TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) ); //stop fleeing if ( NPCInfo->squadState == SQUAD_RETREAT ) { TIMER_Set( NPC, "flee", -level.time ); NPCInfo->squadState = SQUAD_IDLE; } return; } //keep going, hold of roamTimer until we get there TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) ); } if ( !NPCInfo->goalEntity ) { if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) { NPCInfo->goalEntity = NPC->enemy; NPCInfo->goalRadius = (NPC->maxs[0]*1.5f); } } }
int BotDoChat(bot_state_t *bs, char *section, int always) { char *chatgroup; int rVal; int inc_1; int inc_2; int inc_n; int lines; int checkedline; int getthisline; gentity_t *cobject; if (!bs->canChat) { return 0; } if (bs->doChat) { //already have a chat scheduled return 0; } if (trap->Cvar_VariableIntegerValue("se_language")) { //no chatting unless English. return 0; } if (Q_irand(1, 10) > bs->chatFrequency && !always) { return 0; } bs->chatTeam = 0; chatgroup = (char *)B_TempAlloc(MAX_CHAT_BUFFER_SIZE); rVal = GetValueGroup(gBotChatBuffer[bs->client], section, chatgroup); if (!rVal) //the bot has no group defined for the specified chat event { B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup return 0; } inc_1 = 0; inc_2 = 2; while (chatgroup[inc_2] && chatgroup[inc_2] != '\0') { if (chatgroup[inc_2] != 13 && chatgroup[inc_2] != 9) { chatgroup[inc_1] = chatgroup[inc_2]; inc_1++; } inc_2++; } chatgroup[inc_1] = '\0'; inc_1 = 0; lines = 0; while (chatgroup[inc_1] && chatgroup[inc_1] != '\0') { if (chatgroup[inc_1] == '\n') { lines++; } inc_1++; } if (!lines) { B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup return 0; } getthisline = Q_irand(0, (lines+1)); if (getthisline < 1) { getthisline = 1; } if (getthisline > lines) { getthisline = lines; } checkedline = 1; inc_1 = 0; while (checkedline != getthisline) { if (chatgroup[inc_1] && chatgroup[inc_1] != '\0') { if (chatgroup[inc_1] == '\n') { inc_1++; checkedline++; } } if (checkedline == getthisline) { break; } inc_1++; } //we're at the starting position of the desired line here inc_2 = 0; while (chatgroup[inc_1] != '\n') { chatgroup[inc_2] = chatgroup[inc_1]; inc_2++; inc_1++; } chatgroup[inc_2] = '\0'; //trap->EA_Say(bs->client, chatgroup); inc_1 = 0; inc_2 = 0; if (strlen(chatgroup) > MAX_CHAT_LINE_SIZE) { B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup return 0; } while (chatgroup[inc_1]) { if (chatgroup[inc_1] == '%' && chatgroup[inc_1+1] != '%') { inc_1++; if (chatgroup[inc_1] == 's' && bs->chatObject) { cobject = bs->chatObject; } else if (chatgroup[inc_1] == 'a' && bs->chatAltObject) { cobject = bs->chatAltObject; } else { cobject = NULL; } if (cobject && cobject->client) { inc_n = 0; while (cobject->client->pers.netname[inc_n]) { bs->currentChat[inc_2] = cobject->client->pers.netname[inc_n]; inc_2++; inc_n++; } inc_2--; //to make up for the auto-increment below } } else { bs->currentChat[inc_2] = chatgroup[inc_1]; } inc_2++; inc_1++; } bs->currentChat[inc_2] = '\0'; if (strcmp(section, "GeneralGreetings") == 0) { bs->doChat = 2; } else { bs->doChat = 1; } bs->chatTime_stored = (strlen(bs->currentChat)*45)+Q_irand(1300, 1500); bs->chatTime = level.time + bs->chatTime_stored; B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup return 1; }
void TieFighterThink ( gentity_t *self ) { gentity_t *player = &g_entities[0]; if ( self->health <= 0 ) { return; } self->nextthink = level.time + FRAMETIME; if ( player ) { vec3_t playerDir, fighterDir, fwd, rt; float playerDist, fighterSpeed; //use player eyepoint VectorSubtract( player->currentOrigin, self->currentOrigin, playerDir ); playerDist = VectorNormalize( playerDir ); VectorSubtract( self->currentOrigin, self->lastOrigin, fighterDir ); VectorCopy( self->currentOrigin, self->lastOrigin ); fighterSpeed = VectorNormalize( fighterDir )*1000; AngleVectors( self->currentAngles, fwd, rt, NULL ); if ( fighterSpeed ) { float side; // Magic number fun! Speed is used for banking, so modulate the speed by a sine wave fighterSpeed *= sin( ( 100 ) * 0.003 ); // Clamp to prevent harsh rolling if ( fighterSpeed > 10 ) fighterSpeed = 10; side = fighterSpeed * DotProduct( fighterDir, rt ); self->s.apos.trBase[2] -= side; } //FIXME: bob up/down, strafe left/right some float dot = DotProduct( playerDir, fighterDir ); if ( dot > 0 ) {//heading toward the player if ( playerDist < 1024 ) { if ( DotProduct( playerDir, fwd ) > 0.7 ) {//facing the player if ( self->attackDebounceTime < level.time ) { gentity_t *bolt; bolt = G_Spawn(); bolt->classname = "tie_proj"; bolt->nextthink = level.time + 10000; bolt->e_ThinkFunc = thinkF_G_FreeEntity; bolt->s.eType = ET_MISSILE; bolt->s.weapon = WP_BLASTER; bolt->owner = self; bolt->damage = 30; bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming bolt->splashDamage = 0; bolt->splashRadius = 0; bolt->methodOfDeath = MOD_ENERGY; // ? bolt->clipmask = MASK_SHOT; bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time; // move a bit on the very first frame VectorCopy( self->currentOrigin, bolt->s.pos.trBase ); VectorScale( fwd, 8000, bolt->s.pos.trDelta ); SnapVector( bolt->s.pos.trDelta ); // save net bandwidth VectorCopy( self->currentOrigin, bolt->currentOrigin); if ( !Q_irand( 0, 2 ) ) { G_SoundOnEnt( bolt, CHAN_VOICE, "sound/weapons/tie_fighter/tie_fire.wav" ); } else { G_SoundOnEnt( bolt, CHAN_VOICE, va( "sound/weapons/tie_fighter/tie_fire%d.wav", Q_irand( 2, 3 ) ) ); } self->attackDebounceTime = level.time + Q_irand( 300, 2000 ); } } } } if ( playerDist < 1024 )//512 ) {//within range to start our sound if ( dot > 0 ) { if ( !self->fly_sound_debounce_time ) {//start sound G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/tie_fighter/tiepass%d.wav", Q_irand( 1, 5 ) ) ); self->fly_sound_debounce_time = 2000; } else {//sound already started self->fly_sound_debounce_time = -1; } } } else if ( self->fly_sound_debounce_time < level.time ) { self->fly_sound_debounce_time = 0; } } }
void target_random_use(gentity_t *self, gentity_t *other, gentity_t *activator) { int t_count = 0, pick; gentity_t *t = NULL; G_ActivateBehavior(self,BSET_USE); if(self->spawnflags & 1) { self->use = 0; } while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL ) { if (t != self) { t_count++; } } if(!t_count) { return; } if(t_count == 1) { G_UseTargets (self, activator); return; } //FIXME: need a seed pick = Q_irand(1, t_count); t_count = 0; while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL ) { if (t != self) { t_count++; } else { continue; } if (t == self) { } else if(t_count == pick) { if (t->use != NULL) // check can be omitted { GlobalUse(t, self, activator); return; } } if (!self->inuse) { Com_Printf("entity was removed while using targets\n"); return; } } }
void Mark2_AttackDecision( void ) { float distance; qboolean visible; qboolean advance; NPC_FaceEnemy( qtrue ); distance = (int)DistanceHorizontalSquared( &NPC->r.currentOrigin, &NPC->enemy->r.currentOrigin ); visible = NPC_ClearLOS4( NPC->enemy ); advance = (qboolean)(distance > MIN_DISTANCE_SQR); // He's been ordered to get up if ( NPCInfo->localState == LSTATE_RISINGUP ) { NPC->flags &= ~FL_SHIELDED; NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1START, SETANIM_FLAG_HOLD | SETANIM_FLAG_OVERRIDE ); if ( (NPC->client->ps.legsTimer <= 0) && NPC->client->ps.torsoAnim == BOTH_RUN1START ) { NPCInfo->localState = LSTATE_NONE; // He's up again. } return; } // If we cannot see our target, move to see it if ( (!visible) || (!NPC_FaceEnemy( qtrue )) ) { // If he's going down or is down, make him get up if ( (NPCInfo->localState == LSTATE_DOWN) || (NPCInfo->localState == LSTATE_DROPPINGDOWN) ) { if ( TIMER_Done( NPC, "downTime" ) ) // Down being down?? (The delay is so he doesn't pop up and down when the player goes in and out of range) { NPCInfo->localState = LSTATE_RISINGUP; NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD | SETANIM_FLAG_OVERRIDE ); TIMER_Set( NPC, "runTime", Q_irand( 3000, 8000 ) ); // So he runs for a while before testing to see if he should drop down. } } else { Mark2_Hunt(); } return; } // He's down but he could advance if he wants to. if ( (advance) && (TIMER_Done( NPC, "downTime" )) && (NPCInfo->localState == LSTATE_DOWN) ) { NPCInfo->localState = LSTATE_RISINGUP; NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD | SETANIM_FLAG_OVERRIDE ); TIMER_Set( NPC, "runTime", Q_irand( 3000, 8000 ) ); // So he runs for a while before testing to see if he should drop down. } NPC_FaceEnemy( qtrue ); // Dropping down to shoot if ( NPCInfo->localState == LSTATE_DROPPINGDOWN ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD | SETANIM_FLAG_OVERRIDE ); TIMER_Set( NPC, "downTime", Q_irand( 3000, 9000 ) ); if ( (NPC->client->ps.legsTimer <= 0) && NPC->client->ps.torsoAnim == BOTH_RUN1STOP ) { NPC->flags |= FL_SHIELDED; NPCInfo->localState = LSTATE_DOWN; } } // He's down and shooting else if ( NPCInfo->localState == LSTATE_DOWN ) { NPC->flags |= FL_SHIELDED;//only damagable by lightsabers and missiles Mark2_BlasterAttack( qfalse ); } else if ( TIMER_Done( NPC, "runTime" ) ) // Lowering down to attack. But only if he's done running at you. { NPCInfo->localState = LSTATE_DROPPINGDOWN; } else if ( advance ) { // We can see enemy so shoot him if timer lets you. Mark2_BlasterAttack( advance ); } }
//------------------------------------ void Seeker_MaintainHeight( void ) { float dif; // Update our angles regardless NPC_UpdateAngles( qtrue, qtrue ); // If we have an enemy, we should try to hover at or a little below enemy eye level if ( NPC->enemy ) { if (TIMER_Done( NPC, "heightChange" )) { TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); // Find the height difference dif = (NPC->enemy->currentOrigin[2] + Q_flrand( NPC->enemy->maxs[2]/2, NPC->enemy->maxs[2]+8 )) - NPC->currentOrigin[2]; float difFactor = 1.0f; if ( NPC->client->NPC_class == CLASS_BOBAFETT ) { if ( TIMER_Done( NPC, "flameTime" ) ) { difFactor = 10.0f; } } // cap to prevent dramatic height shifts if ( fabs( dif ) > 2*difFactor ) { if ( fabs( dif ) > 24*difFactor ) { dif = ( dif < 0 ? -24*difFactor : 24*difFactor ); } NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; } if ( NPC->client->NPC_class == CLASS_BOBAFETT ) { NPC->client->ps.velocity[2] *= Q_flrand( 0.85f, 3.0f ); } } } else { gentity_t *goal = NULL; if ( NPCInfo->goalEntity ) // Is there a goal? { goal = NPCInfo->goalEntity; } else { goal = NPCInfo->lastGoalEntity; } if ( goal ) { dif = goal->currentOrigin[2] - NPC->currentOrigin[2]; if ( fabs( dif ) > 24 ) { ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); } else { if ( NPC->client->ps.velocity[2] ) { NPC->client->ps.velocity[2] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) { NPC->client->ps.velocity[2] = 0; } } } } } // Apply friction if ( NPC->client->ps.velocity[0] ) { NPC->client->ps.velocity[0] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) { NPC->client->ps.velocity[0] = 0; } } if ( NPC->client->ps.velocity[1] ) { NPC->client->ps.velocity[1] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) { NPC->client->ps.velocity[1] = 0; } } }