From ced3d03bdb3ce866d832e03fb212865140905a9a Mon Sep 17 00:00:00 2001 From: Thomas Leyh Date: Sun, 24 Jul 2016 08:14:18 +0200 Subject: Add project files. --- V3/Objects/AbstractCreature.cs | 911 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 911 insertions(+) create mode 100644 V3/Objects/AbstractCreature.cs (limited to 'V3/Objects/AbstractCreature.cs') diff --git a/V3/Objects/AbstractCreature.cs b/V3/Objects/AbstractCreature.cs new file mode 100644 index 0000000..a4ac419 --- /dev/null +++ b/V3/Objects/AbstractCreature.cs @@ -0,0 +1,911 @@ +using System; +using System.Collections.Generic; +using Castle.Core.Internal; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using V3.Camera; +using V3.Data; +using V3.Map; +using V3.Objects.Movement; +using V3.Objects.Sprite; + +namespace V3.Objects +{ + /// + /// Abstract class for deriving all moving objects in the game, + /// be it the player, his minions or the enemy. + /// + public abstract class AbstractCreature : ICreature + { + // ************************************************** + // Internal variables, managers and values. + protected virtual Point CreatureSize { get; } = new Point(24); + protected virtual Point BoundaryShift { get; } = new Point(-12, -16); + protected virtual Point SelectionSize { get; } = new Point(48, 64); + protected virtual Point SelectionShift { get; } = new Point(-24, -40); + /// + /// Array of sprites for drawing the creature. Can have up to four entries for (ordered): Body, Head, Weapon, Offhand + /// + protected abstract ISpriteCreature[] Sprite { get; } + protected abstract IMovable MovementScheme { get; } + protected abstract CreatureType Type { get; } + private Texture2D mOnePixelTexture; + private Texture2D mSelectionTexture; + private readonly Pathfinder mPathfinder; + private readonly ContentManager mContentManager; + private readonly IOptionsManager mOptionsManager; + private AchievementsAndStatistics mAchievementsAndStatistics; + private static readonly Random sRandom = new Random(); + private static readonly object sYncLock = new object(); + private List mArrowList = new List(); + +#if NO_AUDIO +#else + private SoundEffect mSoundEffect; + private SoundEffectInstance mSoundEffectInstance; + private SoundEffect mSoundEffectHorse; + private SoundEffectInstance mSoundEffectInstanceHorse; + private SoundEffect mSoundEffectKnight; + private SoundEffectInstance mSoundEffectInstanceKnight; + private SoundEffect mSoundEffectFight; + private SoundEffectInstance mSoundEffectInstanceFight; + private SoundEffect mSoundEffectMeatball; + private SoundEffectInstance mSoundEffectInstanceMeatball; +#endif + private bool mIsDead; + //private float mReculate = 1.0f; + + // ************************************************** + // Public variables and important attributes of the creature: + public abstract string Name { get; protected set; } + public abstract int Life { get; protected set; } + public abstract int MaxLife { get; protected set; } + public abstract int Speed { get; } + public abstract int Attack { get; protected set; } + public abstract int AttackRadius { get; protected set; } + public abstract int SightRadius { get; protected set; } + public abstract TimeSpan TotalRecovery { get;} + public abstract TimeSpan Recovery { get; set; } + public Vector2 Position { get; set; } + public Vector2 InitialPosition { private get; set; } + public abstract Faction Faction { get; } + public int Id { get; } + + public bool IsDead + { + get { return Life <= 0; } + } + + public bool IsUpgraded { get; set; } + + public abstract IBuilding IsAttackingBuilding { get; set; } + public MovementDirection MovementDirection { get; set; } + public MovementState MovementState { get; set; } = MovementState.Idle; + public Rectangle SelectionRectangle => new Rectangle(Position.ToPoint() + SelectionShift, SelectionSize); + public Rectangle BoundaryRectangle => new Rectangle(Position.ToPoint() + BoundaryShift, CreatureSize); + public bool IsSelected { get; set; } + private static int sMaxNumberOfSoundHorse; + private static int sMaxNumberOfSoundMeatball; + private static int sMaxNumberOfSoundZombie; + private static int sMaxNumberOfSoundKnight; + + public abstract ICreature IsAttacking { get; set; } + + /// + /// Color of the rectangle displayed when creature is selected. + /// + protected virtual Color SelectionColor + { + get + { + switch (Faction) + { + case Faction.Undead: + return Color.Red; + case Faction.Kingdom: + return Color.Blue; + case Faction.Plebs: + return Color.Green; + default: + return Color.Gray; + } + } + } + + protected AbstractCreature(ContentManager contentManager, Pathfinder pathfinder, IOptionsManager optionsManager, AchievementsAndStatistics achievementsAndStatistics) + { + mPathfinder = pathfinder; + mContentManager = contentManager; + mOptionsManager = optionsManager; + mAchievementsAndStatistics = achievementsAndStatistics; + Id = IdGenerator.GetNextId(); + LoadContent(mContentManager); + } + + /// + /// The specific creature takes damage. If its life falls below zero it dies. + /// + /// Amount of received damage. + public void TakeDamage(int damage) + { + if (Life > 0) + { + Life -= damage; + } + + if (Life <= 0) + { + Die(); + } + } + + /// + /// Loads content files needed for drawing the creature. + /// + /// Content manager used. + public void LoadContent(ContentManager contentManager) + { + Sprite.ForEach(e => e?.Load(contentManager)); + mOnePixelTexture = contentManager.Load("Sprites/WhiteRectangle"); + mSelectionTexture = contentManager.Load("Sprites/selection"); +#if NO_AUDIO +#else + try + { + mSoundEffect = contentManager.Load("Sounds/walking"); + mSoundEffectInstance = mSoundEffect.CreateInstance(); + mSoundEffectHorse = contentManager.Load("Sounds/SkeletonHorse"); + mSoundEffectInstanceHorse = mSoundEffectHorse.CreateInstance(); + mSoundEffectKnight = contentManager.Load("Sounds/Knight"); + mSoundEffectInstanceKnight = mSoundEffectKnight.CreateInstance(); + mSoundEffectFight = contentManager.Load("Sounds/punch"); + mSoundEffectInstanceFight = mSoundEffectFight.CreateInstance(); + mSoundEffectMeatball = contentManager.Load("Sounds/Monster_Gigante-Doberman-1334685792"); + mSoundEffectInstanceMeatball = mSoundEffectMeatball.CreateInstance(); + } + catch (DllNotFoundException) + { + // HACK: ignore sound-related errors as the sound is currently + // not working on the pool computers + } + catch (NoAudioHardwareException) + { + // HACK: ignore sound-related errors as the sound is currently + // not working on the pool computers + } +#endif + } + + /// + /// The creature should move to the specified destination if possible. + /// + /// The desired destination. + public void Move(Vector2 destination) + { + MovementScheme.FindPath(mPathfinder, Position, destination); + } + + public void Update(GameTime gameTime, ICreature playerCharacter, + bool rightButtonPressed, Vector2 rightButtonPosition, Quadtree quadtree, ICamera camera) + { + // If Creature is dead, don't update anymore. + Sprite.ForEach(e => e?.PlayAnimation(gameTime)); + if (mIsDead) + { + return; + } + bool showedByCamera = camera.ScreenRectangle.Contains(Position); +#if NO_AUDIO +#else + + // update the volume according to the current setting + if (mSoundEffectInstance != null) + mSoundEffectInstance.Volume = mOptionsManager.Options.GetEffectiveVolume()*0.1f; + if (mSoundEffectInstanceHorse != null) + mSoundEffectInstanceHorse.Volume = mOptionsManager.Options.GetEffectiveVolume() * 0.07f; + if (mSoundEffectInstanceKnight != null) + mSoundEffectInstanceKnight.Volume = mOptionsManager.Options.GetEffectiveVolume() * 0.07f; + if (mSoundEffectInstanceFight != null) + mSoundEffectInstanceFight.Volume = mOptionsManager.Options.GetEffectiveVolume() * 0.07f; + if (mSoundEffectInstanceMeatball != null) + mSoundEffectInstanceMeatball.Volume = mOptionsManager.Options.GetEffectiveVolume() * 0.07f; +#endif + if (MovementScheme.IsMoving) + { + MovementState = MovementState.Moving; + Position += MovementScheme.GiveNewPosition(Position, Speed); + MovementDirection = MovementScheme.GiveMovementDirection(); +#if NO_AUDIO +#else + try + { + if (showedByCamera) + { + if ((this is Zombie || this is FemalePeasant || this is MalePeasant || this is Necromancer) && + sMaxNumberOfSoundZombie < 3) + { + if (mSoundEffectInstance != null) + { + mSoundEffectInstance.Play(); + sMaxNumberOfSoundZombie++; + } + + } + if (this is SkeletonHorse && sMaxNumberOfSoundHorse < 3) + { + if (mSoundEffectInstanceHorse != null) + { + mSoundEffectInstanceHorse.Play(); + sMaxNumberOfSoundHorse++; + } + + } + if (this is Knight && sMaxNumberOfSoundKnight < 3) + { + if (mSoundEffectInstanceKnight != null) + { + mSoundEffectInstanceKnight.Play(); + sMaxNumberOfSoundKnight++; + } + + } + if (this is Meatball && sMaxNumberOfSoundMeatball < 3) + { + if (mSoundEffectInstanceMeatball != null) + { + mSoundEffectInstanceMeatball.Play(); + sMaxNumberOfSoundMeatball++; + } + + } + } + } + catch (InstancePlayLimitException) + { + // HACK: ignore sound-related errors as the sound is currently + // not working on the pool computers + } +#endif + } + else + { +#if NO_AUDIO +#else + if (mSoundEffectInstance != null) + { + mSoundEffectInstance.Stop(); + sMaxNumberOfSoundZombie--; + } + if (mSoundEffectInstanceHorse != null) + { + mSoundEffectInstanceHorse.Stop(); + sMaxNumberOfSoundHorse--; + } + if (mSoundEffectInstanceKnight != null) + { + mSoundEffectInstanceKnight.Stop(); + sMaxNumberOfSoundKnight--; + } + + if (mSoundEffectInstanceMeatball != null) + { + mSoundEffectInstanceMeatball.Stop(); + sMaxNumberOfSoundMeatball--; + } +#endif + MovementState = MovementState.Idle; + } + if (IsDead) + { + if (mSoundEffectInstance != null) + { + mSoundEffectInstance.Stop(); + sMaxNumberOfSoundZombie--; + } + if (mSoundEffectInstanceHorse != null) + { + mSoundEffectInstanceHorse.Stop(); + sMaxNumberOfSoundHorse--; + } + if (mSoundEffectInstanceKnight != null) + { + mSoundEffectInstanceKnight.Stop(); + sMaxNumberOfSoundKnight--; + } + + if (mSoundEffectInstanceMeatball != null) + { + mSoundEffectInstanceMeatball.Stop(); + sMaxNumberOfSoundMeatball--; + } + } + + /* + * + * Random movement of Zombies + * + */ + #region Random Movement + Ellipse necroArea = new Ellipse(new Vector2((int)playerCharacter.Position.X, (int)playerCharacter.Position.Y), 1280, 640); + float necroDistance = Vector2.Distance(Position, playerCharacter.Position); + List randomMoveVector = new List(); + + + int rndX = RandomNumber(0, 20); + int rndY = RandomNumber(0, 20); + + randomMoveVector.Add(new Vector2(rndX, rndY)); + randomMoveVector.Add(new Vector2(-rndX, -rndY)); + randomMoveVector.Add(new Vector2(rndX, -rndY)); + randomMoveVector.Add(new Vector2(-rndX, rndY)); + + int rndNumber; + + if (!(this is Necromancer) && + Faction.Equals(Faction.Undead) && + MovementState == MovementState.Idle && + !necroArea.Contains(Position)) + { + rndNumber = RandomNumber(1, 60); + if (rndNumber == 7) + { + int rndMoveVector = RandomNumber(0, 400); + Move(Position + randomMoveVector[rndMoveVector%4]); + } + } + if (Faction.Equals(Faction.Plebs)) + { + rndNumber = RandomNumber(1, 60); + if (rndNumber == 7) + { + int rndMoveVector = RandomNumber(0, 400); + Move(Position + randomMoveVector[rndMoveVector%4]); + } + + } + #endregion + + List defenders = new List(); + if (IsAttacking == null) + { + // Get the quadtree of the sight radius. + defenders = quadtree.GetObjectsInRectangle(new Rectangle((int) Position.X - SightRadius/2, (int) Position.Y - SightRadius/2, SightRadius, SightRadius)); + // Returns if nothing is in sight. + if (defenders.Count == 0) return; + } + + bool attacking = false; + + /* + * + * COMMAND ATTACKING OF BUILDINGS + * + */ + #region Command Attack + + // Distance between Undead and Necromancer + if (Faction.Equals(Faction.Undead) && !(this is Necromancer)) + { + // If Undead are in sight distance of the Necromancer, do stuff. + if (IsSelected && (int)necroDistance <= playerCharacter.AttackRadius) + { + // Get all the objects of the quadtree of the sightradius of the Necromancer + if (rightButtonPressed) + { + IsAttackingBuilding = null; + var objectsUnderMouse = quadtree.GetObjectsInRectangle(new Rectangle(rightButtonPosition.ToPoint(), new Point(1, 1))); + foreach (var obj in objectsUnderMouse) + { + var building = obj as IBuilding; + if (building == null || !building.BoundaryRectangle.Contains(rightButtonPosition) || building.Robustness == 0) continue; + IsAttackingBuilding = building; + } + } + } + if (IsAttackingBuilding != null ) + { + if ((int)Vector2.Distance(Position, ComputeMoVector(IsAttackingBuilding)) <= AttackRadius && IsAttackingBuilding.Robustness >= 0) // (int)Vector2.Distance(Position, IsAttackingBuilding.Position) <= AttackRadius * 2 + { + //var building = IsAttacking as IBuilding; + // Attacking + Recovery -= gameTime.ElapsedGameTime; + if (Recovery < TimeSpan.Zero) + { + Recovery = TimeSpan.Zero; + } + MovementState = MovementState.Attacking; // PlayAnimationOnce(MovementState.Attacking, TotalRecovery); + if (Recovery <= TimeSpan.Zero) + { + IsAttackingBuilding.TakeDamage(Attack); + Recovery = TotalRecovery; // Cooldown + + // Throw an arrow + if (AttackRadius > 300) + CreateArrow(Position, IsAttackingBuilding.Position); + } + // If house is dead. + if (IsAttackingBuilding.Robustness <= 20) + { + MovementState = MovementState.Idle; + + // Undead getting stronger if building is dead. + switch (IsAttackingBuilding.Name) + { + case "Schmiede": + if (IsAttackingBuilding.MaxGivesWeapons > 0 && !IsUpgraded) + { + MaxLife += 20; + Life += 20; + IsUpgraded = true; + IsAttackingBuilding.UpgradeCounter(); + } + break; + case "Holzhaus": + if (IsAttackingBuilding.MaxGivesWeapons > 0) + { + if (this is Skeleton && !IsUpgraded) + { + ChangeEquipment(EquipmentType.Body, new SkeletonArcherSprite()); + AttackRadius += 500; + SightRadius += 500; + IsUpgraded = true; + IsAttackingBuilding.UpgradeCounter(); + } + if (this is Zombie && !IsUpgraded) + { + ChangeEquipment(EquipmentType.Body, new ZombieWithClubSprite()); + Attack += 10; + IsUpgraded = true; + IsAttackingBuilding.UpgradeCounter(); + } + } + break; + } + + if (IsAttackingBuilding.Robustness <= 0) IsAttackingBuilding = null; + } + } + else + { + Move(ComputeMoVector(IsAttackingBuilding)); + } + } + } + + #endregion + foreach (var arrow in mArrowList) + { + arrow.UpdateArrow(); + } + /* + * + * AUTO ATTACKING OF CREATURES + * + */ + #region Auto-Attack + + // Don't auto attack if Zombie got command from Necromancer. + if (IsAttackingBuilding != null) return; + + if (IsAttacking == null) + { + List creatures = new List(); + foreach (var defender in defenders) + { + var creature = defender as ICreature; + if (creature == null) continue; + creatures.Add(creature); + } + + ICreature attackableCreature = null; + float sightDistance = SightRadius; + + // Compute the nearest enemy. + foreach (var defender in creatures) + { + // Zombies and Knights only. + if (Faction.Equals(Faction.Undead) || Faction.Equals(Faction.Kingdom)) + { + // If attacker-type == defender-type go to next possible defender. + if ((Faction.Equals(Faction.Undead) && !defender.Faction.Equals(Faction.Undead)) || + (Faction.Equals(Faction.Kingdom) && defender.Faction.Equals(Faction.Undead))) + { + if (defender.Life > 0) + { + // Compute the distance of attacker and possible defender. + float distanceTest = Vector2.Distance(Position, defender.Position); + + if (distanceTest <= sightDistance) + { + sightDistance = distanceTest; + attackableCreature = defender; + } + // IsAttacking = defender; + if (attackableCreature != null) + { + IsAttacking = defender; + + } + } + } + } + } + if (attackableCreature == null) + { + IsAttacking = null; + } + } + + if (IsAttacking == null) return; + /* + * + * ATTACKING OF CREATURES + * + */ + if ((int)Vector2.Distance(Position, IsAttacking.Position) > AttackRadius && + !MovementScheme.IsMoving) + { + // Do not move if attacker is already in attackrange + if (!attacking) + { + Move(ComputeMoVector(IsAttacking)); + } + } + + if (IsAttacking.IsDead || (int)Vector2.Distance(Position, IsAttacking.Position) > SightRadius) + { + IsAttacking = null; + MovementState = MovementState.Idle; + return; + } + // Attacking + Recovery -= gameTime.ElapsedGameTime; + if (Recovery < TimeSpan.Zero) + { + Recovery = TimeSpan.Zero; + } + + // If attacker is in the attack radius of defender + if ((int)Vector2.Distance(Position, IsAttacking.Position) <= AttackRadius) + { + if (Recovery <= TimeSpan.Zero) + { + IsAttacking.TakeDamage(Attack); + PlayAnimationOnce(MovementState.Attacking, TotalRecovery); + Recovery = TotalRecovery; // Cooldown + + // Throw an arrow + if (AttackRadius > 300 && IsAttacking != null) + CreateArrow(Position, IsAttacking.Position); + } + } + + #endregion + } + + public void PlayAnimationOnce(MovementState animation, TimeSpan duration) + { + Sprite.ForEach(e => e.PlayOnce(animation, duration)); + } + + /// + /// Draw the creature on the screen. + /// + /// Sprite batch used for drawing. + public void Draw(SpriteBatch spriteBatch) + { + if (IsSelected) DrawSelection(spriteBatch); + Sprite.ForEach(e => e?.Draw(spriteBatch, Position, MovementState, MovementDirection)); + if (IsSelected) DrawLifeRectangle(spriteBatch); + foreach (var arrow in mArrowList) + { + arrow.DrawArrow(spriteBatch); + } + } + + public void DrawStatic(SpriteBatch spriteBatch, Point position) + { + Sprite.ForEach(e => e?.DrawStatic(spriteBatch, position, MovementState.Idle, MovementDirection.S)); + } + + protected virtual void Die() + { + if (Faction == Faction.Plebs || Faction == Faction.Kingdom) + { + mAchievementsAndStatistics.mHundredDeadCorpses += 1; + mAchievementsAndStatistics.mUndeadArmy += 1; + mAchievementsAndStatistics.mRightHandOfDeath += 1; + mAchievementsAndStatistics.mKilledCreatures += 1; + } + else if (Faction == Faction.Undead) + { + mAchievementsAndStatistics.mLostServants += 1; + } + mIsDead = true; + Life = 0; + MovementState = MovementState.Dying; + } + + private void DrawSelection(SpriteBatch spriteBatch) + { + // TODO: Remove the magic vector and adjust position. + spriteBatch.Draw(mSelectionTexture, Position - new Vector2(32, 16), SelectionColor); + /* + spriteBatch.Draw(mOnePixelTexture, new Rectangle(BoundaryRectangle.X, BoundaryRectangle.Y, BoundaryRectangle.Width, 2), SelectionColor); + spriteBatch.Draw(mOnePixelTexture, new Rectangle(BoundaryRectangle.X, BoundaryRectangle.Y + BoundaryRectangle.Height, BoundaryRectangle.Width, 2), SelectionColor); + spriteBatch.Draw(mOnePixelTexture, new Rectangle(BoundaryRectangle.X, BoundaryRectangle.Y, 2, BoundaryRectangle.Height), SelectionColor); + spriteBatch.Draw(mOnePixelTexture, new Rectangle(BoundaryRectangle.X + BoundaryRectangle.Width, BoundaryRectangle.Y, 2, BoundaryRectangle.Height), SelectionColor); + */ + } + + private void DrawLifeRectangle(SpriteBatch spriteBatch) + { + var backgroundRectangle = new Rectangle(SelectionRectangle.X, SelectionRectangle.Y - 12, SelectionRectangle.Width, SelectionRectangle.Height / 10); + var lifeBarRectangle = new Rectangle(SelectionRectangle.X + 2, SelectionRectangle.Y - 11, SelectionRectangle.Width - 3, SelectionRectangle.Height / 10 - 2); + spriteBatch.Draw(mOnePixelTexture, backgroundRectangle, Color.Black * 0.7f); + spriteBatch.Draw(mOnePixelTexture, new Rectangle(lifeBarRectangle.X, lifeBarRectangle.Y, lifeBarRectangle.Width * Life / MaxLife, lifeBarRectangle.Height), Color.Firebrick); + } + + /// + /// Change the equipment/sprite of the creature to something other. + /// + /// Which part of the equipment should be changed. + /// Which sprite to use instead. + public void ChangeEquipment(EquipmentType equipmentType, ISpriteCreature sprite) + { + if (Sprite.Length > (int) equipmentType) + { + sprite.Load(mContentManager); + Sprite[(int)equipmentType] = sprite; + } +#if DEBUG + else + { + throw new Exception("Creature does not have a " + equipmentType.ToString() + " slot. For further information talk to Thomas."); + } +#endif + } + + /// + /// Returns the object instance without modifications. + /// + /// This object. + public virtual IGameObject GetSelf() + { + return this; + } + + public void ResetPosition() + { + Position = InitialPosition; + } + + #region Compute Move Distance + /// + /// Returns vector for the moving distance to attack. + /// + /// Vector. + private Vector2 ComputeMoVector(IGameObject gameObject) + { + Vector2 goToPosition = new Vector2(Position.X, Position.Y); + + /* + * P + * D + * + */ + if (Position.X <= gameObject.BoundaryRectangle.Left && + Position.Y <= gameObject.BoundaryRectangle.Top) + { + goToPosition = + new Vector2(gameObject.BoundaryRectangle.Left - AttackRadius * 0.5f, + gameObject.BoundaryRectangle.Top - AttackRadius * 0.5f); + } + /* + * P + * D + * + */ + if (Position.X >= gameObject.BoundaryRectangle.Left && + Position.X <= gameObject.BoundaryRectangle.Right && + Position.Y <= gameObject.BoundaryRectangle.Top) + { + goToPosition = new Vector2(Position.X, + gameObject.BoundaryRectangle.Top - AttackRadius * 0.5f); + } + /* + * P + * D + * + */ + if (Position.X >= gameObject.BoundaryRectangle.Right && + Position.Y <= gameObject.BoundaryRectangle.Top) + { + goToPosition = + new Vector2(gameObject.BoundaryRectangle.Right - AttackRadius * 0.5f, + gameObject.BoundaryRectangle.Top - AttackRadius * 0.5f); + } + /* + * + * D P + * + */ + if (Position.X >= gameObject.BoundaryRectangle.Right && + Position.Y <= gameObject.BoundaryRectangle.Bottom && + Position.Y >= gameObject.BoundaryRectangle.Top) + { + goToPosition = + new Vector2(gameObject.BoundaryRectangle.Right - AttackRadius * 0.5f, + Position.Y); + } + /* + * + * D + * P + */ + if (Position.X >= gameObject.BoundaryRectangle.Right && + Position.Y >= gameObject.BoundaryRectangle.Bottom) + { + goToPosition = + new Vector2(gameObject.BoundaryRectangle.Right - AttackRadius * 0.5f, + gameObject.BoundaryRectangle.Bottom - AttackRadius * 0.5f); + } + /* + * + * D + * P + */ + if (Position.X >= gameObject.BoundaryRectangle.Left && + Position.X <= gameObject.BoundaryRectangle.Right && + Position.Y >= gameObject.BoundaryRectangle.Bottom) + { + goToPosition = new Vector2(Position.X, + gameObject.BoundaryRectangle.Bottom - AttackRadius * 0.5f); + } + /* + * + * D + * P + */ + if (Position.X <= gameObject.BoundaryRectangle.Left && + Position.Y >= gameObject.BoundaryRectangle.Bottom) + { + goToPosition = + new Vector2(gameObject.BoundaryRectangle.Left - AttackRadius * 0.5f, + gameObject.BoundaryRectangle.Bottom - AttackRadius * 0.5f); + } + /* + * + * P D + * + */ + if (Position.X <= gameObject.BoundaryRectangle.Left && + Position.Y <= gameObject.BoundaryRectangle.Bottom && + Position.Y >= gameObject.BoundaryRectangle.Top) + { + goToPosition = + new Vector2(gameObject.BoundaryRectangle.Left - AttackRadius * 0.5f, + Position.Y); + } + + return goToPosition; + } + #endregion + + public override bool Equals(Object obj) + { + if (obj == null) + return false; + if (obj == this) + return true; + if (!(obj is IGameObject)) + return false; + return Id.Equals(((IGameObject) obj).Id); + } + + public override int GetHashCode() + { + return Id; + } + + /// + /// Returns synchronized random value. + /// + /// Vector. + /// Lower bound for random value. + /// Upper bound +1 for random value. + private static int RandomNumber(int min, int max) + { + // synchronize + lock (sYncLock) + { + return sRandom.Next(min, max); + } + } + + public void Heal(int amount) + { + if (Life + amount < MaxLife) + { + Life += amount; + } + else + { + Life = MaxLife; + } + } + + public void Empower(int modifier) + { + MaxLife *= modifier; + Life *= modifier; + } + + private void CreateArrow(Vector2 start, Vector2 goal) + { + if (mArrowList.Count >= 60) mArrowList.Clear(); + Arrow arrow = new Arrow(start, goal); + arrow.LoadArrow(mContentManager); + mArrowList.Add(arrow); + } + + /// + /// Save this creature’s data to a CreatureData object. + /// + /// the CreatureData object with the status of this creature + public virtual CreatureData SaveData() + { + var creatureData = new CreatureData(); + creatureData.Type = Type; + creatureData.Id = Id; + creatureData.Life = Life; + creatureData.MaxLife = MaxLife; + creatureData.Attack = Attack; + creatureData.Recovery = Recovery; + creatureData.IsUpgraded = IsUpgraded; + creatureData.PositionX = Position.X; + creatureData.PositionY = Position.Y; + creatureData.MovementDirection = MovementDirection; + creatureData.MovementState = MovementState; + creatureData.MovementData = MovementScheme.SaveData(); + // references + if (IsAttacking != null) + creatureData.IsAttackingId = IsAttacking.Id; + return creatureData; + } + + /// + /// Restore the creature's state from the given data. + /// + /// the state of the creature to restore + public virtual void LoadData(CreatureData creatureData) + { + // ID is set by IdGenerator.SetIdOnce + Life = creatureData.Life; + MaxLife = creatureData.MaxLife; + Attack = creatureData.Attack; + Recovery = creatureData.Recovery; + IsUpgraded = creatureData.IsUpgraded; + Position = new Vector2(creatureData.PositionX, creatureData.PositionY); + MovementDirection = creatureData.MovementDirection; + MovementState = creatureData.MovementState; + MovementScheme.LoadData(creatureData.MovementData); + + if (Life <= 0) + Die(); + } + + /// + /// Restore the creature's references to other creatures from the given data. + /// + /// the state of the creature to restore + /// the list of all creatures by ID + public virtual void LoadReferences(CreatureData data, Dictionary creatures) + { + if (creatures.ContainsKey(data.IsAttackingId)) + IsAttacking = creatures[data.IsAttackingId]; + } + } +} -- cgit v1.2.1