#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
}
}