//---------------------------------------------------------------------------------------------------------------------------------------------------
//
// Copyright (C)2007 DarkWynter Studios. All rights reserved.
//
//---------------------------------------------------------------------------------------------------------------------------------------------------
// {License Information: Creative Commons}
//---------------------------------------------------------------------------------------------------------------------------------------------------
namespace ElementalGame
{
#region Using Statements
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using System.Xml;
using System.Diagnostics;
#endregion
//AI State
public enum AIState { IDLE, GOTO, ATTACK, START_EVADE, EVADE, SEARCH_MODE};
public enum AIAttackState { PURSUE, DESTROY, NONE };
public class AI : Player
{
Random rand = new Random();
public const int ATTACK_RADIUS = 100;
public const float HYSTERESIS_AMOUNT = 5.0f; //range
public const int BOT_SPAWN_HEIGHT = 500;
public const int LOWHEALTH = 50;
public const int LOWMANNA = 50;
private const int MAX_SEARCH_SECONDS = 5;
private const int JUMP_DELAY = 1;
private enum AmountType { LOW, HIGH, NEUTRAL };
private class PerceptInterface
{
public Mass.TerrainType hitTerrain;
public Mass.ObjectType upcomingTerrain;
public bool hitLeftByParticle;
public bool hitRightByParticle;
public List playersInView = new List();
public AmountType mannaAmount;
public AmountType healthAmount;
public void init()
{
hitTerrain = Mass.TerrainType.NONE;
hitLeftByParticle = false;
hitRightByParticle = false;
playersInView.Clear();
mannaAmount = AmountType.NEUTRAL;
healthAmount = AmountType.NEUTRAL;
upcomingTerrain = Mass.ObjectType.NONE;
}
};
// Stimuli for the AI to react to
PerceptInterface percept = new PerceptInterface();
// Number between 1 and 10 for smartness (10 being super smart)
public int intelligence = 8;
//Actuator properties
private enum TurnDirection
{
LEFT, RIGHT, NOT_SET
};
TurnDirection turnDirection = TurnDirection.NOT_SET;
AIState aiState = new AIState();
AIAttackState aiAttackState = new AIAttackState();
Vector3 randomDestination = new Vector3();
private Stopwatch searchTimer = new Stopwatch();
private Stopwatch jumpTimer = null;
private Player playerToAttack = null;
private Player playerToAvoid = null;
public static int AI_MAX_HEALTH = 100;
public AI(int playerNumber)
: base(playerNumber, ObjectLibrary.darkMatter[ObjectLibrary.darkMatterIterator++])
{
mass.objectType = Mass.ObjectType.PLAYER;
mass.gameObjectPointer = this;
}
public new void SpawnPlayer()
{
base.SpawnPlayer();
health = AI_MAX_HEALTH;
}
public override void Load(XmlNode node)
{
//base.LoadGame(graphics, content);
health = AI_MAX_HEALTH;
manna = 100;
base.Load(node);
characterType = CharacterType.SEPTASOUL;
if (GameFlow.gameType == GameFlow.GameType.SURVIVAL)
{
// Enters into search mode to see who shot him
aiState = AIState.SEARCH_MODE;
// Only search for a few seconds..
searchTimer = Stopwatch.StartNew();
}
else
{
aiState = AIState.IDLE;
}
aiAttackState = AIAttackState.NONE;
//relies on the x,z being set somewhere else..
SetSpawnPoint(Vector3.Zero);
}
private float AngleToDirection(Vector3 direction)
{
Vector2 diff2 = new Vector2(direction.X, direction.Z);
diff2.Normalize();
Vector2 N = new Vector2(mass.normalVector.X, mass.normalVector.Z);
N.Normalize();
return (float)Math.Acos((double)(Vector2.Dot(diff2, N)));
}
private TurnDirection DirectionToOrientation(Vector3 direction)
{
// Determine which direction to turn
Vector3 toDirection = new Vector3(direction.X, 0, direction.Z);
toDirection.Normalize();
Vector3 lookAt = new Vector3(mass.normalVector.X, 0, mass.normalVector.Z);
lookAt.Normalize();
Vector3 cross = Vector3.Cross(toDirection, lookAt);
if (cross.Y < 0.2)
{
return TurnDirection.LEFT;
}
else
{
return TurnDirection.RIGHT;
}
}
private void MoveTowardDirection(Vector3 direction)
{
float angle = AngleToDirection(direction);
if (float.IsNaN(angle) || angle < 0.1)
{
controllerInput.playerRotation.X = 0.0f;
controllerInput.playerMotion.Y = 1.0f;
}
else
{
if (turnDirection == TurnDirection.NOT_SET)
{
// Determine which direction to turn
turnDirection = DirectionToOrientation(direction);
}
if (turnDirection == TurnDirection.LEFT)
{
controllerInput.playerRotation.X = 0.05f;
}
else
{
controllerInput.playerRotation.X = -0.05f;
}
}
}
private List GetPlayersInView(ObjectLibrary objectLibrary)
{
List playersInView = new List();
BoundingFrustum viewFrustum;
Matrix matrixModelView = Matrix.CreateLookAt(mass.currentPosition,
mass.currentPosition + mass.normalVector,
mass.upVector);
// Create a limited range projection matrix for distance culling
Matrix limitedMatrixProjection = Matrix.CreatePerspectiveFieldOfView((float)Math.PI / 4,
(1.0f * XML.SystemSettings.GAME_WINDOW_WIDTH) /
XML.SystemSettings.GAME_WINDOW_HEIGHT,
0.3f,
1000.0f * (1.0f * intelligence / 2.0f));
viewFrustum = new BoundingFrustum(matrixModelView * limitedMatrixProjection);
// Check for human in radius
for (int i = 0; i < objectLibrary.humans.Count; i++)
{
if (objectLibrary.humans[i].IsAlive())
{
// Player found!
// Is he in view?
if (viewFrustum.Contains(objectLibrary.humans[i].boundingSphere) == ContainmentType.Contains ||
viewFrustum.Contains(objectLibrary.humans[i].boundingSphere) == ContainmentType.Intersects)
{
playersInView.Add(objectLibrary.humans[i]);
}
}
}
// Check for AI in radius
// If in survival mode.. ignore other AI bots
// Also, don't attack other bots unless there are no human players visible
if (GameFlow.gameType != GameFlow.GameType.SURVIVAL && GameFlow.gameType != GameFlow.GameType.STORY_MODE && playersInView.Count == 0)
{
for (int i = 0; i < objectLibrary.bots.Count; i++)
{
if (objectLibrary.bots[i].IsAlive() && objectLibrary.bots[i] != this)
{
//Player found!
//is he in view?
if (viewFrustum.Contains(objectLibrary.bots[i].boundingSphere) == ContainmentType.Contains ||
viewFrustum.Contains(objectLibrary.bots[i].boundingSphere) == ContainmentType.Intersects)
{
playersInView.Add(objectLibrary.bots[i]);
}
}
}
}
return playersInView;
}
private Player GetClosestPlayerInView()
{
// Get the closest player in the playersInView array
if (percept.playersInView.Count == 0)
{
return null;
}
Player closestPlayer = null;
float closestDistance = 100000.0f;
// Check for AI in radius
for (int i = 0; i < percept.playersInView.Count; i++)
{
float distance = (percept.playersInView[i].mass.currentPosition - mass.currentPosition).Length();
if (distance < closestDistance)
{
closestPlayer = percept.playersInView[i];
closestDistance = distance;
}
}
return closestPlayer;
}
private bool DoSmarterThing()
{
int probability = rand.Next(1000);
return (probability < (intelligence * 100) - HYSTERESIS_AMOUNT);
}
private bool DoDumberThing()
{
int probability = rand.Next(1000);
return (probability > (intelligence * 100) + HYSTERESIS_AMOUNT);
}
private void Jump()
{
if (jumpTimer == null)
{
jumpTimer = Stopwatch.StartNew();
}
if (jumpTimer.Elapsed.Seconds >= JUMP_DELAY)
{
controllerInput.jump = true;
jumpTimer = Stopwatch.StartNew();
}
}
public void UpdatePercepts(ObjectLibrary objectLibrary)
{
// Clear out percepts first
percept.init();
// First check for all stimuli
// Was he hit by something?
if (hitWhere.Contains(HitWhere.LEFT))
{
percept.hitLeftByParticle = true;
}
if (hitWhere.Contains(HitWhere.RIGHT))
{
percept.hitRightByParticle = true;
}
if (mass.objectHitBy.Contains(Mass.TerrainType.TERRAIN_HIGH) ||
collisionSensor.mass.objectHitBy.Contains(Mass.TerrainType.TERRAIN_HIGH))
{
percept.hitTerrain = Mass.TerrainType.TERRAIN_HIGH;
}
else if (mass.objectHitBy.Contains(Mass.TerrainType.TERRAIN_MEDIUM) ||
collisionSensor.mass.objectHitBy.Contains(Mass.TerrainType.TERRAIN_MEDIUM))
{
percept.hitTerrain = Mass.TerrainType.TERRAIN_MEDIUM;
}
else if (mass.objectHitBy.Contains(Mass.TerrainType.TERRAIN_LOW) ||
collisionSensor.mass.objectHitBy.Contains(Mass.TerrainType.TERRAIN_LOW))
{
percept.hitTerrain = Mass.TerrainType.TERRAIN_LOW;
}
else if (mass.objectHitBy.Contains(Mass.TerrainType.TERRAIN_GROUND) ||
collisionSensor.mass.objectHitBy.Contains(Mass.TerrainType.TERRAIN_GROUND))
{
percept.hitTerrain = Mass.TerrainType.TERRAIN_GROUND;
}
//Find all players in view
percept.playersInView = GetPlayersInView(objectLibrary);
if (health < LOWHEALTH - HYSTERESIS_AMOUNT)
{
percept.healthAmount = AmountType.LOW;
}
else if (health > LOWHEALTH + HYSTERESIS_AMOUNT)
{
percept.healthAmount = AmountType.HIGH;
}
else
{
percept.healthAmount = AmountType.NEUTRAL;
}
if (manna < LOWMANNA - HYSTERESIS_AMOUNT)
{
percept.mannaAmount = AmountType.LOW;
}
else if (manna > LOWMANNA + HYSTERESIS_AMOUNT)
{
percept.mannaAmount = AmountType.HIGH;
}
else
{
percept.mannaAmount = AmountType.NEUTRAL;
}
}
// Functions for updating the AI's current state
private void UpdateState()
{
if (aiState == AIState.IDLE)
{
// Find a new place to go
// just always do it for now
//if (DoSmarterThing() == true)
{
randomDestination = new Vector3(rand.Next(0, Terrain.collisionMapSize), XML.PlayerSettings.DEFAULT_PLAYER_HEIGHT, rand.Next(0, Terrain.collisionMapSize));
turnDirection = TurnDirection.NOT_SET;
aiState = AIState.GOTO;
}
}
else if (aiState == AIState.GOTO)
{
// Move towards a destination point
Vector3 diff = new Vector3(randomDestination.X - mass.currentPosition.X, 0, randomDestination.Z - mass.currentPosition.Z);
MoveTowardDirection(diff);
if (Math.Abs(diff.X) < 5 && Math.Abs(diff.Z) < 5)
{
aiState = AIState.IDLE;
}
}
else if (aiState == AIState.SEARCH_MODE)
{
// Search in the specified direction until the time runs out
if (turnDirection == TurnDirection.LEFT)
{
controllerInput.playerRotation.X = -0.1f;
}
else if (turnDirection == TurnDirection.RIGHT)
{
controllerInput.playerRotation.X = 0.1f;
}
if (searchTimer.Elapsed.Seconds > MAX_SEARCH_SECONDS)
{
aiState = AIState.IDLE;
}
}
else if (aiState == AIState.ATTACK)
{
if (playerToAttack != null)
{
Vector3 vecToPlayer = playerToAttack.mass.currentPosition - mass.currentPosition;
if (aiAttackState == AIAttackState.NONE)
{
turnDirection = TurnDirection.NOT_SET;
aiAttackState = AIAttackState.PURSUE;
}
else if (aiAttackState == AIAttackState.PURSUE)
{
MoveTowardDirection(vecToPlayer);
float angle = AngleToDirection(vecToPlayer);
// NaN if he is facing you
if (float.IsNaN(angle) || angle < 0.1)
{
if (vecToPlayer.Length() <= ATTACK_RADIUS)
{
AttackPlayer();
aiAttackState = AIAttackState.DESTROY;
}
}
}
else if (aiAttackState == AIAttackState.DESTROY)
{
//controllerInput.attackAmount = 1.0f;
controllerInput.playerMotion.Y = 0.0f;
turnDirection = DirectionToOrientation(vecToPlayer);
if (turnDirection == TurnDirection.LEFT)
{
controllerInput.playerRotation.X = -0.1f;
}
else if (turnDirection == TurnDirection.RIGHT)
{
controllerInput.playerRotation.X = 0.1f;
}
float angle = AngleToDirection(vecToPlayer);
if (float.IsNaN(angle) || angle < 0.1f)
{
controllerInput.playerRotation.X = 0.0f;
}
// If the player moves away then face and pursue again
if (vecToPlayer.Length() > ATTACK_RADIUS)
{
turnDirection = TurnDirection.NOT_SET;
aiAttackState = AIAttackState.PURSUE;
}
else
{
//in range
AttackPlayer();
}
}
//If hit while attacking...
if (percept.hitLeftByParticle || percept.hitRightByParticle)
{
controllerInput.playerMotion.Y = -1.0f;
}
}
else
{
// Reset all controller inputs
controllerInput.playerMotion = Vector2.Zero;
controllerInput.playerRotation = Vector2.Zero;
controllerInput.attackAmount = 0.0f;
controllerInput.defendAmount = 0.0f;
controllerInput.elementUse = 0;
aiState = AIState.IDLE;
}
}
else if (aiState == AIState.START_EVADE)
{
if (playerToAttack != null)
{
Vector3 vecToPlayer = playerToAttack.mass.currentPosition - mass.currentPosition;
if (vecToPlayer.Length() < ATTACK_RADIUS - HYSTERESIS_AMOUNT)
{
turnDirection = TurnDirection.NOT_SET;
playerToAvoid = playerToAttack;
aiState = AIState.EVADE;
}
else if (vecToPlayer.Length() > ATTACK_RADIUS + HYSTERESIS_AMOUNT)
{
//Player is too far away.. ignore
aiState = AIState.IDLE;
}
}
else
{
aiState = AIState.IDLE;
}
}
else if (aiState == AIState.EVADE)
{
if (playerToAvoid != null)
{
Vector3 vecToPlayer = playerToAvoid.mass.currentPosition - mass.currentPosition;
MoveTowardDirection(-vecToPlayer);
if (vecToPlayer.Length() > ATTACK_RADIUS)
{
aiState = AIState.IDLE;
}
//If they were also hit by something
if (percept.hitLeftByParticle || percept.hitRightByParticle)
{
Jump();
}
}
else
{
aiState = AIState.IDLE;
}
}
}
private void AttackPlayer()
{
//pick random element
int element = rand.Next(3);
switch (element)
{
case 0:
controllerInput.modeSwitch = GameController.ElementalMode.EARTH_MODE;
break;
case 1:
controllerInput.modeSwitch = GameController.ElementalMode.WATER_MODE;
break;
case 2:
controllerInput.modeSwitch = GameController.ElementalMode.WIND_MODE;
break;
}
controllerInput.attackMode = GameController.AttackMode.ATTACK;
controllerInput.attackAmount = 1.0f;
particleMassValue = rand.Next(50);
particleThermalValue = rand.Next(50);
}
// General update function
public override void Update(ObjectLibrary objectLibrary)
{
//if dead they can't update
if (IsAlive() == false)
{
return;
}
// Reset all controller inputs
controllerInput.init();
// Turn terrain mod back off
terrainModEnabled = false;
// Fuzzy state machine
// * <- means smarter choice
// Update percepts
UpdatePercepts(objectLibrary);
// Was he hit by something?
if (percept.hitLeftByParticle || percept.hitRightByParticle)
{
if (aiState != AIState.ATTACK && aiState != AIState.EVADE)
{
if (percept.hitRightByParticle)
{
// Hit on the right
turnDirection = TurnDirection.RIGHT;
}
else
{
// Hit on the left
turnDirection = TurnDirection.LEFT;
}
// Enters into search mode to see who shot him
aiState = AIState.SEARCH_MODE;
// Only search for a few seconds..
searchTimer = Stopwatch.StartNew();
}
}
// Now look for players within view
else if (percept.playersInView.Count > 0)
{
playerToAttack = GetClosestPlayerInView();
Vector3 toPlayer = playerToAttack.mass.currentPosition - mass.currentPosition;
controllerInput.playerRotation = Vector2.Zero;
// Check your health
if (percept.healthAmount == AmountType.LOW)
{
// Low health
if (DoSmarterThing())
{
// RUN AWAY
aiState = AIState.START_EVADE;
}
else if (DoDumberThing())
{
// ATTACK!
aiState = AIState.ATTACK;
}
}
else if (percept.healthAmount == AmountType.HIGH)
{
if (percept.mannaAmount == AmountType.LOW)
{
// Low manna
if (DoSmarterThing())
{
// RUN AWAY
aiState = AIState.START_EVADE;
}
else if (DoDumberThing())
{
// ATTACK!
aiState = AIState.ATTACK;
}
}
else if (percept.mannaAmount == AmountType.HIGH)
{
// Have manna
if (DoSmarterThing())
{
// ATTACK!
aiState = AIState.ATTACK;
}
else if (DoDumberThing())
{
// RUN AWAY
aiState = AIState.START_EVADE;
}
}
}
}
// Hit by terrain means he either fell from a high place or walked into a wall
else if (percept.hitTerrain == Mass.TerrainType.TERRAIN_GROUND)
{
// Recalculate where to go
aiState = AIState.IDLE;
}
else if (percept.hitTerrain == Mass.TerrainType.TERRAIN_LOW)
{
//terrainModEnabled = true;
//controllerInput.defendAmount = -1.0f;
}
if (percept.hitTerrain == Mass.TerrainType.TERRAIN_MEDIUM ||
percept.hitTerrain == Mass.TerrainType.TERRAIN_HIGH)
{
Jump();
}
UpdateState();
/* h4x0rz
switch (aiState)
{
case AIState.GOTO:
materialDiffuse[0] = 0;
materialDiffuse[1] = 0;
materialDiffuse[2] = 1;
break;
case AIState.EVADE:
materialDiffuse[0] = 0;
materialDiffuse[1] = 1;
materialDiffuse[2] = 0;
break;
case AIState.ATTACK:
if (aiAttackState == AIAttackState.NONE)
{
materialDiffuse[0] = 0;
materialDiffuse[1] = 0;
materialDiffuse[2] = 0;
break;
}
else if (aiAttackState == AIAttackState.PURSUE)
{
materialDiffuse[0] = 0.4f;
materialDiffuse[1] = 0;
materialDiffuse[2] = 0;
break;
}
else
{
materialDiffuse[0] = 1;
materialDiffuse[1] = 0;
materialDiffuse[2] = 0;
break;
}
case AIState.SEARCH_MODE:
materialDiffuse[0] = 1;
materialDiffuse[1] = 1;
materialDiffuse[2] = 0;
break;
case AIState.START_EVADE:
materialDiffuse[0] = 0;
materialDiffuse[1] = 0.5f;
materialDiffuse[2] = 0;
break;
default:
materialDiffuse[0] = 1;
materialDiffuse[1] = 1;
materialDiffuse[2] = 1;
break;
}
* */
controllerInput.elementUse = controllerInput.attackAmount + controllerInput.defendAmount;
base.Update(objectLibrary); // UseControllerInput(objectLibrary);
}
}
}