using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; namespace CarFire { /// /// A type for the states of an artificually intelligent entity. /// public enum AiState { Standing, Pacing, Chasing, Dazed, Fighting, Retreating } /// /// An example monster. This can serve as a starting place for /// creating other monsters. This one just follows a path. /// public class SaberMonster2 : IMonster { //starting health int health = 100; /// /// Construct this type of monster. This constructor is called /// by the map when the game requests entities. /// /// The single character ID. /// The initial position on the map. /// More parameters. /// The game object reference. public SaberMonster2(char identifier, Point position, Dictionary info, Game game) { mId = identifier; mMotion = new MovementManager(position); // We need to keep the game reference in order to get the grid when we // need to find paths. mGame = game; pathFinder = null; // Get the speed of the monster. If not set in the map, it defaults to // whatever the default of MovementManager is... 1 I think. string speedString; if (info.TryGetValue("speed", out speedString)) { int? speed = Parse.Integer(speedString); if (speed != null) mMotion.Speed = speed.Value; } // Get the "idle path" coordinates loaded from the map. string idlePath; if (info.TryGetValue("path", out idlePath)) { string[] idlePathPoints = Parse.List(idlePath); foreach (string pathPoint in idlePathPoints) { Point? point = Parse.Coordinates(pathPoint); if (point != null) mIdlePath.Add(point.Value); } } // Start doing something... //StartPacing(); } public void DefaultAction() { mState = AiState.Standing; } public void Chasing(Point Chase) { Chasing(Chase.X, Chase.Y); } /// /// Call this to switch the monster AI state to Chasing and set up /// the initial paths. A path to . /// public void Chasing(int X, int Y) { mState = AiState.Chasing; // Determine the best (closest) waypoint to start at. // We may not be on the path, so we have to walk to get on it. /* mIdlePathIndex = 0; int closest = int.MaxValue; for (int i = 0; i < mIdlePath.Count; i++) { int distance = PathFinder.GetManhattanDistance(Coordinates, mIdlePath[i]); if (distance < closest) { mIdlePathIndex = i; closest = distance; } } */ // Find the path to get to the closest waypoint. if (pathFinder == null) pathFinder = new PathFinder(mGame.Grid); mPath = new List(32); mPath.Add(Coordinates); List path = pathFinder.GetPath(mMotion.Coordinates, new Point(X,Y)); if (path != null) { mPath.AddRange(path); //mPath.Add(mIdlePath[mIdlePathIndex]); } mPathIndex = 0; } /// /// Call this to switch the monster AI state to pacing and set up /// the initial paths. The monster will start following the path it /// was defined with in the map file. /// public void StartPacing() { mState = AiState.Pacing; if (mIdlePath.Count == 0) return; // Determine the best (closest) waypoint to start at. // We may not be on the path, so we have to walk to get on it. mIdlePathIndex = 0; int closest = int.MaxValue; for (int i = 0; i < mIdlePath.Count; i++) { int distance = PathFinder.GetManhattanDistance(Coordinates, mIdlePath[i]); if (distance < closest) { mIdlePathIndex = i; closest = distance; } } // Find the path to get to the closest waypoint. PathFinder pathFinder = new PathFinder(mGame.Grid); mPath = new List(32); mPath.Add(Coordinates); List path = pathFinder.GetPath(mMotion.Coordinates, mIdlePath[mIdlePathIndex]); if (path != null) { mPath.AddRange(path); mPath.Add(mIdlePath[mIdlePathIndex]); } mPathIndex = 0; } Direction GetDirectionToNextCell() { if (mPathIndex >= mPath.Count) { mState = AiState.Standing; } // We need to make sure out direction is set to the next cell // we want to be. If our current coordinates match that, we need // to change our direction to get to the next cell. if (mPath[mPathIndex % mPath.Count] == mMotion.Coordinates) { mPathIndex++; mPathDirection = MovementManager.GetDirection(mMotion.Coordinates, mPath[mPathIndex % mPath.Count]); } return mPathDirection; } #region IMonster Members /// /// I don't know what this is for. /// public bool visible { get { throw new NotImplementedException(); } } #endregion #region ICharacter Members /// /// Load the monster's content. This is called by the map when /// the game requests the entities. /// /// The zaphnod. public void LoadContent(ContentManager contentManager) { mTexture = contentManager.Load("menuItem"); } /// /// Update the monster's state. This should be called by the game /// every "frame" (whenever the game is updating its state). In this /// simple monster, all we need to do is update the motion manager. /// /// public void Update(TimeSpan timeSpan) { int closestChar = 0; int closestCharDist = 1000; for (int c = 0; c < mGame.State.mCharacters.Length; c++) { if (mGame.State.mCharacters[c] != null && mGame.AIData.spaceVisible(Coordinates, mGame.State.mCharacters[c].Coordinates)) { if (PathFinder.GetManhattanDistance(Coordinates, mGame.State.mCharacters[c].Coordinates) < closestCharDist) { closestCharDist = PathFinder.GetManhattanDistance(Coordinates, mGame.State.mCharacters[c].Coordinates); closestChar = c; } } } if (mGame.State.mCharacters[closestChar] != null && mGame.AIData.spaceVisible(Coordinates, mGame.State.mCharacters[closestChar].Coordinates)) { Chasing(mGame.State.mCharacters[0].Coordinates); } else { DefaultAction(); } switch (mState) { case AiState.Pacing: case AiState.Chasing: mMotion.Update(timeSpan, GetDirectionToNextCell()); break; case AiState.Standing: case AiState.Dazed: case AiState.Fighting: case AiState.Retreating: break; } } /// /// Draw the monster. We just ask the map for our screen position, /// passing it the position which the motion manager keeps track of. /// /// The public void Draw(SpriteBatch spriteBatch) { Rectangle position = mGame.State.Map.GetRectangleFromCoordinates(mMotion.Position); spriteBatch.Draw(mTexture, position, Color.White); } /// /// A monster should keep track of its health. This one doesn't. /// public int Health { get { return this.health; } } /// /// This monster is invincible. /// /// public void causeDamageTo(int amount) { this.health -= amount; } public bool IsCollidable { get { return true; } } /// /// Get the smoothed position. /// public Vector2 Position { get { return mMotion.Position; } } /// /// Get the grid coordinates. /// public Point Coordinates { get { return mMotion.Coordinates; } set { mMotion = new MovementManager(value, mMotion.Speed); } } /// /// Get the entity identifier. /// public char Identifier { get { return mId; } } #endregion #region Private Variables Game mGame; char mId; MovementManager mMotion; AI mAI; PathFinder pathFinder; List mIdlePath = new List(); // List of waypoints that we got from the map. int mIdlePathIndex; // Index to the waypoint we're heading for. List mPath; // List of cells in the path between the position between where // we started and the waypoint we're heading for. int mPathIndex; // Index to the cell we're heading for. Direction mPathDirection; // The direction between our current position and the place we're going. AiState mState; // What is the monster doing? Texture2D mTexture; // Obvious. #endregion } }