GrenadeSoldier.cs
#region Using Statements using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using ParadigmEngine.TileEngine; using ProtoplasmEngine; using FarseerGames.FarseerPhysics; using FarseerGames.FarseerPhysics.Dynamics; using FarseerGames.FarseerPhysics.Factories; using FarseerGames.FarseerPhysics.Collisions; #endregion namespace Project_Daedalus.Game_World.Characters { /// <summary> /// Grenade soldier /// </summary> public class GrenadeSoldier : Enemy { #region Fields /// <summary> /// Throw animation /// </summary> private Animation throwAnimation; /// <summary> /// Duration of throw animation /// </summary> private float throwAnimationTime; /// <summary> /// Throw timer /// </summary> private float throwTimer; /// <summary> /// Time between throws /// </summary> private const float TimeBetweenThrows = 3f; /// <summary> /// Impulse to apply to grenade /// </summary> private Vector2 throwImpulse = Vector2.Zero; #endregion #region Constructors public GrenadeSoldier() { } #endregion #region Public Overridden Methods public override void Initialize(GameWorldArea gameWorld, Player player, Vector2 spawnPoint, Map map, string name) { this.gameWorld = gameWorld; this.spawnPoint = spawnPoint; this.map = map; this.player = player; this.name = name; flip = SpriteEffects.None; // setup attributes hitPoints = 30; AttackStrength = 10; MoveSpeed = 0.5f; MaxWaitTime = 2.5f; // set view distance viewDistance = map.Tile_Size * 20; // enemy is hostile isHostile = true; LoadContent("Enemies/GrenadeSoldier"); Reset(); } /// <summary> /// Load all entity content /// </summary> public override void LoadContent(string entity) { // Load animated textures. idleAnimation = new Animation(GameWorldComponent.MapContent.Load<Texture2D>("Sprites/Enemies/GrenadeSoldier/Idle"), 0.15f, 7, true); throwAnimation = new Animation(GameWorldComponent.MapContent.Load<Texture2D>("Sprites/Enemies/GrenadeSoldier/Throw"), 0.15f, 7, true); throwAnimationTime = throwAnimation.FrameCount * throwAnimation.FrameTime; // Calculate bounds within texture size. int width = (int)(idleAnimation.FrameWidth * 0.75f); int left = (idleAnimation.FrameWidth - width) / 2; int height = (int)(idleAnimation.FrameWidth * 0.85f); int top = idleAnimation.FrameHeight - height; localBounds = new Rectangle(left, top, width, height); killedSound = "Charger_Death"; bodyOffset = (localBounds.Height / 2); body = BodyFactory.Instance.CreateEllipseBody(GameWorldComponent.PhysicsSimulator, width / 2, height / 2, 100); body.MomentOfInertia = float.MaxValue; geom = GeomFactory.Instance.CreateEllipseGeom(GameWorldComponent.PhysicsSimulator, body, width / 2, height / 2, 200); geom.FrictionCoefficient = 3f; List<string> tags = new List<string>(); tags.Add("NPC"); geom.Tag = tags as object; geom.CollisionCategories = CollisionCategory.NPC; geom.CollidesWith = CollisionCategory.World; throwTimer = TimeBetweenThrows + Rand.GetRandomFloat(0f, 1.5f); } /// <summary> /// Perform AI Routines /// </summary> /// <remarks> /// GrenadeSoldier entity considers throwing a grenade at the player. /// If specific conditions are met, a grenade will be thrown. /// </remarks> public override void Think(GameTime gameTime) { if (isFrozen) return; float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; // set our face direction to face the player if (body.Position.X < player.Body.Position.X) direction = FaceDirection.Left; else direction = FaceDirection.Right; // if we cannot see the player, reset the throw timer and return if (!CanSee()) { throwTimer = TimeBetweenThrows + Rand.GetRandomFloat(0f, 1.5f); return; } // start the throw process if we are ready if (throwTimer <= 0f) { // if we have not yet gotten our throw impulse, try to get it if (throwImpulse == Vector2.Zero) { // calculate angle float v = 1600f; float g = GameWorldComponent.PhysicsSimulator.Gravity.Y * 1f; float x = Math.Abs(player.Position.X - body.Position.X); float y = Math.Abs(player.Position.Y - body.Position.Y); if (player.Position.Y > body.Position.Y) y *= -1; // use the Angle θ required to hit coordinate (x,y) formula in order to // find theta double radical = Math.Pow(v, 4) - g * (g * Math.Pow(x, 2) + 2 * y * Math.Pow(v, 2)); // only proceed if our result will not be imaginary if (radical >= 0) { // play throw animation and set animation timer sprite.PlayAnimation(throwAnimation); attackAnimTimer = throwAnimationTime; // no real use for the higher arc angle as of yet //var numeratorPlus = Math.Pow(v, 2) + Math.Sqrt(radical); double numeratorMinus = Math.Pow(v, 2) - Math.Sqrt(radical); double denominator = g * x; // no real use for the higher arc angle as of yet //var thetaPlus = Math.Atan(numeratorPlus / denominator); // we will be using the lower angle double thetaMinus = Math.Atan(numeratorMinus / denominator); // get angle theta double theta; theta = thetaMinus; // get our unit vector from theta Vector2 unitV = new Vector2((float)Math.Cos(theta), -(float)Math.Sin(theta)); // get our impulse from our velocity scalar and our unit vector Vector2 impulse = Vector2.Multiply(unitV, v); // reverse the x component if the player is behind the thrower if (player.Position.X < body.Position.X) impulse.X *= -1; // set impulse throwImpulse = impulse; } else // if we reach this, our radical was imaginary, and our throw is impossible. reset the timer { throwTimer = TimeBetweenThrows + Rand.GetRandomFloat(0f, 1.5f); } } else // we have our throw impulse { // since we have our throw impulse, we need to wait for the // throw animation to progress half way before applying the impulse if (attackAnimTimer <= throwAnimationTime / 2) { // create grenade, and apply the throw impulse Grenade grenade = new Grenade(this); gameWorld.EntityList.Add(grenade); grenade.Body.ApplyImpulse(throwImpulse); // reset throw impulse throwImpulse = Vector2.Zero; // reset throw timer throwTimer = TimeBetweenThrows + Rand.GetRandomFloat(0f, 1.5f); } } } else { // decrement throw timer throwTimer = Math.Max(0.0f, throwTimer - elapsed); } } /// <summary> /// Die /// </summary> public override void Die(GameTime gameTime) { if (IsAlive) { // play death sound if (killedSound != null) SoundManager.PlaySound(killedSound, 1f); // create explosion at the new position GameWorldComponent.GameParticleManager.MakeExplosion(body.Position, 0.5f); } base.Die(gameTime); } /// <summary> /// Think, then update /// </summary> public override void Update(GameTime gameTime) { if (IsAlive) { if (!isFrozen) { (geom.Tag as List<string>)[0] = "Grenade Soldier"; geom.CollisionCategories = CollisionCategory.NPC; Think(gameTime); DamagePlayer(gameTime); } else { (geom.Tag as List<string>)[0] = "Frozen"; geom.CollisionCategories = CollisionCategory.FrozenNPC; } base.Update(gameTime); } } #endregion #region Private Methods /// <summary> /// Check if the player is in sight /// </summary> private new bool CanSee() { // if the player is dead, or not in the water, halt if (!player.IsAlive) return false; // first, if the player is too far away ignore all checking if (Vector2.Distance(this.Position, player.Position) > viewDistance) return false; // cast a ray to decide whether or not we can see the player List<GeomPointPair> intersectingGeoms; intersectingGeoms = RayHelper.LineSegmentAllGeomsIntersect( body.Position, player.Body.Position, GameWorldComponent.PhysicsSimulator, true); // remove the characters own geometry from the ray collision list for (int i = 0; i < intersectingGeoms.Count; i++) { if (intersectingGeoms[i].Geom == this.geom) { intersectingGeoms.RemoveAt(i); break; ; } } // sort geoms list to get the closest geom collision intersectingGeoms.Sort(delegate(GeomPointPair g1, GeomPointPair g2) { return g1.Geom.Position.X.CompareTo(g2.Geom.Position.X); }); // if there are geoms left, test them for the player if (intersectingGeoms.Count > 0) { if (intersectingGeoms[0].Geom == player.Geom) return true; else return false; } return false; } #endregion #region Grenade Class /// <summary> /// Nested class. Grenade thrown by grenade soldier /// </summary> private class Grenade : PhysicsObject { #region Fields /// <summary> /// Throwing soldier /// </summary> private GrenadeSoldier owner; /// <summary> /// Bounding box used for explosion collision /// </summary> private Rectangle explosionBox; /// <summary> /// Timer for explosion duration /// </summary> private float explosionTimer; /// <summary> /// Maximum explosion duration /// </summary> private const float MaxExplosionTime = 0.5f; /// <summary> /// Size of explosion /// </summary> private const int explosionSize = 20; /// <summary> /// Strength of explosion /// </summary> private const float explosionStrength = 5f; #endregion #region Constructors public Grenade(GrenadeSoldier owner) { this.owner = owner; Initialize(owner.gameWorld, owner.player, owner.Body.Position, owner.Map, "Grenade"); } #endregion #region Public Overridden Methods /// <summary> /// Initialize /// </summary> public override void Initialize(GameWorldArea gameWorld, Player player, Vector2 spawnPoint, Map map, string name) { this.gameWorld = gameWorld; this.spawnPoint = spawnPoint; this.map = map; this.player = player; this.name = name; flip = SpriteEffects.None; LoadContent("Enemies/GrenadeSoldier/Grenade"); Reset(); } /// <summary> /// Load all content /// </summary> public override void LoadContent(string entity) { // Load animated textures. animation = new Animation(GameWorldComponent.MapContent.Load<Texture2D>("Sprites/Enemies/GrenadeSoldier/Grenade"), 1f, 1, false); // Calculate bounds within texture size. int width = (animation.FrameWidth); int left = (animation.FrameWidth - width) / 2; int height = (animation.FrameWidth); int top = animation.FrameHeight - height; localBounds = new Rectangle(left, top, width, height); bodyOffset = (localBounds.Height / 2); body = BodyFactory.Instance.CreateEllipseBody(GameWorldComponent.PhysicsSimulator, width / 2, height / 2, 1f); body.MomentOfInertia = float.MaxValue; geom = GeomFactory.Instance.CreateEllipseGeom(GameWorldComponent.PhysicsSimulator, body, width / 2, height / 2, 1); geom.FrictionCoefficient = 3f; List<string> tags = new List<string>(); tags.Add("EnemyPhysObj"); geom.Tag = tags as object; geom.CollisionCategories = CollisionCategory.EnemyPhysObj; geom.CollidesWith = CollisionCategory.World & CollisionCategory.Player; geom.OnCollision += OnCollision; } /// <summary> /// Update method /// </summary> public override void Update(GameTime gameTime) { float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; // if the explosion timer is running, check for collisions if (explosionTimer > 0f) { CheckForPlayerCollision(gameTime); explosionTimer = Math.Max(0.0f, explosionTimer - elapsed); } // if we have a collision box set up, kill the grenade when the timer hits zero if (explosionBox != Rectangle.Empty) if (explosionTimer <= 0f) Die(gameTime); base.Update(gameTime); } /// <summary> /// Check for player collision with explosion /// </summary> private void CheckForPlayerCollision(GameTime gameTime) { if (explosionBox.Intersects(player.BoundingRectangle)) player.TakeDamage(gameTime, explosionStrength); } #endregion #region Private Methods /// <summary> /// Collision event /// </summary> private bool OnCollision(Geom geometry1, Geom geometry2, ContactList contactList) { // make explosion effect GameWorldComponent.GameParticleManager.MakeExplosion(body.Position, 1f); // play explosion sfx SoundManager.PlaySound("Explosion", 1f); // set up bounding box for explosion explosionBox.X = (int)body.Position.X - (explosionSize / 2); explosionBox.Y = (int)body.Position.Y - (explosionSize / 2); explosionBox.Width = explosionSize; explosionBox.Height = explosionSize; // start explosion timer explosionTimer = MaxExplosionTime; // disable body body.Enabled = false; return true; } #endregion } #endregion } }