/* ============ AICast_Blocked ============ */ void AICast_Blocked( cast_state_t *cs, bot_moveresult_t *moveresult, int activate, bot_goal_t *goal ) { vec3_t pos, dir; aicast_predictmove_t move; usercmd_t ucmd; bot_input_t bi; cast_state_t *ocs; int i, blockEnt = -1; bot_goal_t ogoal; if ( cs->blockedAvoidTime < level.time ) { if ( cs->blockedAvoidTime < level.time - 300 ) { if ( VectorCompare( cs->bs->cur_ps.velocity, vec3_origin ) && !cs->bs->lastucmd.forwardmove && !cs->bs->lastucmd.rightmove ) { // not moving, don't bother checking cs->blockedAvoidTime = level.time - 1; return; } // are we going to hit someone soon? trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi ); AICast_InputToUserCommand( cs, &bi, &ucmd, cs->bs->cur_ps.delta_angles ); AICast_PredictMovement( cs, 1, 0.6, &move, &ucmd, ( goal && goal->entitynum > -1 ) ? goal->entitynum : cs->entityNum ); // blocked if we hit a client (or non-stationary mover) other than our enemy or goal if ( move.stopevent != PREDICTSTOP_HITCLIENT ) { // not blocked cs->blockedAvoidTime = level.time - 1; return; } // if we stopped passed our goal, ignore it if ( goal ) { if ( VectorDistance( cs->bs->origin, goal->origin ) < VectorDistance( cs->bs->origin, move.endpos ) ) { vec3_t v1, v2; VectorSubtract( goal->origin, cs->bs->origin, v1 ); VectorSubtract( goal->origin, move.endpos, v2 ); VectorNormalize( v1 ); VectorNormalize( v2 ); if ( DotProduct( v1, v2 ) < 0 ) { // we went passed the goal, so assume we can reach it cs->blockedAvoidTime = level.time - 1; return; } } } // try and get them to move, in case we can't get around them blockEnt = -1; for ( i = 0; i < move.numtouch; i++ ) { if ( move.touchents[i] >= MAX_CLIENTS ) { if ( !Q_stricmp( g_entities[move.touchents[i]].classname, "script_mover" ) ) { // avoid script_mover's blockEnt = move.touchents[i]; } // if we are close to the impact point, then avoid this entity else if ( VectorDistance( cs->bs->origin, move.endpos ) < 10 ) { //G_Printf("AI (%s) avoiding %s\n", g_entities[cs->entityNum].aiName, g_entities[move.touchents[i]].classname ); blockEnt = move.touchents[i]; } continue; } // ocs = AICast_GetCastState( move.touchents[i] ); if ( !ocs->bs ) { blockEnt = move.touchents[i]; } // reject this blocker if we are following or going to them else if ( cs->followEntity != ocs->entityNum ) { // if they are moving away from us already, let them go if ( VectorLength( ocs->bs->cur_ps.velocity ) > 10 ) { vec3_t v1, v2; VectorSubtract( ocs->bs->origin, cs->bs->origin, v2 ); VectorNormalize( v2 ); VectorNormalize2( ocs->bs->cur_ps.velocity, v1 ); if ( DotProduct( v1, v2 ) > 0.0 ) { continue; } } // // if they recently were asked to avoid us, then they're probably not listening if ( ocs->obstructingTime > level.time - 500 ) { blockEnt = move.touchents[i]; } // // if they are not avoiding, ignore if ( !( ocs->aiFlags & AIFL_NOAVOID ) ) { continue; } // // they should avoid us if ( ocs->leaderNum >= 0 ) { ogoal.entitynum = ocs->leaderNum; VectorCopy( g_entities[ocs->leaderNum].r.currentOrigin, ogoal.origin ); if ( AICast_GetAvoid( ocs, &ogoal, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else ocs->obstructingTime = level.time + 1000; } else { // make sure they don't call GetAvoid() for another few frames to let others avoid also ocs->obstructingTime = level.time - 1; blockEnt = move.touchents[i]; } } else { if ( AICast_GetAvoid( ocs, NULL, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else ocs->obstructingTime = level.time + 1000; } else { // make sure they don't call GetAvoid() for another few frames to let others avoid also ocs->obstructingTime = level.time - 1; blockEnt = move.touchents[i]; } } } } } else { return; } if ( blockEnt < 0 ) { // nothing found to be worth avoding cs->blockedAvoidTime = level.time - 1; return; } // something is blocking our path if ( g_entities[blockEnt].aiName && g_entities[blockEnt].client ) { int oldId = cs->castScriptStatus.scriptId; AICast_ScriptEvent( cs, "blocked", g_entities[blockEnt].aiName ); if ( oldId != cs->castScriptStatus.scriptId ) { // the script has changed, so assume the scripting is handling the avoidance return; } } // avoid geometry and props, but assume clients will get out the way if ( /*blockEnt > MAX_CLIENTS &&*/ AICast_GetAvoid( cs, goal, pos, qfalse, blockEnt ) ) { VectorSubtract( pos, cs->bs->cur_ps.origin, dir ); VectorNormalize( dir ); cs->blockedAvoidYaw = vectoyaw( dir ); if ( blockEnt >= MAX_CLIENTS ) { cs->blockedAvoidTime = level.time + 100 + rand() % 200; } else { cs->blockedAvoidTime = level.time + 300 + rand() % 400; } } else { cs->blockedAvoidTime = level.time - 1; // don't look again for another few frames return; } } VectorClear( pos ); pos[YAW] = cs->blockedAvoidYaw; AngleVectors( pos, dir, NULL, NULL ); if ( moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE ) { trap_EA_Jump( cs->bs->entitynum ); } trap_EA_Move( cs->bs->entitynum, dir, 200 ); //400); vectoangles( dir, cs->bs->ideal_viewangles ); cs->bs->ideal_viewangles[2] *= 0.5; }
const char *AIFunc_Heinrich_Earthquake( cast_state_t *cs ) { gentity_t *ent = &g_entities[cs->entityNum]; gentity_t *enemy; cast_state_t *ecs; vec3_t enemyVec; float enemyDist, scale; trace_t *tr; cs->aiFlags |= AIFL_SPECIAL_FUNC; if ( cs->enemyNum < 0 ) { if ( !ent->client->ps.torsoTimer ) { return AIFunc_DefaultStart( cs ); } return NULL; } enemy = &g_entities[cs->enemyNum]; ecs = AICast_GetCastState( cs->enemyNum ); VectorMA( enemy->r.currentOrigin, HEINRICH_STOMP_DELAY, enemy->client->ps.velocity, enemyVec ); enemyDist = VectorDistance( ent->r.currentOrigin, enemyVec ); if ( ent->client->ps.torsoTimer < 500 ) { int rnd; aicast_predictmove_t move; vec3_t vec; AICast_PredictMovement( ecs, 2, 0.5, &move, &g_entities[cs->enemyNum].client->pers.cmd, -1 ); VectorSubtract( move.endpos, cs->bs->origin, vec ); vec[2] = 0; enemyDist = VectorLength( vec ); enemyDist -= g_entities[cs->enemyNum].r.maxs[0]; enemyDist -= ent->r.maxs[0]; // if ( enemyDist < 140 ) { // combo attack rnd = rand() % 3; switch ( rnd ) { case 0: return AIFunc_Heinrich_SwordSideSlashStart( cs ); case 1: return AIFunc_Heinrich_SwordKnockbackStart( cs ); case 2: return AIFunc_Heinrich_SwordLungeStart( cs ); } } else { // back to roaming ent->client->ps.legsTimer = 0; ent->client->ps.torsoTimer = 0; cs->castScriptStatus.scriptNoMoveTime = 0; AICast_Heinrich_Taunt( cs ); return AIFunc_DefaultStart( cs ); } } // time for the thump? if ( !( cs->aiFlags & AIFL_MISCFLAG1 ) ) { // face them AICast_AimAtEnemy( cs ); // ready for damage? if ( cs->thinkFuncChangeTime < level.time - HEINRICH_STOMP_DELAY ) { cs->aiFlags |= AIFL_MISCFLAG1; // play the stomp sound G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[ORDERSDENYSOUNDSCRIPT] ) ); // check for striking the player tr = CheckMeleeAttack( ent, 70, qfalse ); // do melee damage if ( tr && ( tr->entityNum == cs->enemyNum ) ) { G_Damage( &g_entities[tr->entityNum], ent, ent, vec3_origin, tr->endpos, HEINRICH_STOMP_DAMAGE, 0, MOD_GAUNTLET ); } // call the debris trigger AICast_ScriptEvent( cs, "trigger", "quake" ); } } enemyDist = Distance( enemy->s.pos.trBase, ent->s.pos.trBase ); // do the earthquake effects if ( cs->thinkFuncChangeTime < level.time - HEINRICH_STOMP_DELAY ) { // throw the player into the air, if they are on the ground if ( ( enemy->s.groundEntityNum != ENTITYNUM_NONE ) && enemyDist < HEINRICH_STOMP_RANGE ) { scale = 0.5 + 0.5 * ( (float)ent->client->ps.torsoTimer / 1000.0 ); if ( scale > 1.0 ) { scale = 1.0; } VectorSubtract( ent->s.pos.trBase, enemy->s.pos.trBase, enemyVec ); VectorScale( enemyVec, 2.0 * ( 0.6 + 0.5 * random() ) * scale * ( 0.6 + 0.6 * ( 1.0 - ( enemyDist / HEINRICH_STOMP_RANGE ) ) ), enemyVec ); enemyVec[2] = scale * HEINRICH_STOMP_VELOCITY_Z * ( 1.0 - 0.5 * ( enemyDist / HEINRICH_STOMP_RANGE ) ); // bounce the player using this velocity VectorAdd( enemy->client->ps.velocity, enemyVec, enemy->client->ps.velocity ); } } return NULL; }
/* ============ AICast_GetAvoid ============ */ qboolean AICast_GetAvoid( cast_state_t *cs, bot_goal_t *goal, vec3_t outpos, qboolean reverse, int blockEnt ) { float yaw, oldyaw, distmoved, bestmoved, bestyaw; vec3_t bestpos; aicast_predictmove_t castmove; usercmd_t ucmd; qboolean enemyVisible; float angleDiff; // TTimo might be used uninitialized int starttraveltime = 0; int besttraveltime, traveltime; int invert; float inc; qboolean averting = qfalse; float maxYaw, simTime; static int lastTime; VectorCopy( vec3_origin, bestpos ); // // if we are in the air, no chance of avoiding if ( cs->bs->cur_ps.groundEntityNum == ENTITYNUM_NONE && g_entities[cs->entityNum].waterlevel <= 1 ) { return qfalse; } // if ( cs->lastAvoid > level.time - rand() % 500 ) { return qfalse; } cs->lastAvoid = level.time + 50 + rand() % 500; // if ( lastTime == level.time ) { return qfalse; } lastTime = level.time; // if they have an enemy, and can currently see them, don't move out of their view enemyVisible = ( cs->bs->enemy >= 0 ) && ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) ); // // look for a good direction to move out of the way bestmoved = 0; bestyaw = 360; besttraveltime = 9999999; if ( goal ) { starttraveltime = trap_AAS_AreaTravelTimeToGoalArea( cs->bs->areanum, cs->bs->origin, goal->areanum, cs->travelflags ); } memcpy( &ucmd, &cs->bs->lastucmd, sizeof( usercmd_t ) ); ucmd.forwardmove = 127; ucmd.rightmove = 0; ucmd.upmove = 0; if ( cs->dangerEntity >= 0 && cs->dangerEntityValidTime >= level.time ) { averting = qtrue; } else if ( !goal ) { averting = qtrue; // not heading for a goal, so we must be getting out of someone's way } // maxYaw = 0; simTime = 1.2; // if ( averting ) { // avoiding danger, go anywhere! angleDiff = 300; inc = 60; invert = 1; } else { if ( level.time % 1000 < 500 ) { invert = 1; } else { invert = -1; } angleDiff = 140; inc = 35; } if ( blockEnt > aicast_maxclients ) { maxYaw = angleDiff; simTime = 0.5; } // for ( yaw = -angleDiff * invert; yaw*invert <= maxYaw; yaw += inc * invert ) { if ( !averting && !yaw ) { continue; } oldyaw = cs->bs->cur_ps.viewangles[YAW]; cs->bs->cur_ps.viewangles[YAW] += yaw + reverse * 180; // ucmd.angles[YAW] = ANGLE2SHORT( AngleMod( cs->bs->cur_ps.viewangles[YAW] ) ); // AICast_PredictMovement( cs, 5, 0.4, &castmove, &ucmd, -1 ); // if we have a danger entity, try and get away from it at all costs if ( cs->dangerEntity >= 0 && cs->dangerEntityValidTime >= level.time ) { distmoved = Distance( castmove.endpos, cs->dangerEntityPos ); } else if ( goal ) { //distmoved = 99999 - trap_AAS_AreaTravelTimeToGoalArea( BotPointAreaNum(castmove.endpos), castmove.endpos, goal->areanum, cs->travelflags ); distmoved = 99999 - Distance( castmove.endpos, goal->origin ); } else { distmoved = Distance( castmove.endpos, cs->bs->cur_ps.origin ); } if ( ( distmoved > bestmoved ) //&& ((cs->bs->origin[2] - castmove.endpos[2]) < 64) // allow up, but not down (falling) && ( castmove.groundEntityNum != ENTITYNUM_NONE ) ) { // they all passed, check any other stuff if ( !enemyVisible || AICast_CheckAttackAtPos( cs->entityNum, cs->bs->enemy, castmove.endpos, qfalse, qfalse ) ) { if ( !goal || ( traveltime = trap_AAS_AreaTravelTimeToGoalArea( BotPointAreaNum( castmove.endpos ), castmove.endpos, goal->areanum, cs->travelflags ) ) < ( starttraveltime + 200 ) ) { bestyaw = yaw; bestmoved = distmoved; besttraveltime = traveltime; VectorCopy( castmove.endpos, bestpos ); } } } // cs->bs->cur_ps.viewangles[YAW] = oldyaw; } // if ( bestmoved > 0 ) { VectorCopy( bestpos, outpos ); return qtrue; } else { return qfalse; } //G_Printf("GetAvoid: %i ms\n", -pretime + Sys_MilliSeconds() ); }
const char *AIFunc_Heinrich_SwordSideSlash( cast_state_t *cs ) { gentity_t *ent = &g_entities[cs->entityNum]; trace_t *tr; vec3_t right, left; float enemyDist; aicast_predictmove_t move; vec3_t vec; cast_state_t *ecs; cs->aiFlags |= AIFL_SPECIAL_FUNC; if ( cs->enemyNum < 0 ) { if ( ent->client->ps.torsoTimer ) { return NULL; } return AIFunc_DefaultStart( cs ); } ecs = AICast_GetCastState( cs->enemyNum ); if ( ent->client->ps.torsoTimer < 500 ) { if ( !ent->client->ps.legsTimer ) { trap_EA_MoveForward( cs->entityNum ); } ent->client->ps.legsTimer = 0; ent->client->ps.torsoTimer = 0; cs->castScriptStatus.scriptNoMoveTime = 0; AICast_Heinrich_Taunt( cs ); return AIFunc_BattleChaseStart( cs ); } // time for the melee? if ( cs->enemyNum >= 0 && !( cs->aiFlags & AIFL_MISCFLAG1 ) ) { // face them AICast_AimAtEnemy( cs ); // keep checking for impact status tr = CheckMeleeAttack( ent, HEINRICH_SLASH_RANGE, qfalse ); // ready for damage? if ( cs->thinkFuncChangeTime < level.time - HEINRICH_SLASH_DELAY ) { cs->aiFlags |= AIFL_MISCFLAG1; // do melee damage if ( tr && ( tr->entityNum == cs->enemyNum ) ) { AngleVectors( cs->viewangles, NULL, right, NULL ); VectorNegate( right, left ); G_Damage( &g_entities[tr->entityNum], ent, ent, left, tr->endpos, HEINRICH_SLASH_DAMAGE, 0, MOD_GAUNTLET ); // sound G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_SWORDIMPACT] ); // throw them in direction of impact left[2] = 0.5; VectorMA( g_entities[cs->enemyNum].client->ps.velocity, 400, left, g_entities[cs->enemyNum].client->ps.velocity ); } } } // if they are outside range, move forward AICast_PredictMovement( ecs, 2, 0.3, &move, &g_entities[cs->enemyNum].client->pers.cmd, -1 ); VectorSubtract( move.endpos, cs->bs->origin, vec ); vec[2] = 0; enemyDist = VectorLength( vec ); enemyDist -= g_entities[cs->enemyNum].r.maxs[0]; enemyDist -= ent->r.maxs[0]; if ( enemyDist > 30 ) { // we can get closer if ( ent->client->ps.legsTimer ) { cs->castScriptStatus.scriptNoMoveTime = level.time + 100; ent->client->ps.legsTimer = 0; // allow legs to move us } if ( cs->castScriptStatus.scriptNoMoveTime < level.time ) { trap_EA_MoveForward( cs->entityNum ); } } return NULL; }
/* ================ AIFunc_Helga_Melee ================ */ const char *AIFunc_Helga_Melee( cast_state_t *cs ) { gentity_t *ent = &g_entities[cs->entityNum]; gentity_t *enemy; cast_state_t *ecs; int hitDelay = -1, anim; trace_t tr; float enemyDist; aicast_predictmove_t move; vec3_t vec; cs->aiFlags |= AIFL_SPECIAL_FUNC; if ( !ent->client->ps.torsoTimer || !ent->client->ps.legsTimer ) { cs->aiFlags &= ~AIFL_SPECIAL_FUNC; return AIFunc_DefaultStart( cs ); } if ( cs->enemyNum < 0 ) { ent->client->ps.legsTimer = 0; // allow legs us to move ent->client->ps.torsoTimer = 0; // allow legs us to move cs->aiFlags &= ~AIFL_SPECIAL_FUNC; return AIFunc_DefaultStart( cs ); } ecs = AICast_GetCastState( cs->enemyNum ); enemy = &g_entities[cs->enemyNum]; anim = ( ent->client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) - BG_AnimationIndexForString( "attack3", cs->entityNum ); if ( anim < 0 || anim >= NUM_HELGA_ANIMS ) { // animation interupted cs->aiFlags &= ~AIFL_SPECIAL_FUNC; return AIFunc_DefaultStart( cs ); //G_Error( "AIFunc_HelgaZombieMelee: helgaBoss using invalid or unknown attack anim" ); } if ( cs->animHitCount < MAX_HELGA_IMPACTS && helgaHitTimes[anim][cs->animHitCount] >= 0 ) { // face them VectorCopy( cs->bs->origin, vec ); vec[2] += ent->client->ps.viewheight; VectorSubtract( enemy->client->ps.origin, vec, vec ); VectorNormalize( vec ); vectoangles( vec, cs->ideal_viewangles ); cs->ideal_viewangles[PITCH] = AngleNormalize180( cs->ideal_viewangles[PITCH] ); // get hitDelay if ( !cs->animHitCount ) { hitDelay = helgaHitTimes[anim][cs->animHitCount]; } else { hitDelay = helgaHitTimes[anim][cs->animHitCount] - helgaHitTimes[anim][cs->animHitCount - 1]; } // check for inflicting damage if ( level.time - cs->weaponFireTimes[cs->weaponNum] > hitDelay ) { // do melee damage enemyDist = VectorDistance( enemy->r.currentOrigin, ent->r.currentOrigin ); enemyDist -= g_entities[cs->enemyNum].r.maxs[0]; enemyDist -= ent->r.maxs[0]; if ( enemyDist < 10 + AICast_WeaponRange( cs, cs->weaponNum ) ) { trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL, enemy->r.currentOrigin, ent->s.number, MASK_SHOT ); if ( tr.entityNum == cs->enemyNum ) { G_Damage( &g_entities[tr.entityNum], ent, ent, vec3_origin, tr.endpos, helgaHitDamage[anim], 0, MOD_GAUNTLET ); G_AddEvent( enemy, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[STAYSOUNDSCRIPT] ) ); } } cs->weaponFireTimes[cs->weaponNum] = level.time; cs->animHitCount++; } } // if they are outside range, move forward AICast_PredictMovement( ecs, 2, 0.3, &move, &g_entities[cs->enemyNum].client->pers.cmd, -1 ); VectorSubtract( move.endpos, cs->bs->origin, vec ); vec[2] = 0; enemyDist = VectorLength( vec ); enemyDist -= g_entities[cs->enemyNum].r.maxs[0]; enemyDist -= ent->r.maxs[0]; if ( enemyDist > 8 ) { // we can get closer //if (!ent->client->ps.legsTimer) { // cs->castScriptStatus.scriptNoMoveTime = 0; trap_EA_MoveForward( cs->entityNum ); //} //ent->client->ps.legsTimer = 0; // allow legs us to move } return NULL; }
/* =============== AIFunc_LoperAttack2() Loper's leaping long range attack =============== */ char *AIFunc_LoperAttack2( cast_state_t *cs ) { gentity_t *ent; vec3_t vec; qboolean onGround = qfalse; // ent = &g_entities[cs->entityNum]; // // are we waiting to inflict damage? if ( ( cs->weaponFireTimes[WP_MONSTER_ATTACK2] < level.time - 100 ) && ( cs->bs->cur_ps.groundEntityNum == ENTITYNUM_NONE ) ) { // ready to inflict damage? if ( cs->thinkFuncChangeTime < level.time - LOPER_LEAP_DELAY ) { // check for damage if ( //(tr = CheckMeleeAttack(&g_entities[cs->entityNum], LOPER_LEAP_RANGE, qtrue)) && ( G_RadiusDamage( cs->bs->origin, ent, LOPER_LEAP_DAMAGE, LOPER_LEAP_RANGE, ent, MOD_LOPER_LEAP ) ) ) { // draw the client-side lightning effect ent->client->ps.eFlags |= EF_MONSTER_EFFECT; // do the damage //G_Damage( &g_entities[tr->entityNum], ent, ent, vec3_origin, tr->endpos, // LOPER_LEAP_DAMAGE, 0, MOD_LOPER_LEAP ); G_Sound( &g_entities[cs->entityNum], level.loperZapSound ); //cs->weaponFireTimes[WP_MONSTER_ATTACK2] = level.time; // TODO: client-side visual effect // TODO: throw them backwards (away from us) } } } // // landed? if ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) { onGround = qtrue; } else { // predict a landing aicast_predictmove_t move; float changeTime; AICast_PredictMovement( cs, 1, 0.2, &move, &cs->bs->lastucmd, cs->bs->enemy ); if ( move.groundEntityNum != ENTITYNUM_NONE ) { onGround = qtrue; } // // adjust velocity VectorCopy( g_entities[cs->entityNum].s.pos.trDelta, vec ); vec[2] = 0; VectorNormalize( vec ); changeTime = 2.0 * ( 0.001 * ( level.time - cs->thinkFuncChangeTime ) ); if ( changeTime > 1.0 ) { changeTime = 1.0; } VectorScale( vec, LOPER_LEAP_VELOCITY_START + changeTime * ( LOPER_LEAP_VELOCITY_END - LOPER_LEAP_VELOCITY_START ), vec ); g_entities[cs->entityNum].s.pos.trDelta[0] = vec[0]; g_entities[cs->entityNum].s.pos.trDelta[1] = vec[1]; } // if ( onGround || ( cs->aiFlags & AIFL_LAND_ANIM_PLAYED ) ) { // if we just started the attack recently, we probably haven't had a chance to get airborne yet if ( cs->thinkFuncChangeTime < level.time - LOPER_LEAP_DELAY ) { // loper is back on ground, wait for animation to play out if ( !( cs->aiFlags & AIFL_LAND_ANIM_PLAYED ) ) { ent->client->ps.legsAnim = ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | LOPER_LAND_ANIM; ent->client->ps.legsTimer = LOPER_LAND_DURATION; // cs->aiFlags |= AIFL_LAND_ANIM_PLAYED; // TODO:play the landing thud } // if ( !ent->client->ps.legsTimer ) { // we're done return AIFunc_DefaultStart( cs ); } // keep moving slightly in our facing direction to simulate landing momentum AngleVectors( cs->bs->viewangles, vec, NULL, NULL ); trap_EA_Move( cs->entityNum, vec, ( (float)ent->client->ps.legsTimer / (float)LOPER_LAND_DURATION ) * (float)LOPER_LEAP_LAND_MOMENTUM ); return NULL; } } return NULL; }
/* ================ AIFunc_WarriorZombieMelee ================ */ char *AIFunc_WarriorZombieMelee( cast_state_t *cs ) { gentity_t *ent = &g_entities[cs->entityNum]; int hitDelay = -1, anim; trace_t *tr; cast_state_t *ecs = AICast_GetCastState( cs->enemyNum ); aicast_predictmove_t move; float enemyDist; if ( !ent->client->ps.torsoTimer ) { return AIFunc_DefaultStart( cs ); } // if ( cs->enemyNum < 0 ) { return NULL; } if ( ecs ) { anim = ( ent->client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) - BG_AnimationIndexForString( "attack1", cs->entityNum ); if ( anim < 0 || anim >= NUM_WARRIOR_ANIMS ) { // animation interupted return AIFunc_DefaultStart( cs ); } if ( warriorHitTimes[anim][cs->animHitCount] >= 0 && cs->animHitCount < 3 ) { if ( !cs->animHitCount ) { hitDelay = warriorHitTimes[anim][cs->animHitCount]; } else { hitDelay = warriorHitTimes[anim][cs->animHitCount] - warriorHitTimes[anim][cs->animHitCount - 1]; } // check for inflicting damage if ( level.time - cs->weaponFireTimes[cs->weaponNum] > hitDelay ) { // do melee damage if ( ( tr = CheckMeleeAttack( ent, 44, qfalse ) ) && ( tr->entityNum == cs->enemyNum ) ) { G_Damage( &g_entities[tr->entityNum], ent, ent, vec3_origin, tr->endpos, warriorHitDamage[anim], 0, MOD_GAUNTLET ); G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[STAYSOUNDSCRIPT] ) ); } else { G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[FOLLOWSOUNDSCRIPT] ) ); } cs->weaponFireTimes[cs->weaponNum] = level.time; cs->animHitCount++; } } // face them AICast_AimAtEnemy( cs ); if ( anim < 3 ) { // back handed-swinging, dont allow legs to move // if they are outside range, move forward AICast_PredictMovement( ecs, 2, 0.5, &move, &g_entities[cs->enemyNum].client->pers.cmd, -1 ); enemyDist = Distance( move.endpos, cs->bs->origin ); enemyDist -= g_entities[cs->enemyNum].r.maxs[0]; enemyDist -= ent->r.maxs[0]; if ( enemyDist > 16 ) { // we can get closer if ( ent->client->ps.legsTimer ) { ent->client->ps.legsTimer = 0; // allow legs us to move if ( cs->castScriptStatus.scriptNoMoveTime < level.time + 200 ) { // dont move until the legs are done lerping out of attack anim cs->castScriptStatus.scriptNoMoveTime = level.time + 200; } } if ( cs->castScriptStatus.scriptNoMoveTime < level.time ) { trap_EA_MoveForward( cs->entityNum ); } } } } return NULL; }
/* =============== AIFunc_LoperAttack2() Loper's leaping long range attack =============== */ char *AIFunc_LoperAttack2( cast_state_t *cs ) { gentity_t *ent; vec3_t vec; qboolean onGround = qfalse; // ent = &g_entities[cs->entityNum]; // // are we waiting to inflict damage? if ( ( cs->enemyNum >= 0 ) && ( cs->weaponFireTimes[WP_MONSTER_ATTACK2] < level.time - 50 ) && ( cs->bs->cur_ps.groundEntityNum == ENTITYNUM_NONE ) ) { // ready to inflict damage? if ( cs->thinkFuncChangeTime < level.time - LOPER_LEAP_DELAY ) { // check for damage if ( VectorDistance( cs->bs->origin, g_entities[cs->enemyNum].client->ps.origin ) < LOPER_LEAP_RANGE ) { // draw the client-side lightning effect ent->client->ps.eFlags |= EF_MONSTER_EFFECT; // do the damage G_Damage( &g_entities[cs->enemyNum], ent, ent, vec3_origin, cs->bs->origin, LOPER_LEAP_DAMAGE, 0, MOD_LOPER_LEAP ); G_Sound( &g_entities[cs->entityNum], level.loperZapSound ); cs->weaponFireTimes[WP_MONSTER_ATTACK2] = level.time; } } } // // landed? if ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) { onGround = qtrue; } else { // predict a landing aicast_predictmove_t move; float changeTime; AICast_PredictMovement( cs, 1, 0.2, &move, &cs->lastucmd, cs->enemyNum ); if ( move.groundEntityNum != ENTITYNUM_NONE ) { onGround = qtrue; } // // adjust velocity VectorCopy( cs->loperLeapVel, vec ); vec[2] = 0; VectorNormalize( vec ); changeTime = 2.0 * ( 0.001 * ( level.time - cs->thinkFuncChangeTime ) ); if ( changeTime > 1.0 ) { changeTime = 1.0; } VectorScale( vec, LOPER_LEAP_VELOCITY_START + changeTime * ( LOPER_LEAP_VELOCITY_END - LOPER_LEAP_VELOCITY_START ), vec ); g_entities[cs->entityNum].s.pos.trDelta[0] = vec[0]; g_entities[cs->entityNum].s.pos.trDelta[1] = vec[1]; } // if ( onGround || ( cs->aiFlags & AIFL_LAND_ANIM_PLAYED ) ) { // if we just started the attack recently, we probably haven't had a chance to get airborne yet if ( cs->thinkFuncChangeTime < level.time - LOPER_LEAP_DELAY ) { // loper is back on ground, wait for animation to play out if ( !( cs->aiFlags & AIFL_LAND_ANIM_PLAYED ) ) { BG_PlayAnimName( &ent->client->ps, "legs_extra4", ANIM_BP_LEGS, qtrue, qfalse, qtrue ); // cs->aiFlags |= AIFL_LAND_ANIM_PLAYED; // TODO:play the landing thud } // if ( ent->client->ps.legsTimer < 800 ) { // we're done ent->client->ps.legsTimer = 0; return AIFunc_DefaultStart( cs ); } // keep moving slightly in our facing direction to simulate landing momentum AngleVectors( cs->viewangles, vec, NULL, NULL ); trap_EA_Move( cs->entityNum, vec, ( (float)ent->client->ps.legsTimer / (float)LOPER_LAND_DURATION ) * (float)LOPER_LEAP_LAND_MOMENTUM ); return NULL; } } ent->client->ps.legsTimer = 500; // stay on this until landing return NULL; }