/*
================
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_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->weaponNum] < level.time - 100 ) {
			return AIFunc_DefaultStart( cs );
		}
		return NULL;
	}

	if ( ( cs->enemyNum < 0 ) || ( cs->dangerEntityValidTime >= level.time ) ) {
		ent->flags &= ~FL_DEFENSE_GUARD;
		ent->client->ps.torsoTimer = 0;
		ent->client->ps.legsTimer = 0;
		return NULL;
	}

	enemy = &g_entities[cs->enemyNum];

	if ( cs->thinkFuncChangeTime < level.time - 1500 ) {
		// if we cant see them
		if ( !AICast_EntityVisible( cs, cs->enemyNum, 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_CLASS_SPECIAL ) {
			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->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;
}
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->enemyNum, 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->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;
}
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;
}