char *AIFunc_ZombieAttack2( cast_state_t *cs ) { gentity_t *ent; ent = &g_entities[cs->entityNum]; if ( cs->enemyNum < 0 ) { return AIFunc_DefaultStart( cs ); } // if we can't see them anymore, abort immediately if ( cs->vislist[cs->enemyNum].real_visible_timestamp != cs->vislist[cs->enemyNum].real_update_timestamp ) { return AIFunc_DefaultStart( cs ); } // lastZombieSpiritAttack = level.time; // we are firing this weapon, so record it cs->weaponFireTimes[WP_MONSTER_ATTACK2] = level.time; // once an attack has started, only abort once the player leaves our view, or time runs out if ( cs->thinkFuncChangeTime < level.time - ZOMBIE_SPIRIT_BUILDUP_TIME ) { // if enough time has elapsed, finish this attack if ( level.time > cs->thinkFuncChangeTime + ZOMBIE_SPIRIT_BUILDUP_TIME + ZOMBIE_SPIRIT_FADEOUT_TIME ) { return AIFunc_DefaultStart( cs ); } } else { // draw the client-side effect ent->client->ps.eFlags |= EF_MONSTER_EFFECT; // inform the client of our enemies position VectorCopy( g_entities[cs->enemyNum].client->ps.origin, ent->s.origin2 ); ent->s.origin2[2] += g_entities[cs->enemyNum].client->ps.viewheight; } return NULL; }
char *AIFunc_Heinrich_RaiseDead(cast_state_t *cs) { int i; gentity_t *ent = &g_entities[cs->entityNum]; gentity_t *enemy = &g_entities[cs->enemyNum]; gentity_t *trav, *closest; float closestDist, dist; cs->aiFlags |= AIFL_SPECIAL_FUNC; if (cs->enemyNum < 0) { if (!ent->client->ps.torsoTimer) { return AIFunc_DefaultStart(cs); } return NULL; } // record weapon fire cs->weaponFireTimes[cs->weaponNum] = level.time; if (!ent->client->ps.torsoTimer) { return AIFunc_DefaultStart(cs); } if (ent->count2 && lastRaise < level.time - HEINRICH_RAISEDEAD_DELAY) { lastRaise = level.time; // summons the closest warrior closest = NULL; closestDist = 0; // shutup the compiler for (i = 0, trav = g_entities; i < level.maxclients; i++, trav++) { if (!trav->inuse) { continue; } if (!trav->aiInactive) { continue; } if (trav->aiCharacter != AICHAR_WARZOMBIE) { continue; } dist = VectorDistance(trav->s.pos.trBase, enemy->r.currentOrigin); if (!closest || dist < closestDist) { closest = trav; closestDist = dist; } } if (closest) { closest->AIScript_AlertEntity(closest); // make them aware of the player AICast_UpdateVisibility(closest, enemy, qtrue, qtrue); // reduce the count ent->count2--; } } return NULL; }
char *AIFunc_ZombieFlameAttack( cast_state_t *cs ) { gentity_t *ent; // ent = &g_entities[cs->entityNum]; // ent->s.onFireEnd = level.time + 2000; // if ( ent->health < 0 ) { ent->s.onFireEnd = 0; return AIFunc_DefaultStart( cs ); } // if ( cs->bs->enemy < 0 ) { ent->s.onFireEnd = level.time + 1500; ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return AIFunc_DefaultStart( cs ); } // if outside range, move closer if ( VectorDistance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) > ZOMBIE_FLAME_RADIUS ) { ent->s.onFireEnd = level.time + 1500; ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return AIFunc_DefaultStart( cs ); } // we are firing this weapon, so record it cs->weaponFireTimes[WP_MONSTER_ATTACK1] = level.time; // once an attack has started, only abort once the player leaves our view, or time runs out if ( cs->thinkFuncChangeTime < level.time - ZOMBIE_FLAME_DURATION ) { // finish this attack ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return AIFunc_DefaultStart( cs ); } else { ent->client->ps.torsoTimer = 400; //ent->client->ps.legsTimer = 400; // draw the client-side effect ent->client->ps.eFlags |= EF_MONSTER_EFFECT3; // keep facing them AICast_AimAtEnemy( cs ); // look slightly downwards since animation is facing upwards slightly cs->bs->ideal_viewangles[PITCH] += 10; } // // return NULL; }
const char *AIFunc_Helga_SpiritAttack( cast_state_t *cs ) { bot_state_t *bs; gentity_t *ent; // cs->aiFlags |= AIFL_SPECIAL_FUNC; ent = &g_entities[cs->entityNum]; bs = cs->bs; // make sure we're still playing the right anim if ( ( ent->client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) - BG_AnimationIndexForString( "attack1", cs->entityNum ) ) { return AIFunc_DefaultStart( cs ); } // if ( cs->enemyNum < 0 ) { ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return AIFunc_DefaultStart( cs ); } // // if we can't see them anymore, abort immediately if ( cs->vislist[cs->enemyNum].real_visible_timestamp != cs->vislist[cs->enemyNum].real_update_timestamp ) { ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return AIFunc_DefaultStart( cs ); } // we are firing this weapon, so record it cs->weaponFireTimes[WP_MONSTER_ATTACK2] = level.time; // // once an attack has started, only abort once the player leaves our view, or time runs out if ( cs->thinkFuncChangeTime < level.time - HELGA_SPIRIT_BUILDUP_TIME ) { // if enough time has elapsed, finish this attack if ( level.time > cs->thinkFuncChangeTime + HELGA_SPIRIT_BUILDUP_TIME + HELGA_SPIRIT_FADEOUT_TIME ) { ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return AIFunc_DefaultStart( cs ); } } else { // set timers ent->client->ps.torsoTimer = 1000; ent->client->ps.legsTimer = 1000; // draw the client-side effect ent->client->ps.eFlags |= EF_MONSTER_EFFECT; // inform the client of our enemies position VectorCopy( g_entities[cs->enemyNum].client->ps.origin, ent->s.origin2 ); ent->s.origin2[2] += g_entities[cs->enemyNum].client->ps.viewheight; } // // 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; if ( !ent->client->ps.torsoTimer ) { return AIFunc_DefaultStart( cs ); } 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 ); //G_Error( "AIFunc_WarriorZombieMelee: warrior using invalid or unknown attack anim" ); } if ( warriorHitTimes[anim][cs->animHitCount] >= 0 ) { // face them AICast_AimAtEnemy( cs ); 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->bs->weaponnum] > hitDelay ) { // do melee damage if ( ( tr = CheckMeleeAttack( ent, 48, qfalse ) ) && ( tr->entityNum == cs->bs->enemy ) ) { 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].staySoundScript ) ); cs->weaponFireTimes[cs->bs->weaponnum] = level.time; cs->animHitCount++; } else { // if they are outside range, move forward if ( anim != 4 && !CheckMeleeAttack( ent, 48, qfalse ) ) { //ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; // allow legs us to move //return AIFunc_DefaultStart(cs); trap_EA_MoveForward( cs->entityNum ); } } } return NULL; }
/* ============ AICast_ProcessAIFunctions ============ */ void AICast_ProcessAIFunctions( cast_state_t *cs, float thinktime ) { int i; const char *funcname; //check for air BotCheckAir( cs->bs ); //if the cast has no ai function if ( !cs->aifunc ) { AIFunc_DefaultStart( cs ); } // // call AI funcs for this cast // AICast_DBG_InitAIFuncs(); // // only allow looping in debug mode (since it's much slower) for ( i = 0; i < ( aicast_debug.integer ? MAX_AIFUNCS : 1 ); i++ ) { if ( !( funcname = cs->aifunc( cs ) ) ) { break; } else { trap_BotResetAvoidReach( cs->bs->ms ); // reset avoidreach cs->thinkFuncChangeTime = level.time; AICast_DBG_AddAIFunc( cs, funcname ); } } // //if the cast executed too many AI functions // if ( aicast_debug.integer && i >= MAX_AIFUNCS ) { AICast_DBG_ListAIFuncs( cs, 10 ); // print the last 10 funcs called } }
char *AIFunc_BlackGuardAttack1( cast_state_t *cs ) { gentity_t *ent = &g_entities[cs->entityNum]; trace_t *tr; vec3_t fwd; if ( !ent->client->ps.legsTimer ) { return AIFunc_DefaultStart( cs ); } // if ( cs->enemyNum < 0 ) { return NULL; } // time for the melee? if ( !( cs->aiFlags & AIFL_MISCFLAG1 ) ) { // face them AICast_AimAtEnemy( cs ); // ready for damage? if ( cs->thinkFuncChangeTime < level.time - BLACKGUARD_KICK_DELAY ) { cs->aiFlags |= AIFL_MISCFLAG1; // keep checking for impact status tr = CheckMeleeAttack( ent, BLACKGUARD_KICK_RANGE, qfalse ); // do melee damage? if ( tr && ( tr->entityNum == cs->enemyNum ) ) { AngleVectors( cs->viewangles, fwd, NULL, NULL ); G_Damage( &g_entities[tr->entityNum], ent, ent, fwd, tr->endpos, BLACKGUARD_KICK_DAMAGE, 0, MOD_GAUNTLET ); // throw them in direction of impact fwd[2] = 0.5; VectorMA( g_entities[cs->enemyNum].client->ps.velocity, 300, fwd, g_entities[cs->enemyNum].client->ps.velocity ); } } } return NULL; }
char *AIFunc_LoperAttack2Start( cast_state_t *cs ) { gentity_t *ent; vec3_t vec, avec; // ent = &g_entities[cs->entityNum]; // if ( cs->enemyNum < 0 ) { return AIFunc_DefaultStart( cs ); } // face them AICast_AimAtEnemy( cs ); // if not facing them yet, wait VectorSubtract( g_entities[cs->enemyNum].client->ps.origin, cs->bs->origin, vec ); VectorNormalize( vec ); AngleVectors( cs->viewangles, avec, NULL, NULL ); if ( DotProduct( vec, avec ) < 0.9 ) { //cs->aifunc = AIFunc_LoperAttack2Start; return NULL; } // OK, start the animation BG_PlayAnimName( &ent->client->ps, "legs_extra3", ANIM_BP_LEGS, qtrue, qfalse, qtrue ); ent->client->ps.legsTimer = 500; // stay on this until landing // send us hurtling towards our enemy VectorScale( vec, LOPER_LEAP_VELOCITY_START, vec ); vec[2] = LOPER_LEAP_VELOCITY_Z; VectorCopy( vec, ent->client->ps.velocity ); VectorCopy( vec, cs->loperLeapVel ); // cs->aiFlags &= ~AIFL_LAND_ANIM_PLAYED; // play the sound // TODO // cs->aifunc = AIFunc_LoperAttack2; return "AIFunc_LoperAttack2"; }
/* =============== AIFunc_LoperAttack1() Loper's close range melee attack =============== */ char *AIFunc_LoperAttack1( cast_state_t *cs ) { trace_t *tr; gentity_t *ent; // ent = &g_entities[cs->entityNum]; // // draw the client-side lightning effect //ent->client->ps.eFlags |= EF_MONSTER_EFFECT; // // have we inflicted the damage? if ( cs->weaponFireTimes[WP_MONSTER_ATTACK1] > cs->thinkFuncChangeTime ) { // has the animation finished? if ( cs->thinkFuncChangeTime < level.time - LOPER_MELEE_DURATION ) { return AIFunc_DefaultStart( cs ); } return NULL; // just wait for anim to finish } // ready to inflict damage? if ( cs->thinkFuncChangeTime < level.time - LOPER_MELEE_DAMAGE_DELAY ) { // check for damage // TTimo: assignment used as truth value if ( ( tr = CheckMeleeAttack( &g_entities[cs->entityNum], LOPER_MELEE_RANGE, qfalse ) ) ) { G_Damage( &g_entities[tr->entityNum], ent, ent, vec3_origin, tr->endpos, LOPER_MELEE_DAMAGE, 0, MOD_LOPER_HIT ); } cs->weaponFireTimes[WP_MONSTER_ATTACK1] = level.time; } return NULL; }
/* ================ AIFunc_WarriorZombieSight ================ */ char *AIFunc_WarriorZombieSight( cast_state_t *cs ) { gentity_t *ent = &g_entities[cs->entityNum]; if ( !ent->client->ps.torsoTimer ) { return AIFunc_DefaultStart( cs ); } return NULL; }
/* =============== AIFunc_LoperAttack3() Loper's ground electrical attack =============== */ char *AIFunc_LoperAttack3( cast_state_t *cs ) { gentity_t *ent; qboolean hitClient = qfalse; // ent = &g_entities[cs->entityNum]; // // done with this attack? if ( cs->thinkFuncChangeTime < level.time - LOPER_GROUND_DELAY ) { cs->pauseTime = level.time + 600; // don't move until effect is done ent->client->ps.legsTimer = 600; // stay down until effect is done return AIFunc_DefaultStart( cs ); } // ready to inflict damage? if ( cs->thinkFuncChangeTime < level.time - 900 ) { // // draw the client-side lightning effect ent->client->ps.eFlags |= EF_MONSTER_EFFECT3; //ent->s.effect3Time = level.time + 500;//cs->thinkFuncChangeTime + LOPER_GROUND_DELAY - 200; // // are we waiting to inflict damage? if ( cs->weaponFireTimes[WP_MONSTER_ATTACK3] < level.time - 100 ) { // check for damage hitClient = G_RadiusDamage( cs->bs->origin, ent, LOPER_GROUND_DAMAGE, LOPER_GROUND_RANGE, ent, MOD_LOPER_GROUND ); // cs->weaponFireTimes[WP_MONSTER_ATTACK3] = level.time; // TODO: client-side visual effect // TODO: throw them backwards (away from us) } else { hitClient = qtrue; // so we don't abort } // if ( !hitClient && cs->thinkFuncChangeTime < ( level.time - 1500 ) ) { // we're done with this attack cs->pauseTime = level.time + 600; // don't move until effect is done ent->client->ps.legsTimer = 600; // stay down until effect is done return AIFunc_DefaultStart( cs ); } } // if ( ent->client->ps.legsTimer < 1000 ) { ent->client->ps.legsTimer = 1000; // stay down until effect is done } return NULL; }
/* ============== AIFunc_FlameZombie_Portal ============== */ const char *AIFunc_FlameZombie_Portal( cast_state_t *cs ) { gentity_t *ent = &g_entities[cs->entityNum]; // if ( cs->thinkFuncChangeTime < level.time - PORTAL_ZOMBIE_SPAWNTIME ) { // HACK, make them aware of the player AICast_UpdateVisibility( &g_entities[cs->entityNum], AICast_FindEntityForName( "player" ), qfalse, qtrue ); ent->s.time2 = 0; // turn spawning effect off return AIFunc_DefaultStart( cs ); } // return NULL; }
/* =============== AIFunc_LoperAttack1() Loper's close range melee attack =============== */ char *AIFunc_LoperAttack1( cast_state_t *cs ) { trace_t *tr; gentity_t *ent; int anim; // ent = &g_entities[cs->entityNum]; // // draw the client-side lightning effect //ent->client->ps.eFlags |= EF_MONSTER_EFFECT; // // have we inflicted the damage? if ( cs->weaponFireTimes[WP_MONSTER_ATTACK1] > cs->thinkFuncChangeTime ) { // has the animation finished? if ( !ent->client->ps.legsTimer ) { return AIFunc_DefaultStart( cs ); } return NULL; // just wait for anim to finish } // ready to inflict damage? anim = ( ent->client->ps.legsAnim & ~ANIM_TOGGLEBIT ) - BG_AnimationIndexForString( "legs_extra", cs->entityNum ); if ( cs->thinkFuncChangeTime < level.time - loperHitTimes[anim] ) { // check for damage // TTimo: gcc: suggests () around assignment used as truth value if ( ( tr = CheckMeleeAttack( &g_entities[cs->entityNum], LOPER_MELEE_RANGE, qfalse ) ) ) { G_Damage( &g_entities[tr->entityNum], ent, ent, vec3_origin, tr->endpos, LOPER_MELEE_DAMAGE, 0, MOD_LOPER_HIT ); // sound if ( anim == 0 ) { G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[ORDERSDENYSOUNDSCRIPT] ) ); } else { G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[MISC1SOUNDSCRIPT] ) ); } } cs->weaponFireTimes[WP_MONSTER_ATTACK1] = level.time; } 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_WarriorZombieDefense ================ */ char *AIFunc_WarriorZombieDefense( cast_state_t *cs ) { gentity_t *ent, *enemy; vec3_t enemyDir, vec; float dist; ent = &g_entities[cs->entityNum]; if ( !( ent->flags & FL_DEFENSE_GUARD ) ) { if ( cs->weaponFireTimes[cs->bs->weaponnum] < level.time - 100 ) { return AIFunc_DefaultStart( cs ); } return NULL; } if ( cs->bs->enemy < 0 ) { ent->flags &= ~FL_DEFENSE_GUARD; ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return NULL; } enemy = &g_entities[cs->bs->enemy]; if ( cs->thinkFuncChangeTime < level.time - 1500 ) { // if we cant see them if ( !AICast_EntityVisible( cs, cs->bs->enemy, qtrue ) ) { ent->flags &= ~FL_DEFENSE_GUARD; ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return NULL; } // if our enemy isn't using a dangerous weapon if ( enemy->client->ps.weapon < WP_LUGER || enemy->client->ps.weapon > WP_CROSS ) { ent->flags &= ~FL_DEFENSE_GUARD; ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return NULL; } // if our enemy isn't looking right at us, abort VectorSubtract( ent->client->ps.origin, enemy->client->ps.origin, vec ); dist = VectorNormalize( vec ); if ( dist > 512 ) { dist = 512; } AngleVectors( enemy->client->ps.viewangles, enemyDir, NULL, NULL ); if ( DotProduct( vec, enemyDir ) < ( 0.98 - 0.2 * ( dist / 512 ) ) ) { ent->flags &= ~FL_DEFENSE_GUARD; ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return NULL; } } cs->weaponFireTimes[cs->bs->weaponnum] = level.time; if ( !ent->client->ps.torsoTimer ) { ent->flags &= ~FL_DEFENSE_GUARD; ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return NULL; } // face them AICast_AimAtEnemy( cs ); // crouching position, use smaller bounding box trap_EA_Crouch( cs->bs->client ); 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; }
/* ================ 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; }
char *AIFunc_Heinrich_SwordKnockback(cast_state_t *cs) { gentity_t *ent = &g_entities[cs->entityNum]; trace_t *tr; vec3_t right, left; cs->aiFlags |= AIFL_SPECIAL_FUNC; if (cs->enemyNum < 0) { if (ent->client->ps.torsoTimer) { return NULL; } return AIFunc_DefaultStart(cs); } 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_KNOCKBACK_RANGE, qfalse); /* // do we need to move? if (!(tr && (tr->entityNum == cs->enemyNum))) { ent->client->ps.legsTimer = 0; cs->castScriptStatus.scriptNoMoveTime = 0; trap_EA_MoveForward(cs->entityNum); } */ // ready for damage? if (cs->thinkFuncChangeTime < level.time - HEINRICH_KNOCKBACK_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_KNOCKBACK_DAMAGE, 0, MOD_GAUNTLET); // sound G_AddEvent(ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_SWORDIMPACT]); // throw them in direction of impact if ((ent->client->ps.torsoAnim & ~ANIM_TOGGLEBIT) == BG_AnimationIndexForString("attack2", cs->entityNum)) { // right right[2] = 0.5; VectorMA(g_entities[cs->enemyNum].client->ps.velocity, 400, right, g_entities[cs->enemyNum].client->ps.velocity); } else { // left left[2] = 0.5; VectorMA(g_entities[cs->enemyNum].client->ps.velocity, 400, left, g_entities[cs->enemyNum].client->ps.velocity); } } } } return NULL; }
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_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; }
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; }
char *AIFunc_StimSoldierAttack1( cast_state_t *cs ) { gentity_t *ent; vec3_t vec; static vec3_t up = {0,0,1}; // ent = &g_entities[cs->entityNum]; cs->weaponFireTimes[WP_MONSTER_ATTACK1] = level.time; // face them AICast_AimAtEnemy( cs ); // // are we done with this attack? if ( cs->thinkFuncChangeTime < level.time - STIMSOLDIER_FLYJUMP_DELAY ) { // have we hit the ground yet? if ( ent->s.groundEntityNum != ENTITYNUM_NONE ) { // we are on something, have we started the landing animation? if ( !( cs->aiFlags & AIFL_LAND_ANIM_PLAYED ) ) { ent->client->ps.legsAnim = ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | STIMSOLDIER_FLYLAND_ANIM; ent->client->ps.legsTimer = STIMSOLDIER_FLYLAND_DURATION; // stay down until attack is finished // cs->noAttackTime = level.time + STIMSOLDIER_FLYLAND_DURATION; cs->aiFlags |= AIFL_LAND_ANIM_PLAYED; } else { if ( !ent->client->ps.legsTimer ) { // animation has finished, resume AI return AIFunc_DefaultStart( cs ); } } } else { // still flying } return NULL; } // // are we ready to start flying? if ( cs->thinkFuncChangeTime < ( level.time - STIMSOLDIER_STARTJUMP_DELAY ) ) { if ( !ent->client->ps.powerups[PW_FLIGHT] ) { // play a special ignition sound? } ent->client->ps.powerups[PW_FLIGHT] = 1; // let them fly ent->s.loopSound = level.stimSoldierFlySound; ent->client->ps.eFlags |= EF_MONSTER_EFFECT; // client-side stim engine effect if ( ent->s.effect1Time != ( cs->thinkFuncChangeTime + STIMSOLDIER_STARTJUMP_DELAY ) ) { ent->s.effect1Time = ( cs->thinkFuncChangeTime + STIMSOLDIER_STARTJUMP_DELAY ); // start the hovering animation ent->client->ps.legsAnim = ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | STIMSOLDIER_FLYHOVER_ANIM; } // give us some upwards velocity? if ( cs->thinkFuncChangeTime > level.time - STIMSOLDIER_FLYJUMP_DURATION * 0.9 ) { trap_EA_Move( cs->entityNum, up, 300 ); //trap_EA_Jump(cs->entityNum); VectorCopy( cs->bs->origin, cs->stimFlyAttackPos ); } else { // attack them // // if we can't attack, abort if ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) ) { // apply weapons.. trap_EA_Attack( cs->entityNum ); } // we're done here cs->thinkFuncChangeTime = -9999; } } else { // still on ground, so move forward to account for stepping animation AngleVectors( cs->bs->viewangles, vec, NULL, NULL ); trap_EA_Move( cs->entityNum, vec, 300 ); } // if ( ent->client->ps.legsTimer < 1000 ) { ent->client->ps.legsTimer = 1000; // stay down until effect is done } // return NULL; }
/* ============ AICast_CreateCharacter returns 0 if unable to create the character ============ */ gentity_t *AICast_CreateCharacter( gentity_t *ent, float *attributes, cast_weapon_info_t *weaponInfo, char *castname, char *model, char *head, char *sex, char *color, char *handicap ) { gentity_t *newent; gclient_t *client; cast_state_t *cs; char **ppStr; int j; if (g_gametype.integer != GT_SINGLE_PLAYER) { // no cast AI in multiplayer return NULL; } // are bots enabled? if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { G_Printf( S_COLOR_RED "ERROR: Unable to spawn %s, 'bot_enable' is not set\n", ent->classname ); return NULL; } // // make sure we have a free slot for them // if (level.numPlayingClients+1 > aicast_maxclients) { G_Error( "Exceeded sv_maxclients (%d), unable to create %s\n", aicast_maxclients, ent->classname ); return NULL; } // // add it to the list (only do this if everything else passed) // newent = AICast_AddCastToGame( ent, castname, model, head, sex, color, handicap ); if (!newent) { return NULL; } client = newent->client; // // setup the character.. // cs = AICast_GetCastState( newent->s.number ); // // setup the attributes memcpy( cs->attributes, attributes, sizeof(cs->attributes) ); ppStr = &ent->aiAttributes; AICast_CheckLevelAttributes( cs, ent, ppStr ); // AICast_SetAASIndex( cs ); // make sure they face the right direction VectorCopy( ent->s.angles, cs->bs->ideal_viewangles ); // factor in the delta_angles for (j = 0; j < 3; j++) { cs->bs->viewangles[j] = AngleMod(newent->s.angles[j] - SHORT2ANGLE(newent->client->ps.delta_angles[j])); } VectorCopy( ent->s.angles, newent->s.angles ); VectorCopy( ent->s.origin, cs->startOrigin ); // cs->lastEnemy = -1; cs->bs->enemy = -1; cs->leaderNum = -1; cs->castScriptStatus.scriptGotoEnt = -1; cs->aiCharacter = ent->aiCharacter; // newent->aiName = ent->aiName; newent->aiTeam = ent->aiTeam; newent->targetname = ent->targetname; // newent->AIScript_AlertEntity = ent->AIScript_AlertEntity; newent->aiInactive = ent->aiInactive; newent->aiCharacter = cs->aiCharacter; // // parse the AI script for this character (if applicable) cs->aiFlags |= AIFL_CORPSESIGHTING; // this is on by default for all characters, disabled if they have a "friendlysightcorpse" script event AICast_ScriptParse( cs ); // // setup bounding boxes //VectorCopy( mins, client->ps.mins ); //VectorCopy( maxs, client->ps.maxs ); AIChar_SetBBox( newent, cs ); client->ps.friction = cs->attributes[RUNNING_SPEED]/300.0; // // clear weapons/ammo client->ps.weapon = 0; memcpy( client->ps.weapons, weaponInfo->startingWeapons, sizeof(weaponInfo->startingWeapons) ); memcpy( client->ps.ammo, weaponInfo->startingAmmo, sizeof(client->ps.ammo) ); // // starting health if (ent->health) { newent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = ent->health; } else { newent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = cs->attributes[STARTING_HEALTH]; } // cs->weaponInfo = weaponInfo; // cs->lastThink = level.time; // newent->pain = AICast_Pain; newent->die = AICast_Die; // //update the attack inventory values AICast_UpdateBattleInventory(cs, cs->bs->enemy); //----(SA) make sure all clips are loaded so we don't hear everyone loading up // (we don't want to do this inside AICast_UpdateBattleInventory(), only on spawn or giveweapon) for (j=0; j<MAX_WEAPONS; j++) { Fill_Clip (&client->ps, j); } //----(SA) end // select a weapon AICast_ChooseWeapon( cs, qfalse ); // // set the default function, overwrite if necessary cs->aiFlags |= AIFL_JUST_SPAWNED; AIFunc_DefaultStart( cs ); // numcast++; // return newent; }
char *AIFunc_ZombieFlameAttack( cast_state_t *cs ) { bot_state_t *bs; gentity_t *ent; // ent = &g_entities[cs->entityNum]; bs = cs->bs; // ent->s.onFireEnd = level.time + 2000; // if ( ent->health < 0 ) { ent->s.onFireEnd = 0; return AIFunc_DefaultStart( cs ); } // if ( cs->enemyNum < 0 ) { ent->s.onFireEnd = level.time + 1500; ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return AIFunc_DefaultStart( cs ); } /* disabled, keep going so they cant come back for the easy kill // // if we can't see them anymore, abort immediately if (cs->vislist[cs->enemyNum].real_visible_timestamp != cs->vislist[cs->enemyNum].real_update_timestamp) { ent->s.onFireEnd = level.time + 1500; ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return AIFunc_DefaultStart( cs ); } */ // if outside range, move closer if ( VectorDistance( cs->bs->origin, cs->vislist[cs->enemyNum].visible_pos ) > ZOMBIE_FLAME_RADIUS ) { ent->s.onFireEnd = level.time + 1500; ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return AIFunc_DefaultStart( cs ); } // we are firing this weapon, so record it cs->weaponFireTimes[WP_MONSTER_ATTACK1] = level.time; // once an attack has started, only abort once the player leaves our view, or time runs out if ( cs->thinkFuncChangeTime < level.time - ZOMBIE_FLAME_DURATION ) { // finish this attack ent->client->ps.torsoTimer = 0; ent->client->ps.legsTimer = 0; return AIFunc_DefaultStart( cs ); } else { //ent->client->ps.torsoTimer = 400; //ent->client->ps.legsTimer = 400; // draw the client-side effect ent->client->ps.eFlags |= EF_MONSTER_EFFECT3; // inform the client of our enemies position //VectorCopy( g_entities[cs->enemyNum].client->ps.origin, ent->s.origin2 ); //ent->s.origin2[2] += g_entities[cs->enemyNum].client->ps.viewheight; // keep facing them AICast_AimAtEnemy( cs ); // look slightly downwards since animation is facing upwards slightly cs->ideal_viewangles[PITCH] += 20; } // // return NULL; }