//--------------------------------------------------------------------------------------------------------------------------------------------------- // // 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; } */