//---------------------------------------------------------------------------------------------------------------------------------------------------
//
// 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;
using DarkWynter.Engine.Init;
using Xclna.Xna.Animation;
#endregion
///
/// Bots
///
public class AI : Player
{
private Enums_Engine.AIState aiState = new Enums_Engine.AIState();
private Enums_Engine.AIAttackState aiAttackState = new Enums_Engine.AIAttackState();
private Enums_Engine.TurnDirection turnDirection = Enums_Engine.TurnDirection.NOT_SET;
PerceptInterface percept = new PerceptInterface();// Stimuli for the AI to react to
private AIVision aiVision;
private Stopwatch runAIVisionUpdate = new Stopwatch();
private Stopwatch jumpTimer = null;
private Vector3 playerToAttack = new Vector3(-1.0f);
private Vector3 playerToAvoid = new Vector3(-1.0f);
private Vector3 randomDestination = new Vector3();
private Vector3 Destination = new Vector3(0, 0, 0);
private Random rand = new Random();
private int ATTACK_RADIUS = 100;
private int BOT_SPAWN_HEIGHT = 0;
private int LOWHEALTH = 50;
private int LOWMANNA = 50;
private int JUMP_DELAY = 1;
private int AIVISION_UPDATE_TIME_MILLI;
private float shootAmount = 0.0f;
private float defendAmount = 0.0f;
private float HYSTERESIS_AMOUNT = 5.0f; //range
private enum ThoughtState
{
Goto,
Stop,
Exit
}
private ThoughtState thoughtState = ThoughtState.Goto;
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(Load gameObjectLoader)
: base(gameObjectLoader)
{
mass.objectType = Enums_Engine.ObjectType.PLAYER;
mass.gameObjectPointer = this;
aiVision = new AIVision();
}
///
/// Load stuff from XML
///
/// The Bot node
/// ObjectLibrary
/// True if load was successful
public override bool Load(ObjectLibrary objectLibrary)
{
if (!base.Load(objectLibrary))
{
return false;
}
health = Statics_Game.AISettings.AI_MAX_HEALTH;
manna = 100;
// Put the Thoughts and Wishes in a different headspace than the Nightmare AI
thoughtState = ThoughtState.Goto;
aiState = Enums_Engine.AIState.IDLE;
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;
}
#region Helper Methods
public void SpawnPlayer(ObjectLibrary objectLibrary)
{
spawnPosition.Y = objectLibrary.terrain.GetTerrainHeight(
spawnPosition.X / Statics_Engine.TerrainSettings.terrainScaleFactor,
spawnPosition.Z / Statics_Engine.TerrainSettings.terrainScaleFactor);
base.SpawnPlayer();
health = Statics_Game.AISettings.AI_MAX_HEALTH;
}
public void goToDestination(Vector3 Destination)
{
//once he gets close enough to the place he's going;
bool close = ((Math.Abs(Destination.X - mass.currentPosition.X) < 10) &&
(Math.Abs(Destination.Z - mass.currentPosition.Z) < 10));
if (!close)
{
Vector3 Orientation = Destination - mass.currentPosition;
Orientation.Normalize();
MoveTowardDirection(Orientation);
mass.MoveObject((float)Math.Abs(Orientation.Z), 0.0f);
}
else
{
mass.currentPosition = Destination;
Destination = new Vector3(0, 0, 0);
thoughtState = ThoughtState.Stop;
}
}
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)
{
// this was set to 10.0 f which completely turned the character around in the wrong direction ALWAYS
// we twitched it to something much smaller and the character walks in a straight line
//Rotate(new Vector2(0.0f, 0.0f));
//DarkWynterGame.playerController.gameInput.playerRotation.X = 0.0f;
//DarkWynterGame.playerController.gameInput.playerMotion.Y = 10.0f;
// weird math
//ACTUALLY
//If the angle is this small or not a number the idea would be NOT to rotate and just to move.
}
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();
// }
//}
#endregion
///
/// General update function
///
/// ObjectLibrary
public override void Update(ref ObjectLibrary objectLibrary)
{
// cap the y at zero so they can't get above the H2O plane
//mass.currentPosition.Y = 0.0f;
// If dead they can't update
if (IsAlive() == false)
{
return;
}
// Goto Logic for the Thoughts and Wishes
if (thoughtState == ThoughtState.Goto)
{
thoughtGotoLogic();
}
// Stop Logic for the Thoughts and Wishes
else if (thoughtState == ThoughtState.Stop)
{
thoughtStopLogic();
}
// Exit Logic for the Thoughts and Wishes
else if (thoughtState == ThoughtState.Exit)
{
thoughtExitLogic();
}
//DarkWynterGame.playerController.gameInput.init();
base.
// 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);
aiState = Enums_Engine.AIState.IDLE;
}
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;
}
}
#region Thought Logic
public void thoughtGotoLogic()
{
Vector3 Destination = new Vector3(6000, 0, 4500);
goToDestination(Destination);
}
public void thoughtStopLogic()
{
}
public void thoughtExitLogic()
{
}
private void thoughtIdleLogic()
{
}
#endregion
public void UpdateState(ObjectLibrary objectLibrary)
{
if (thoughtState == ThoughtState.Stop)
{
thoughtIdleLogic();
}
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);
}
}
#region Regular AI Movement Logic
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);
aiState = Enums_Engine.AIState.IDLE;
}
}
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);
this.Translate(new Vector2(diff.X, diff.Z));
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,
0,
rand.Next(0, Statics_Engine.TerrainSettings.collisionMapSize));
turnDirection = Enums_Engine.TurnDirection.NOT_SET;
aiState = Enums_Engine.AIState.GOTO;
//aiState = Enums_Engine.AIState.ATTACK;
}
}
#endregion
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);
}
}
}