#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 Microsoft.Xna.Framework.Input;
using Project_Daedalus.Game_World.Characters.PlayerAttacks;
using Project_Daedalus.Game_World.Classes.Characters.PlayerAbilities;
using ParadigmEngine.TileEngine;
using ProtoplasmEngine;
using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Dynamics.Joints;
using FarseerGames.FarseerPhysics.Factories;
using FarseerGames.FarseerPhysics.Collisions;
#endregion
namespace Project_Daedalus.Game_World.Characters
{
/// <summary>
/// The player character
/// </summary>
public class Player : Character
{
/// <summary>
/// Event handling portal collisions
/// </summary>
public event PortalCollisionEvent PortalCollision;
/// <summary>
/// Event handing player death
/// </summary>
public event PlayerDeathEvent PlayerDeath;
#region Fields
/// <summary>
/// Friction while on wall
/// </summary>
private const float WallFrictionCoefficiant = .09f;
/// <summary>
/// Friction while on ground
/// </summary>
private const float GroundFrictionCoefficiant = 3f;
/// <summary>
/// Max distance for rope shot
/// TODO: Integrate this into RopeShot class
/// </summary>
private const float RopeDist = 400f;
// attack / weapon states
/// <summary>
/// Did player just attack
/// </summary>
private bool justAttacked;
/// <summary>
/// Did we just switch weapons
/// </summary>
private bool justSwitched;
/// <summary>
/// Current weapon index
/// </summary>
private int currentWeaponIndex = 0;
// respawn info
/// <summary>
/// Respawn timer
/// </summary>
private float deathWaitTimer = 0.0f;
/// <summary>
/// Time between respawns
/// </summary>
private const float MaxDeathWaitTime = 3.0f;
/// <summary>
/// Is player invincible
/// </summary>
private bool isInvincible = false;
/// <summary>
/// Invincible timer
/// </summary>
private float invincibleTimer = 0.0f;
/// <summary>
/// How long does the player remain invincible after damage is received
/// (in seconds)
/// </summary>
private const float MaxInvincibleTime = 2.0f;
/// <summary>
/// Blink timer (used for invincibility)
/// </summary>
private float blinkTimer = 0.0f;
/// <summary>
/// How long between sprite blinks when invincible (in seconds)
/// </summary>
private const float MaxBlinkTime = 0.15f;
// attack classes and variables
/// <summary>
/// Current attack
/// </summary>
private Attack currentAttack;
/// <summary>
/// Container holding all available attacks
/// </summary>
private List<Attack> availableAttacks;
/// <summary>
/// Normal slash attack
/// </summary>
private NormalAttack normalAttack;
/// <summary>
/// Flamethrower
/// </summary>
private FlameThrowerAttack flameThrower;
/// <summary>
/// Ice shield
/// </summary>
private IceShieldAttack iceShield;
/// <summary>
/// Time freeze
/// </summary>
private TimeFreezeAttack timeFreeze;
/// <summary>
/// Grappling hook
/// </summary>
private GrapplingHook grapplingHook;
/// <summary>
/// Are player currently stopping time
/// </summary>
private bool isStoppingTime = false;
/// <summary>
/// Current mana amount
/// </summary>
private float mana;
/// <summary>
/// Maximum mana amount
/// </summary>
private float maxMana = 100;
/// <summary>
/// Last geometry we collided with
/// </summary>
private Geom lastCollided;
#endregion
#region Attributes
/// <summary>
/// Did player just attack
/// </summary>
public bool JustAttacked
{
get { return justAttacked; }
}
/// <summary>
/// Is player invincible
/// </summary>
public bool IsInvincible
{
get { return isInvincible; }
}
/// <summary>
/// Current attack
/// </summary>
public Attack CurrentAttack
{
get { return currentAttack; }
}
/// <summary>
/// Is player currently stopping time
/// </summary>
public bool IsStoppingTime
{
get { return isStoppingTime; }
set { isStoppingTime = value; }
}
/// <summary>
/// Current mana amount
/// </summary>
public float Mana
{
get { return mana; }
set { mana = value; }
}
/// <summary>
/// Maximum mana amount
/// </summary>
public float MaxMana
{
get { return maxMana; }
set { maxMana = value; }
}
#endregion
#region Constructors
/// <summary>
/// Constructors a new player.
/// </summary>
public Player(GameWorldArea gameWorld, Map map)
{
LoadContent("Player");
this.gameWorld = gameWorld;
this.map = map;
maxHitPoints = 100;
flip = SpriteEffects.None;
canWallJump = true;
// initialize attacks and set current
normalAttack = new NormalAttack(this);
flameThrower = new FlameThrowerAttack(this);
iceShield = new IceShieldAttack(this);
timeFreeze = new TimeFreezeAttack(this);
grapplingHook = new GrapplingHook(this);
availableAttacks = new List<Attack>();
availableAttacks.Add(normalAttack);
availableAttacks.Add(flameThrower);
availableAttacks.Add(iceShield);
availableAttacks.Add(timeFreeze);
currentAttack = normalAttack;
Reset();
}
#endregion
#region Public Overridden Methods
/// <summary>
/// Load all content
/// </summary>
/// <param name="entity"></param>
public override void LoadContent(string entity)
{
// Load animated textures.
idleAnimation = new Animation(GameWorldComponent.StaticContent.Load<Texture2D>("Sprites/Player/Idle"), 0.15f, 6, true);
runAnimation = new Animation(GameWorldComponent.StaticContent.Load<Texture2D>("Sprites/Player/Run"), 0.1f, 11, true);
jumpAnimation = new Animation(GameWorldComponent.StaticContent.Load<Texture2D>("Sprites/Player/Jump"), 0.1f, 12, false);
fallAnimation = new Animation(GameWorldComponent.StaticContent.Load<Texture2D>("Sprites/Player/Fall"), 0.1f, 4, true);
wallSlideAnimation = new Animation(GameWorldComponent.StaticContent.Load<Texture2D>("Sprites/Player/Run"), 0.1f, 11, true);
climbUpAnimation = new Animation(GameWorldComponent.StaticContent.Load<Texture2D>("Sprites/Player/Climb_Up"), 2f, 6, true);
climbDownAnimation = new Animation(GameWorldComponent.StaticContent.Load<Texture2D>("Sprites/Player/Climb_Down"), 2f, 6, true);
// set sound cue names
hitSound = "Player_Hit";
jumpSound = "Player_Jump";
killedSound = "Player_Death";
// Calculate bounds within texture size.
int width = (int)(idleAnimation.FrameWidth * 0.6f);
int left = (idleAnimation.FrameWidth - width) / 2;
int height = (int)(idleAnimation.FrameHeight * 0.7f);
int top = idleAnimation.FrameHeight - height;
localBounds = new Rectangle(left, top, width, height);
// create body and geometry
bodyOffset = (localBounds.Height / 2);
body = BodyFactory.Instance.CreateEllipseBody(GameWorldComponent.PhysicsSimulator, width / 2, height / 2, 0.1f);
body.MomentOfInertia = float.MaxValue;
geom = GeomFactory.Instance.CreateEllipseGeom(GameWorldComponent.PhysicsSimulator, body, width / 2, height / 2, 200);
geom.FrictionCoefficient = 3f;
geom.RestitutionCoefficient = 0f;
// set tags
List<string> tags = new List<string>();
tags.Add("Player");
geom.Tag = tags as object;
geom.OnCollision += OnCollision;
geom.CollisionCategories = CollisionCategory.Player;
geom.CollidesWith = CollisionCategory.All & ~CollisionCategory.Player & ~CollisionCategory.NPC & CollisionCategory.FrozenNPC;
}
/// <summary>
/// Collision event
/// </summary>
private bool OnCollision(Geom geometry1, Geom geometry2, ContactList contactList)
{
lastCollided = geometry2;
List<String> tags = geometry2.Tag as List<string>;
try
{
if (tags[0] == "World")
{
try
{
// check for platform collisions
if (tags[1] == "Platform")
{
foreach (Contact contact in contactList)
{
// if the normal is <= 0, we know we are entering the collision from
// below, and we can cancel the collision
if (contact.Normal.Y <= 0)
return false;
}
}
if (isOnLadder)
{
// check for ladder top collisions
if (tags[1] == "Ladder Top")
{
return false;
}
}
}
catch (System.Exception)
{
}
}
}
catch (NullReferenceException)
{
}
return true;
}
/// <summary>
/// Handles input, performs physics, and animates the player sprite.
/// </summary>
public override void Update(GameTime gameTime)
{
if (IsAlive)
{
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
// wait for invincibility to end, then toggle the flag
if (invincibleTimer <= 0.0f)
{
blinkTimer = 0.0f;
isInvincible = false;
}
else
invincibleTimer = Math.Max(0.0f, invincibleTimer - elapsed);
if (isInvincible)
blinkTimer = Math.Max(0.0f, blinkTimer - elapsed);
// do not change direction during knockback
if (!isKnockedBack)
{
// update direction based on velocity
if (velocity.X > 0)
direction = FaceDirection.Right;
if (velocity.X < 0)
direction = FaceDirection.Left;
}
HandlePortalCollision(gameTime);
grapplingHook.Update(gameTime);
}
else
{
// respawn if dead
Respawn(gameTime);
}
// update current attack
currentAttack.Update(gameTime);
base.Update(gameTime);
}
/// <summary>
/// Reset the player
/// </summary>
public override void Reset()
{
hitPoints = 100;
mana = 100;
blinkTimer = 0.0f;
invincibleTimer = 0.0f;
isInvincible = false;
direction = FaceDirection.Right;
body.Position = new Vector2(spawnPoint.X, spawnPoint.Y - bodyOffset);
base.Reset();
}
/// <summary>
/// Draw player and associated attacks
/// </summary>
/// <param name="gameTime"></param>
/// <param name="spriteBatch"></param>
public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
currentAttack.Draw(gameTime, spriteBatch);
if (isInvincible)
{
// if we are in the middle of blinking, advance the frame but do not draw
// otherwise, draw
if (blinkTimer <= 0)
{
base.Draw(gameTime, spriteBatch);
blinkTimer = MaxBlinkTime;
}
else
AdvanceAnimationFrame(gameTime, spriteBatch);
}
if (!isInvincible)
{
grapplingHook.Draw(spriteBatch);
// we use a specialized version of draw here for ladders
if (!isOnLadder)
base.Draw(gameTime, spriteBatch);
else
sprite.Draw(gameTime, spriteBatch, Position, SpriteEffects.None, 1f, Math.Abs(velocity.Y) / 1000f, false);
}
}
#endregion
#region Private Methods
/// <summary>
/// Check for and handle collisions with map portals
/// </summary>
private void HandlePortalCollision(GameTime gameTime)
{
// check for portal collisions
int mapPortalCollision = map.GetPortalCollision(this.BoundingRectangle);
// if we collided, get the portal data
if (mapPortalCollision != -1)
{
PortalInfo fromPortal = map.PortalList[mapPortalCollision];
Vector2 prevPosition = body.Position;
if (PortalCollision != null)
PortalCollision(this, new PortalCollisionEventArgs(fromPortal, prevPosition));
movement = Vector2.Zero;
}
}
/// <summary>
/// Respawn the player
/// </summary>
private void Respawn(GameTime gameTime)
{
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
// if we need to wait..
if (deathWaitTimer > 0)
{
deathWaitTimer = Math.Max(0.0f, deathWaitTimer - elapsed);
// Wait for some amount of time.
if (deathWaitTimer <= 0.0f)
{
if (PlayerDeath != null)
PlayerDeath(this);
// reset player
Reset();
}
}
}
#endregion
#region Public Methods
/// <summary>
/// Change avatar location based on map change
/// </summary>
/// <param name="map">The new map</param>
/// <param name="position">The new position</param>
/// <param name="direction">The new direction</param>
public void MapChange(Map map, Vector2 position, FaceDirection direction)
{
this.map = map;
this.body.Position = position;
this.direction = direction;
this.spawnPoint = position;
}
/// <summary>
/// Start a jump
/// </summary>
public void Jump(bool unlockJump)
{
// if jumping is not currently locked
if (!unlockJump)
{
// if on a ladder, release
if (isOnLadder)
isOnLadder = false;
else
{
jumpLocked = true;
isJumping = true;
}
}
// if we are unlocking the jump
if (unlockJump)
{
if (isOnGround || isOnWall)
{
isJumping = false;
jumpLocked = false;
}
}
}
/// <summary>
/// Check if we are colliding with a ladder
/// </summary>
public void CheckForLadder(bool checkBelow)
{
// do not look for ladders below if we are in the air and not on a ladder
if (checkBelow)
if ((!isOnLadder) && (inTheAir))
return;
foreach (Entity entity in gameWorld.EntityList)
{
Ladder ladder = entity as Ladder;
// if the cast was successful, check for collision
if (ladder != null)
{
// for normal checking, we will check all 3 points
// if we only checking below, the bottom point is all that matters
Point top = new Point(BoundingRectangle.Center.X, BoundingRectangle.Top);
Point center = BoundingRectangle.Center;
Point bottom;
// check further below if we are going down
if (checkBelow)
bottom = new Point(BoundingRectangle.Center.X, BoundingRectangle.Bottom + 10);
else
bottom = new Point(BoundingRectangle.Center.X, BoundingRectangle.Bottom);
bool ladderCheck = ((ladder.BoundingRectangle.Contains(top)) ||
(ladder.BoundingRectangle.Contains(center)) ||
(ladder.BoundingRectangle.Contains(bottom)));
// if any of those 3 points pass a collision test, we are on a ladder
if (ladderCheck)
{
// zero out velocity
if (!isOnLadder)
body.LinearVelocity = Vector2.Zero;
// set position
body.Position = new Vector2((float)ladder.BoundingRectangle.Center.X, body.Position.Y);
// we are on ladder
isOnLadder = true;
// break if we hit a ladder
return;
}
}
}
// we aren't on a ladder
isOnLadder = false;
}
/// <summary>
/// Switch the current weapon
/// TODO: Adjust logic to switch in certain direction when 3rd Weapon is implemented
/// </summary>
public void SwitchWeapon(GameTime gameTime, bool releaseSwitch)
{
if (!releaseSwitch) // if this is a new switch
{
isAttacking = false;
// if we didn't just switch our weapon
if (!justSwitched)
{
if (currentWeaponIndex == availableAttacks.Count - 1)
currentWeaponIndex = 0;
else
currentWeaponIndex++;
currentAttack = availableAttacks[currentWeaponIndex];
// DEBUG: Projective test
//AddProjectile(new SmallFireBall(this, new Vector2(1.3f * (float)direction, 0f), new Vector2((float)BoundingRectangle.Center.X, (float)BoundingRectangle.Center.Y)));
}
justSwitched = true;
}
else // release the switch flag
{
justSwitched = false;
}
}
/// <summary>
/// Attack
/// TODO: Logic to separate single fire and charged shots
/// </summary>
public void Attack(GameTime gameTime, bool releaseAttack)
{
if (!releaseAttack) // if this is a new attack
{
if (currentAttack.IsSingleFire)
{
isAttacking = true;
// if we didn't just attack
if (!justAttacked)
{
currentAttack.DoAttack(gameTime);
justAttacked = true;
if (currentAttack.LocksMovement)
runLocked = true;
}
}
else
{
isAttacking = true;
currentAttack.DoAttack(gameTime);
justAttacked = true;
}
base.Attack(gameTime);
}
else // we are finishing an attack
{
isAttacking = false;
justAttacked = false;
}
}
/// <summary>
/// Fire ropeshot (grappling hook)
/// </summary>
public void RopeShot(bool fireRope)
{
if (fireRope)
grapplingHook.Fire();
else
grapplingHook.Disconnect();
}
/// <summary>
/// Do damage to character
/// </summary>
public override void TakeDamage(GameTime gameTime, float damageDealt)
{
// only take damage if not invincible
if (!isInvincible)
{
// stop jumping in preparation for knockback
isJumping = false;
wasJumping = false;
// perform knockback
if (direction == FaceDirection.Left)
knockbackDirection = FaceDirection.Right;
else
knockbackDirection = FaceDirection.Left;
Knockback();
// set invincibility info
invincibleTimer = MaxInvincibleTime;
isInvincible = true;
// process damage
base.TakeDamage(gameTime, damageDealt);
}
}
/// <summary>
/// Take damage from a damage source
/// </summary>
/// <param name="gameTime"></param>
/// <param name="damageDealt">Amount of damage</param>
/// <param name="enemyDirection">Direction damage came from</param>
public override void TakeDamage(GameTime gameTime, Entity entity, float damageDealt, FaceDirection enemyDirection)
{
// only take damage if not invincible
if (!isInvincible)
{
// stop jumping in preparation for knockback
isJumping = false;
wasJumping = false;
// perform knockback
if (!isOnLadder)
{
// if enemy and player are facing same direction but enemy is not moving, or enemey
// and player are moving in same direction reverse knockback direction
if (direction == enemyDirection)
{
if ((entity.Velocity.X > 0) && (Velocity.X > 0) || (entity.Velocity.X < 0) && (Velocity.X < 0) || (entity.Velocity.X == 0))
{
if (enemyDirection == FaceDirection.Right)
enemyDirection = FaceDirection.Left;
else
enemyDirection = FaceDirection.Right;
}
}
}
// release ladder if we are on one
isOnLadder = false;
Knockback(enemyDirection);
// set invincibility info
invincibleTimer = MaxInvincibleTime;
isInvincible = true;
// process damage
base.TakeDamage(gameTime, entity, damageDealt, enemyDirection);
}
}
/// <summary>
/// Kill the player
/// </summary>
/// <param name="gameTime"></param>
public override void Die(GameTime gameTime)
{
if (IsAlive)
{
// play death sound
SoundManager.PlaySound("Player_Death", 1f);
// create explosion at the new position
GameWorldComponent.GameParticleManager.MakeExplosion(body.Position, 0.5f);
deathWaitTimer = MaxDeathWaitTime;
isInvincible = false;
// end attack
Attack(gameTime, true);
isAttacking = false;
justAttacked = false;
// release ropeshot
RopeShot(false);
// fade out
// game.Fade();
}
base.Die(gameTime);
}
#endregion
}
}