//---------------------------------------------------------------------------------------------------------------------------------------------------
//
// Copyright (C)2007 DarkWynter Studios. All rights reserved.
//
//---------------------------------------------------------------------------------------------------------------------------------------------------
// {License Information: Creative Commons}
//---------------------------------------------------------------------------------------------------------------------------------------------------
namespace DarkWynter.Game.GameObjects
{
#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;
using DarkWynter.Engine;
using DarkWynter.Engine.Globals;
using DarkWynter.Engine.ObjectLib;
using DarkWynter.Engine.GameObjects;
using DarkWynter.Stream;
using Globals;
#endregion
///
/// Bots
///
public class AI : Player
{
// Stimuli for the AI to react to
PerceptInterface percept = new PerceptInterface();
private Enums_Engine.TurnDirection turnDirection = Enums_Engine.TurnDirection.NOT_SET;
private Enums_Engine.AIState aiState = new Enums_Engine.AIState();
private Enums_Engine.AIAttackState aiAttackState = new Enums_Engine.AIAttackState();
private Vector3 randomDestination = new Vector3();
private Stopwatch jumpTimer = null;
private Vector3 playerToAttack = new Vector3(-1.0f);
private Vector3 playerToAvoid = new Vector3(-1.0f);
private AIVision aiVision;
private Random rand = new Random();
private int ATTACK_RADIUS = 100;
private float HYSTERESIS_AMOUNT = 5.0f; //range
//private int BOT_SPAWN_HEIGHT = 500;
private int LOWHEALTH = 50;
private int LOWMANNA = 50;
private int JUMP_DELAY = 1;
private int AIVISION_UPDATE_TIME_MILLI;
private Stopwatch runAIVisionUpdate = new Stopwatch();
private float shootAmount = 0.0f;
private float defendAmount = 0.0f;
private class PerceptInterface
{
public Enums_Engine.TerrainType hitTerrain;
public Enums_Engine.ObjectType upcomingTerrain;
public bool hitLeftByParticle;
public bool hitRightByParticle;
public List playersInView = new List();
public Enums_Engine.AmountType mannaAmount;
public Enums_Engine.AmountType healthAmount;
public void init()
{
hitTerrain = Enums_Engine.TerrainType.NONE;
hitLeftByParticle = false;
hitRightByParticle = false;
playersInView.Clear();
mannaAmount = Enums_Engine.AmountType.NEUTRAL;
healthAmount = Enums_Engine.AmountType.NEUTRAL;
upcomingTerrain = Enums_Engine.ObjectType.NONE;
}
};
///
/// Constructor
///
public AI()
: base()
{
mass.objectType = Enums_Engine.ObjectType.PLAYER;
mass.gameObjectPointer = this;
aiVision = new AIVision();
}
///
/// Spawns a new AI
///
/// ObjectLibrary
public void SpawnPlayer(ObjectLibrary objectLibrary)
{
spawnHeight = objectLibrary.terrain.GetTerrainHeight(spawnPosition.X / Statics_Engine.TerrainSettings.terrainScaleFactor,
spawnPosition.Z / Statics_Engine.TerrainSettings.terrainScaleFactor);
base.SpawnPlayer();
health = Statics_Game.AISettings.AI_MAX_HEALTH;
}
///
/// Load stuff from XML
///
/// The Bot node
/// ObjectLibrary
/// True if load was successful
public override bool Load(XmlNode node, ObjectLibrary objectLibrary)
{
if (!base.Load(node, objectLibrary))
{
return false;
}
health = Statics_Game.AISettings.AI_MAX_HEALTH;
manna = 100;
//aiState = Enums_Engine.AIState.IDLE;
aiState = Enums_Engine.AIState.ATTACK;
aiAttackState = Enums_Engine.AIAttackState.PURSUE;
runAIVisionUpdate.Reset();
runAIVisionUpdate.Start();
AIVISION_UPDATE_TIME_MILLI = (20 - Statics_Game.AISettings.AI_INTELLIGENCE) * 25;
aiVision.DrawObjLibAIVision = new AIVision.AIVisionDelegate(objectLibrary.DrawAIVision);
return true;
}
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 Enums_Engine.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.01)
{
return Enums_Engine.TurnDirection.LEFT;
}
else
{
return Enums_Engine.TurnDirection.RIGHT;
}
}
private void MoveTowardDirection(Vector3 direction)
{
float angle = AngleToDirection(direction);
if (float.IsNaN(angle) || angle < 0.1)
{
Rotate(new Vector2(0.0f, 10.0f));
//DarkWynterGame.playerController.gameInput.playerRotation.X = 0.0f;
//DarkWynterGame.playerController.gameInput.playerMotion.Y = 10.0f;
}
else
{
if (turnDirection == Enums_Engine.TurnDirection.NOT_SET)
{
// Determine which direction to turn
turnDirection = DirectionToOrientation(direction);
}
if (turnDirection == Enums_Engine.TurnDirection.LEFT)
{
Rotate(new Vector2(0.05f, 0.0f));
//DarkWynterGame.playerController.gameInput.playerRotation.X = 0.05f;
}
else
{
Rotate(new Vector2(-0.05f, 0.0f));
//DarkWynterGame.playerController.gameInput.playerRotation.X = -0.05f;
}
}
}
private Vector3 GetClosestPlayerInView()
{
Vector3 closestPlayer = new Vector3(-1.0f);
// Get the closest player in the playersInView array
if (percept.playersInView.Count == 0)
{
return closestPlayer;
}
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).Length();
if (distance < closestDistance)
{
closestPlayer = percept.playersInView[i];
closestDistance = distance;
}
}
return closestPlayer;
}
private bool DoSmarterThing()
{
int probability = rand.Next(1000);
return (probability < (Statics_Game.AISettings.AI_INTELLIGENCE * 100) - HYSTERESIS_AMOUNT);
}
//private bool DoDumberThing()
//{
// int probability = rand.Next(1000);
// return (probability > (Statics_Game.AISettings.AI_INTELLIGENCE * 100) + HYSTERESIS_AMOUNT);
//}
///
/// Method called when the AI wants to jump
///
/// Object Library
public override void Jump(ObjectLibrary objectLibrary)
{
if (jumpTimer == null)
{
jumpTimer = Stopwatch.StartNew();
}
if (jumpTimer.Elapsed.Seconds >= JUMP_DELAY)
{
base.Jump(objectLibrary);
jumpTimer = Stopwatch.StartNew();
}
}
///
/// General update function
///
/// ObjectLibrary
public override void Update(ref ObjectLibrary objectLibrary)
{
// If dead they can't update
if (IsAlive() == false)
{
return;
}
// Reset all controller inputs
//DarkWynterGame.playerController.gameInput.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 != Enums_Engine.AIState.ATTACK && aiState != Enums_Engine.AIState.EVADE)
{
if (percept.hitRightByParticle)
{
// Hit on the right
turnDirection = Enums_Engine.TurnDirection.RIGHT;
}
else
{
// Hit on the left
turnDirection = Enums_Engine.TurnDirection.LEFT;
}
aiState = Enums_Engine.AIState.ATTACK;
}
}
// Now look for players within view
else if (percept.playersInView.Count > 0)
{
playerToAttack = GetClosestPlayerInView();
Vector3 toPlayer = playerToAttack - mass.currentPosition;
//DarkWynterGame.playerController.gameInput.playerRotation = Vector2.Zero;
// Check your health
if (percept.healthAmount == Enums_Engine.AmountType.LOW)
{
// Low health
if (DoSmarterThing())
{
// RUN AWAY
aiState = Enums_Engine.AIState.START_EVADE;
}
else
{
// ATTACK!
aiState = Enums_Engine.AIState.ATTACK;
}
}
else if (percept.healthAmount == Enums_Engine.AmountType.HIGH)
{
if (percept.mannaAmount == Enums_Engine.AmountType.LOW)
{
// Low manna
if (DoSmarterThing())
{
// RUN AWAY
aiState = Enums_Engine.AIState.START_EVADE;
}
else
{
// ATTACK!
aiState = Enums_Engine.AIState.ATTACK;
}
}
else if (percept.mannaAmount == Enums_Engine.AmountType.HIGH)
{
// Have manna
if (DoSmarterThing())
{
// ATTACK!
aiState = Enums_Engine.AIState.ATTACK;
}
else
{
// RUN AWAY
aiState = Enums_Engine.AIState.START_EVADE;
}
}
}
}
// Hit by terrain means he either fell from a high place or walked into a wall
else if (percept.hitTerrain == Enums_Engine.TerrainType.GROUND)
{
// Recalculate where to go
aiState = Enums_Engine.AIState.IDLE;
}
else if (percept.hitTerrain == Enums_Engine.TerrainType.LOW)
{
//terrainModEnabled = true;
//controllerInput.defendAmount = -1.0f;
}
if (percept.hitTerrain == Enums_Engine.TerrainType.MEDIUM ||
percept.hitTerrain == Enums_Engine.TerrainType.HIGH)
{
Jump(objectLibrary);
}
UpdateState(objectLibrary);
attackAmount = shootAmount + defendAmount;
base.Update(ref objectLibrary);
}
private void UpdatePercepts(ObjectLibrary objectLibrary)
{
// Clear out percepts first
percept.init();
// First check for all stimuli
// Was he hit by something?
if (hitWhere.Contains(Enums_Engine.HitWhere.LEFT))
{
percept.hitLeftByParticle = true;
}
if (hitWhere.Contains(Enums_Engine.HitWhere.RIGHT))
{
percept.hitRightByParticle = true;
}
// If health is within danger range
if (health < LOWHEALTH - HYSTERESIS_AMOUNT)
{
percept.healthAmount = Enums_Engine.AmountType.LOW;
}
else if (health > LOWHEALTH + HYSTERESIS_AMOUNT)
{
percept.healthAmount = Enums_Engine.AmountType.HIGH;
}
else
{
percept.healthAmount = Enums_Engine.AmountType.NEUTRAL;
}
// If manna is within danger range
if (manna < LOWMANNA - HYSTERESIS_AMOUNT)
{
percept.mannaAmount = Enums_Engine.AmountType.LOW;
}
else if (manna > LOWMANNA + HYSTERESIS_AMOUNT)
{
percept.mannaAmount = Enums_Engine.AmountType.HIGH;
}
else
{
percept.mannaAmount = Enums_Engine.AmountType.NEUTRAL;
}
hitWhere.Clear();
if (Statics_Game.AISettings.aiSwitches.enableAIVisionSearch &&
runAIVisionUpdate.ElapsedMilliseconds >= AIVISION_UPDATE_TIME_MILLI)
{
runAIVisionUpdate.Reset();
runAIVisionUpdate.Start();
ShaderParameters.DrawFX.numberOfPlayers.SetValue(Statics_Engine.SystemSettings.TOTAL_PLAYERS);
ShaderParameters.DrawFX.fogEnd.SetValue(Statics_Game.AISettings.AI_INTELLIGENCE * 1000);
ShaderParameters.DrawFX.fogStart.SetValue((Statics_Game.AISettings.AI_INTELLIGENCE * 1000) - 1000);
ShaderParameters.AIVisionFX.numberOfPlayers.SetValue(Statics_Engine.SystemSettings.TOTAL_PLAYERS);
percept.playersInView = aiVision.GetVisiblePlayers(mass.currentPosition, mass.normalVector, mass.upVector);
}
// HACK: To test if a player has been spotted
if (percept.playersInView.Count != 0)
{
return;
}
}
private void UpdateState(ObjectLibrary objectLibrary)
{
if (aiState == Enums_Engine.AIState.IDLE)
{
idleLogic();
}
else if (Statics_Game.AISettings.aiSwitches.enableGoTo && aiState == Enums_Engine.AIState.GOTO)
{
gotoLogic();
}
else if (Statics_Game.AISettings.aiSwitches.enableDefaultSearch && aiState == Enums_Engine.AIState.SEARCH_MODE)
{
searchLogic();
}
else if (Statics_Game.AISettings.aiSwitches.enableAttack && aiState == Enums_Engine.AIState.ATTACK)
{
attackLogic();
}
else if (Statics_Game.AISettings.aiSwitches.enableEvade && aiState == Enums_Engine.AIState.START_EVADE)
{
startEvadeLogic();
}
else if (Statics_Game.AISettings.aiSwitches.enableEvade && aiState == Enums_Engine.AIState.EVADE)
{
evadeLogic(objectLibrary);
}
}
private void evadeLogic(ObjectLibrary objectLibrary)
{
if (playerToAvoid != new Vector3(-1.0f))
{
Vector3 vecToPlayer = playerToAvoid - mass.currentPosition;
MoveTowardDirection(-vecToPlayer);
if (vecToPlayer.Length() > ATTACK_RADIUS)
{
aiState = Enums_Engine.AIState.ATTACK;
//aiState = Enums_Engine.AIState.IDLE;
}
//If they were also hit by something
if (percept.hitLeftByParticle || percept.hitRightByParticle)
{
Jump(objectLibrary);
}
}
else
{
aiState = Enums_Engine.AIState.ATTACK;
// aiState = Enums_Engine.AIState.IDLE;
}
}
private void startEvadeLogic()
{
if (playerToAttack != new Vector3(-1.0f))
{
Vector3 vecToPlayer = playerToAttack - mass.currentPosition;
if (vecToPlayer.Length() < ATTACK_RADIUS - HYSTERESIS_AMOUNT)
{
turnDirection = Enums_Engine.TurnDirection.NOT_SET;
playerToAvoid = playerToAttack;
aiState = Enums_Engine.AIState.EVADE;
}
else if (vecToPlayer.Length() > ATTACK_RADIUS + HYSTERESIS_AMOUNT)
{
//Player is too far away.. ignore
//aiState = Enums_Engine.AIState.IDLE;
aiState = Enums_Engine.AIState.ATTACK;
}
}
else
{
aiState = Enums_Engine.AIState.ATTACK;
}
}
private void attackLogic()
{
if (playerToAttack != new Vector3(-1.0f))
{
Vector3 vecToPlayer = playerToAttack - mass.currentPosition;
if (aiAttackState == Enums_Engine.AIAttackState.NONE)
{
turnDirection = Enums_Engine.TurnDirection.NOT_SET;
aiAttackState = Enums_Engine.AIAttackState.PURSUE;
}
else if (aiAttackState == Enums_Engine.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 = Enums_Engine.AIAttackState.DESTROY;
}
}
}
else if (aiAttackState == Enums_Engine.AIAttackState.DESTROY)
{
destroyLogic(vecToPlayer);
}
//If hit while attacking...
if (percept.hitLeftByParticle || percept.hitRightByParticle)
{
Translate(new Vector2(0.0f, -1.0f));
//DarkWynterGame.playerController.gameInput.playerMotion.Y = -1.0f;
}
}
else
{
// Reset all controller inputs
//DarkWynterGame.playerController.gameInput.playerMotion = Vector2.Zero;
//DarkWynterGame.playerController.gameInput.playerRotation = Vector2.Zero;
shootAmount = 0.0f;
defendAmount = 0.0f;
attackAmount = 0;
//aiState = Enums_Engine.AIState.IDLE;
aiState = Enums_Engine.AIState.ATTACK;
}
}
private void destroyLogic(Vector3 vecToPlayer)
{
//controllerInput.attackAmount = 1.0f;
//DarkWynterGame.playerController.gameInput.playerMotion.Y = 0.0f;
turnDirection = DirectionToOrientation(vecToPlayer);
if (turnDirection == Enums_Engine.TurnDirection.LEFT)
{
Rotate(new Vector2(-0.1f, 0.0f));
//DarkWynterGame.playerController.gameInput.playerRotation.X = -0.1f;
}
else if (turnDirection == Enums_Engine.TurnDirection.RIGHT)
{
Rotate(new Vector2(0.1f, 0.0f));
//DarkWynterGame.playerController.gameInput.playerRotation.X = 0.1f;
}
float angle = AngleToDirection(vecToPlayer);
// If the player moves away then face and pursue again
if (vecToPlayer.Length() > ATTACK_RADIUS)
{
turnDirection = Enums_Engine.TurnDirection.NOT_SET;
aiAttackState = Enums_Engine.AIAttackState.PURSUE;
}
else
{
//in range
AttackPlayer();
}
}
private void searchLogic()
{
// Search in the specified direction until the time runs out
if (turnDirection == Enums_Engine.TurnDirection.LEFT)
{
Rotate(new Vector2(-0.1f, 0.0f));
//DarkWynterGame.playerController.gameInput.playerRotation.X = -0.1f;
}
else if (turnDirection == Enums_Engine.TurnDirection.RIGHT)
{
Rotate(new Vector2(0.1f, 0.0f));
//DarkWynterGame.playerController.gameInput.playerRotation.X = 0.1f;
}
// aiState = Enums_Engine.AIState.IDLE;
aiState = Enums_Engine.AIState.ATTACK;
}
private void gotoLogic()
{
// 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 = Enums_Engine.AIState.IDLE;
//aiState = Enums_Engine.AIState.ATTACK;
}
}
private void idleLogic()
{
// Find a new place to go
// just always do it for now
if (DoSmarterThing())
{
randomDestination = new Vector3(rand.Next(0, Statics_Engine.TerrainSettings.collisionMapSize),
Statics_Engine.PlayerSettings.DEFAULT_PLAYER_HEIGHT,
rand.Next(0, Statics_Engine.TerrainSettings.collisionMapSize));
turnDirection = Enums_Engine.TurnDirection.NOT_SET;
aiState = Enums_Engine.AIState.GOTO;
//aiState = Enums_Engine.AIState.ATTACK;
}
}
private void AttackPlayer()
{
// return;
//pick random element
int element = rand.Next(3);
switch (element)
{
case 0:
lastModeSelected = Enums_Engine.AttackType.EARTH;
break;
case 1:
lastModeSelected = Enums_Engine.AttackType.WATER;
break;
case 2:
lastModeSelected = Enums_Engine.AttackType.WIND;
break;
}
shootAmount = 1.0f;
attackMagnitude = rand.Next(50);
attackTemperature = rand.Next(50);
}
}
}
/* Legacy Code
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 * Statics.RenderSettings.GAME_WINDOW_WIDTH) /
Statics.RenderSettings.GAME_WINDOW_HEIGHT,
0.3f,
1000.0f * (1.0f * Statics.PlayerSettings.AI_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].mass.boundingSphere) == ContainmentType.Contains ||
// viewFrustum.Contains(objectLibrary.humans[i].mass.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 (Statics.GameSettings.gameType != Enums_Engine.GameType.SURVIVAL && Statics.GameSettings.gameType != Enums_Engine.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].mass.boundingSphere) == ContainmentType.Contains ||
// viewFrustum.Contains(objectLibrary.bots[i].mass.boundingSphere) == ContainmentType.Intersects)
// {
// playersInView.Add(objectLibrary.bots[i]);
// }
// }
// }
//}
return playersInView;
}
*/