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]; } } }