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