aboutsummaryrefslogtreecommitdiff
path: root/V3/Objects
diff options
context:
space:
mode:
Diffstat (limited to 'V3/Objects')
-rw-r--r--V3/Objects/AbstractBuilding.cs110
-rw-r--r--V3/Objects/AbstractCreature.cs911
-rw-r--r--V3/Objects/Arrow.cs109
-rw-r--r--V3/Objects/BuildingState.cs8
-rw-r--r--V3/Objects/Castle.cs15
-rw-r--r--V3/Objects/CreatureFactory.cs127
-rw-r--r--V3/Objects/FemalePeasant.cs50
-rw-r--r--V3/Objects/Forge.cs19
-rw-r--r--V3/Objects/IBasicCreatureFactory.cs28
-rw-r--r--V3/Objects/IBuilding.cs20
-rw-r--r--V3/Objects/ICreature.cs120
-rw-r--r--V3/Objects/IGameObject.cs43
-rw-r--r--V3/Objects/IObjectsManager.cs131
-rw-r--r--V3/Objects/IdGenerator.cs59
-rw-r--r--V3/Objects/King.cs33
-rw-r--r--V3/Objects/KingsGuard.cs62
-rw-r--r--V3/Objects/Knight.cs65
-rw-r--r--V3/Objects/MalePeasant.cs49
-rw-r--r--V3/Objects/Meatball.cs36
-rw-r--r--V3/Objects/Movement.cs20
-rw-r--r--V3/Objects/Movement/CountStepsMovement.cs16
-rw-r--r--V3/Objects/Movement/IMovable.cs43
-rw-r--r--V3/Objects/Movement/PlayerMovement.cs155
-rw-r--r--V3/Objects/Necromancer.cs61
-rw-r--r--V3/Objects/ObjectsManager.cs314
-rw-r--r--V3/Objects/Prince.cs33
-rw-r--r--V3/Objects/Selection.cs483
-rw-r--r--V3/Objects/Skeleton.cs65
-rw-r--r--V3/Objects/SkeletonElite.cs19
-rw-r--r--V3/Objects/SkeletonHorse.cs87
-rw-r--r--V3/Objects/Sprite/AbstractSpriteCreature.cs201
-rw-r--r--V3/Objects/Sprite/ArrowSprite.cs42
-rw-r--r--V3/Objects/Sprite/BucklerFemaleSprite.cs7
-rw-r--r--V3/Objects/Sprite/BucklerSprite.cs7
-rw-r--r--V3/Objects/Sprite/ChainFemaleSprite.cs7
-rw-r--r--V3/Objects/Sprite/ChainSprite.cs7
-rw-r--r--V3/Objects/Sprite/ClothFemaleSprite.cs7
-rw-r--r--V3/Objects/Sprite/ClothSprite.cs7
-rw-r--r--V3/Objects/Sprite/EquipmentType.cs10
-rw-r--r--V3/Objects/Sprite/HeadBaldSprite.cs10
-rw-r--r--V3/Objects/Sprite/HeadChainFemaleSprite.cs7
-rw-r--r--V3/Objects/Sprite/HeadChainSprite.cs7
-rw-r--r--V3/Objects/Sprite/HeadFemaleSprite.cs7
-rw-r--r--V3/Objects/Sprite/HeadPlateFemaleSprite.cs7
-rw-r--r--V3/Objects/Sprite/HeadPlateSprite.cs7
-rw-r--r--V3/Objects/Sprite/HeadSprite.cs7
-rw-r--r--V3/Objects/Sprite/ISpriteCreature.cs53
-rw-r--r--V3/Objects/Sprite/KingSprite.cs7
-rw-r--r--V3/Objects/Sprite/LongswordFemaleSprite.cs7
-rw-r--r--V3/Objects/Sprite/LongswordSprite.cs7
-rw-r--r--V3/Objects/Sprite/MeatballSprite.cs11
-rw-r--r--V3/Objects/Sprite/NecromancerFemaleSprite.cs7
-rw-r--r--V3/Objects/Sprite/NecromancerSprite.cs7
-rw-r--r--V3/Objects/Sprite/NudeFemaleSprite.cs7
-rw-r--r--V3/Objects/Sprite/NudeSprite.cs7
-rw-r--r--V3/Objects/Sprite/PlateFemaleSprite.cs7
-rw-r--r--V3/Objects/Sprite/PlateSprite.cs7
-rw-r--r--V3/Objects/Sprite/PrinceSprite.cs7
-rw-r--r--V3/Objects/Sprite/ShieldFemaleSprite.cs7
-rw-r--r--V3/Objects/Sprite/ShieldSprite.cs7
-rw-r--r--V3/Objects/Sprite/ShortswordFemaleSprite.cs7
-rw-r--r--V3/Objects/Sprite/ShortswordSprite.cs7
-rw-r--r--V3/Objects/Sprite/SkeletonArcherSprite.cs10
-rw-r--r--V3/Objects/Sprite/SkeletonEliteSprite.cs9
-rw-r--r--V3/Objects/Sprite/SkeletonHorseSprite.cs7
-rw-r--r--V3/Objects/Sprite/SkeletonRiderSprite.cs10
-rw-r--r--V3/Objects/Sprite/SkeletonSprite.cs9
-rw-r--r--V3/Objects/Sprite/StaffFemaleSprite.cs7
-rw-r--r--V3/Objects/Sprite/StaffSprite.cs7
-rw-r--r--V3/Objects/Sprite/ZombieSprite.cs11
-rw-r--r--V3/Objects/Sprite/ZombieWithClubSprite.cs11
-rw-r--r--V3/Objects/TextureObject.cs76
-rw-r--r--V3/Objects/Transformation.cs102
-rw-r--r--V3/Objects/Woodhouse.cs16
-rw-r--r--V3/Objects/Zombie.cs45
75 files changed, 4120 insertions, 0 deletions
diff --git a/V3/Objects/AbstractBuilding.cs b/V3/Objects/AbstractBuilding.cs
new file mode 100644
index 0000000..bc6061c
--- /dev/null
+++ b/V3/Objects/AbstractBuilding.cs
@@ -0,0 +1,110 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace V3.Objects
+{
+ public abstract class AbstractBuilding : IBuilding
+ {
+ private readonly string mTextureName;
+ private Texture2D mTexture;
+ private readonly BuildingFace mFacing;
+ private Rectangle mTilesetRectangle;
+
+ public Vector2 Position { get; set; }
+ protected abstract int MaxRobustness { get; }
+ public abstract int Robustness { get; protected set; }
+ public abstract string Name { get; }
+ public virtual int MaxGivesWeapons { get; protected set; }
+ private bool mIsDestroyed;
+ public Rectangle BoundaryRectangle { get; }
+ public int Id { get; }
+
+ protected AbstractBuilding(Vector2 position, Rectangle size, string textureName, BuildingFace facing)
+ {
+ Position = position;
+ Id = IdGenerator.GetNextId();
+ // TODO: Hella ugly code
+ mTilesetRectangle = size;
+ // Boundary rectangle is smaller than the texture size:
+ if (this is Castle)
+ {
+ BoundaryRectangle = new Rectangle(size.X + 96, size.Y + 296, 900, 500);
+ }
+ else
+ {
+ BoundaryRectangle = new Rectangle(size.X, size.Y + size.Height / 2, size.Width * 4 / 5, size.Height / 2);
+ }
+ mTextureName = textureName;
+ mFacing = facing;
+ }
+
+ public void Draw(SpriteBatch spriteBatch)
+ {
+ int status = 0;
+ if (mIsDestroyed)
+ {
+ status = 2;
+ }
+ else if (Robustness < MaxRobustness / 2)
+ {
+ status = 1;
+ }
+ Rectangle source = new Rectangle(status * mTilesetRectangle.Width, (mFacing == BuildingFace.SW ? 0 : 1) * mTilesetRectangle.Height + (this is Forge ? 384 : 0), mTilesetRectangle.Width, mTilesetRectangle.Height);
+ spriteBatch.Draw(mTexture, mTilesetRectangle, source, Color.White);
+ }
+
+
+ public void LoadContent(ContentManager contentManager)
+ {
+ mTexture = contentManager.Load<Texture2D>("Textures/" + mTextureName);
+ //mOnePixelTexture = contentManager.Load<Texture2D>("Sprites/WhiteRectangle");
+ }
+
+ public void TakeDamage(int damage)
+ {
+ if (Robustness > 0)
+ {
+ Robustness -= damage;
+ }
+
+ if (Robustness <= 0)
+ {
+ Destroyed();
+ }
+ }
+
+ public void UpgradeCounter()
+ {
+ MaxGivesWeapons -= 1;
+ }
+
+
+ private void Destroyed()
+ {
+ mIsDestroyed = true;
+ }
+
+ public IGameObject GetSelf()
+ {
+ return this;
+ }
+
+ 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;
+ }
+ }
+}
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
+{
+ /// <summary>
+ /// Abstract class for deriving all moving objects in the game,
+ /// be it the player, his minions or the enemy.
+ /// </summary>
+ 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);
+ /// <summary>
+ /// Array of sprites for drawing the creature. Can have up to four entries for (ordered): Body, Head, Weapon, Offhand
+ /// </summary>
+ 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<Arrow> mArrowList = new List<Arrow>();
+
+#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; }
+
+ /// <summary>
+ /// Color of the rectangle displayed when creature is selected.
+ /// </summary>
+ 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);
+ }
+
+ /// <summary>
+ /// The specific creature takes damage. If its life falls below zero it dies.
+ /// </summary>
+ /// <param name="damage">Amount of received damage.</param>
+ public void TakeDamage(int damage)
+ {
+ if (Life > 0)
+ {
+ Life -= damage;
+ }
+
+ if (Life <= 0)
+ {
+ Die();
+ }
+ }
+
+ /// <summary>
+ /// Loads content files needed for drawing the creature.
+ /// </summary>
+ /// <param name="contentManager">Content manager used.</param>
+ public void LoadContent(ContentManager contentManager)
+ {
+ Sprite.ForEach(e => e?.Load(contentManager));
+ mOnePixelTexture = contentManager.Load<Texture2D>("Sprites/WhiteRectangle");
+ mSelectionTexture = contentManager.Load<Texture2D>("Sprites/selection");
+#if NO_AUDIO
+#else
+ try
+ {
+ mSoundEffect = contentManager.Load<SoundEffect>("Sounds/walking");
+ mSoundEffectInstance = mSoundEffect.CreateInstance();
+ mSoundEffectHorse = contentManager.Load<SoundEffect>("Sounds/SkeletonHorse");
+ mSoundEffectInstanceHorse = mSoundEffectHorse.CreateInstance();
+ mSoundEffectKnight = contentManager.Load<SoundEffect>("Sounds/Knight");
+ mSoundEffectInstanceKnight = mSoundEffectKnight.CreateInstance();
+ mSoundEffectFight = contentManager.Load<SoundEffect>("Sounds/punch");
+ mSoundEffectInstanceFight = mSoundEffectFight.CreateInstance();
+ mSoundEffectMeatball = contentManager.Load<SoundEffect>("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
+ }
+
+ /// <summary>
+ /// The creature should move to the specified destination if possible.
+ /// </summary>
+ /// <param name="destination">The desired destination.</param>
+ 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<Vector2> randomMoveVector = new List<Vector2>();
+
+
+ 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<IGameObject> defenders = new List<IGameObject>();
+ 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<ICreature> creatures = new List<ICreature>();
+ 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));
+ }
+
+ /// <summary>
+ /// Draw the creature on the screen.
+ /// </summary>
+ /// <param name="spriteBatch">Sprite batch used for drawing.</param>
+ 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);
+ }
+
+ /// <summary>
+ /// Change the equipment/sprite of the creature to something other.
+ /// </summary>
+ /// <param name="equipmentType">Which part of the equipment should be changed.</param>
+ /// <param name="sprite">Which sprite to use instead.</param>
+ 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
+ }
+
+ /// <summary>
+ /// Returns the object instance without modifications.
+ /// </summary>
+ /// <returns>This object.</returns>
+ public virtual IGameObject GetSelf()
+ {
+ return this;
+ }
+
+ public void ResetPosition()
+ {
+ Position = InitialPosition;
+ }
+
+ #region Compute Move Distance
+ /// <summary>
+ /// Returns vector for the moving distance to attack.
+ /// </summary>
+ /// <returns>Vector.</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Returns synchronized random value.
+ /// </summary>
+ /// <returns>Vector.</returns>
+ /// <param name="min">Lower bound for random value.</param>
+ /// <param name="max">Upper bound +1 for random value.</param>
+ 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);
+ }
+
+ /// <summary>
+ /// Save this creature’s data to a CreatureData object.
+ /// </summary>
+ /// <returns>the CreatureData object with the status of this creature</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Restore the creature's state from the given data.
+ /// </summary>
+ /// <param name="creatureData">the state of the creature to restore</param>
+ 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();
+ }
+
+ /// <summary>
+ /// Restore the creature's references to other creatures from the given data.
+ /// </summary>
+ /// <param name="data">the state of the creature to restore</param>
+ /// <param name="creatures">the list of all creatures by ID</param>
+ public virtual void LoadReferences(CreatureData data, Dictionary<int, ICreature> creatures)
+ {
+ if (creatures.ContainsKey(data.IsAttackingId))
+ IsAttacking = creatures[data.IsAttackingId];
+ }
+ }
+}
diff --git a/V3/Objects/Arrow.cs b/V3/Objects/Arrow.cs
new file mode 100644
index 0000000..57200b2
--- /dev/null
+++ b/V3/Objects/Arrow.cs
@@ -0,0 +1,109 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ public sealed class Arrow
+ {
+ //Drawing an arrow
+ private const int SpeedModifier = 10;
+ private readonly ArrowSprite mArrow;
+ private Vector2 mArrowPosition;
+ private Vector2 mArrowGoal;
+ private Vector2 mDirection;
+ private bool mArrowDraw;
+ private readonly MovementDirection mMovementDirection;
+
+ public Arrow(Vector2 start, Vector2 goal)
+ {
+ mArrowPosition = start;
+ mArrowGoal = goal;
+ mDirection = goal - start;
+ mDirection.Normalize();
+ mMovementDirection = GiveMovementDirection(mDirection);
+ mArrow = new ArrowSprite();
+ mArrowDraw = true;
+ }
+
+ public void LoadArrow(ContentManager contentManager)
+ {
+ mArrow.Load(contentManager);
+ }
+
+ public void DrawArrow(SpriteBatch spriteBatch)
+ {
+ if (mArrowDraw)
+ mArrow.Draw(spriteBatch, mArrowPosition, MovementState.Idle, mMovementDirection);
+ }
+
+ /// <summary>
+ /// The moving for arrow
+ /// </summary>
+ public void UpdateArrow()
+ {
+ if (mArrowDraw)
+ mArrowPosition += mDirection * SpeedModifier;
+
+ if (Vector2.Distance(mArrowPosition, mArrowGoal) < 1f * SpeedModifier)
+ mArrowDraw = false;
+ }
+
+ /// <summary>
+ /// Calculates the direction the creature is looking when moving.
+ /// Method copied from PlayerMovement class.
+ /// </summary>
+ private MovementDirection GiveMovementDirection(Vector2 direction)
+ {
+ // |\
+ // |α\ α == 22.5°
+ // b| \ 1 β == 67.5°
+ // | β\
+ // ──────
+ // a
+ const float b = 0.92f; // b == sin β
+ const float a = 0.38f; // a == sin α
+ MovementDirection movementDirection;
+ if (direction.X < -b)
+ {
+ movementDirection = MovementDirection.W;
+ }
+ else if (direction.X > b)
+ {
+ movementDirection = MovementDirection.O;
+ }
+ else if (direction.Y > 0)
+ {
+ if (direction.X < -a)
+ {
+ movementDirection = MovementDirection.SW;
+ }
+ else if (direction.X > a)
+ {
+ movementDirection = MovementDirection.SO;
+ }
+ else
+ {
+ movementDirection = MovementDirection.S;
+ }
+ }
+ else
+ {
+ if (direction.X < -a)
+ {
+ movementDirection = MovementDirection.NW;
+ }
+ else if (direction.X > a)
+ {
+ movementDirection = MovementDirection.NO;
+ }
+ else
+ {
+ movementDirection = MovementDirection.N;
+ }
+ }
+ return movementDirection;
+ }
+ }
+}
diff --git a/V3/Objects/BuildingState.cs b/V3/Objects/BuildingState.cs
new file mode 100644
index 0000000..79c621e
--- /dev/null
+++ b/V3/Objects/BuildingState.cs
@@ -0,0 +1,8 @@
+// ReSharper disable InconsistentNaming
+namespace V3.Objects
+{
+ public enum BuildingFace
+ {
+ SW, NO
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Castle.cs b/V3/Objects/Castle.cs
new file mode 100644
index 0000000..fd04ddd
--- /dev/null
+++ b/V3/Objects/Castle.cs
@@ -0,0 +1,15 @@
+using Microsoft.Xna.Framework;
+
+namespace V3.Objects
+{
+ public sealed class Castle : AbstractBuilding
+ {
+ public override string Name { get; } = "Burg";
+ protected override int MaxRobustness { get; } = 800;
+ public override int Robustness { get; protected set; } = 800;
+
+ public Castle(Vector2 position, Rectangle size, string textureName, BuildingFace facing) : base(position, size, textureName, facing)
+ {
+ }
+ }
+}
diff --git a/V3/Objects/CreatureFactory.cs b/V3/Objects/CreatureFactory.cs
new file mode 100644
index 0000000..238ea41
--- /dev/null
+++ b/V3/Objects/CreatureFactory.cs
@@ -0,0 +1,127 @@
+using System;
+using Microsoft.Xna.Framework;
+using V3.Data;
+
+namespace V3.Objects
+{
+ // ReSharper disable once ClassNeverInstantiated.Global
+ public sealed class CreatureFactory
+ {
+ private readonly IBasicCreatureFactory mFactory;
+ private readonly Random mRnd = new Random();
+
+ public CreatureFactory(IBasicCreatureFactory factory)
+ {
+ mFactory = factory;
+ }
+
+ public ICreature CreateCreature(CreatureType type, int id)
+ {
+ IdGenerator.SetIdOnce(id);
+ switch (type)
+ {
+ case CreatureType.FemalePeasant:
+ return mFactory.CreateFemalePeasant();
+ case CreatureType.King:
+ return mFactory.CreateKing();
+ case CreatureType.KingsGuard:
+ return mFactory.CreateKingsGuard();
+ case CreatureType.Knight:
+ return mFactory.CreateKnight();
+ case CreatureType.MalePeasant:
+ return mFactory.CreateMalePeasant();
+ case CreatureType.Meatball:
+ return mFactory.CreateMeatball();
+ case CreatureType.Necromancer:
+ return mFactory.CreateNecromancer();
+ case CreatureType.Prince:
+ return mFactory.CreatePrince();
+ case CreatureType.Skeleton:
+ return mFactory.CreateSkeleton();
+ case CreatureType.Zombie:
+ return mFactory.CreateZombie();
+ default:
+ IdGenerator.ClearIdOnce();
+ return null;
+ }
+ }
+
+ public MalePeasant CreateMalePeasant(Vector2 position, MovementDirection movementDirection)
+ {
+ return CreateCreature(mFactory.CreateMalePeasant(), position, movementDirection);
+ }
+
+ public FemalePeasant CreateFemalePeasant(Vector2 position, MovementDirection movementDirection)
+ {
+ return CreateCreature(mFactory.CreateFemalePeasant(), position, movementDirection);
+ }
+
+ public Necromancer CreateNecromancer(Vector2 position, MovementDirection movementDirection)
+ {
+ return CreateCreature(mFactory.CreateNecromancer(), position, movementDirection);
+ }
+
+ public Skeleton CreateSkeleton(Vector2 position, MovementDirection movementDirection)
+ {
+ return CreateCreature(mFactory.CreateSkeleton(), position, movementDirection);
+ }
+
+ public SkeletonElite CreateSkeletonElite(Vector2 position, MovementDirection movementDirection)
+ {
+ return CreateCreature(mFactory.CreateSkeletonElite(), position, movementDirection);
+ }
+
+ public Zombie CreateZombie(Vector2 position, MovementDirection movementDirection)
+ {
+ return CreateCreature(mFactory.CreateZombie(), position, movementDirection);
+ }
+
+ public Knight CreateKnight(Vector2 position, MovementDirection movementDirection)
+ {
+ Knight knight = CreateCreature(mFactory.CreateKnight(), position, movementDirection);
+ if (mRnd.Next(3) == 0)
+ {
+ knight.MakeFemale();
+ }
+ return knight;
+ }
+
+ public KingsGuard CreateKingsGuard(Vector2 position, MovementDirection movementDirection)
+ {
+ KingsGuard guard = CreateCreature(mFactory.CreateKingsGuard(), position, movementDirection);
+ if (mRnd.Next(3) == 0)
+ {
+ guard.MakeFemale();
+ }
+ return guard;
+ }
+
+ public SkeletonHorse CreateSkeletonHorse(Vector2 position, MovementDirection movementDirection)
+ {
+ return CreateCreature(mFactory.CreateSkeletonHorse(), position, movementDirection);
+ }
+
+ public Meatball CreateMeatball(Vector2 position, MovementDirection movementDirection)
+ {
+ return CreateCreature(mFactory.CreateMeatball(), position, movementDirection);
+ }
+
+ public Prince CreatePrince(Vector2 position, MovementDirection movementDirection)
+ {
+ return CreateCreature(mFactory.CreatePrince(), position, movementDirection);
+ }
+
+ public King CreateKing(Vector2 position, MovementDirection movementDirection)
+ {
+ return CreateCreature(mFactory.CreateKing(), position, movementDirection);
+ }
+
+ private T CreateCreature<T>(T creature, Vector2 position, MovementDirection movementDirection) where T: ICreature
+ {
+ creature.Position = position;
+ creature.InitialPosition = position;
+ creature.MovementDirection = movementDirection;
+ return creature;
+ }
+ }
+}
diff --git a/V3/Objects/FemalePeasant.cs b/V3/Objects/FemalePeasant.cs
new file mode 100644
index 0000000..855685c
--- /dev/null
+++ b/V3/Objects/FemalePeasant.cs
@@ -0,0 +1,50 @@
+using System;
+using Microsoft.Xna.Framework.Content;
+using V3.Data;
+using V3.Map;
+using V3.Objects.Movement;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// Class for simple peasants which will be transformed into zombies.
+ /// </summary>
+ public sealed class FemalePeasant : AbstractCreature
+ {
+ public override string Name { get; protected set; } = "Dorfbewohnerin";
+ public override int Life { get; protected set; } = 20;
+ public override int MaxLife { get; protected set; } = 20;
+ public override int Speed { get; } = 10;
+ public override int Attack { get; protected set; } = 5;
+ public override int AttackRadius { get; protected set; } = 0;
+ public override int SightRadius { get; protected set; } = 20;
+ public override TimeSpan TotalRecovery { get; } = TimeSpan.FromSeconds(0.3);
+ public override TimeSpan Recovery { get; set; }
+ protected override ISpriteCreature[] Sprite { get; } = {new ClothFemaleSprite(), new HeadFemaleSprite(), null};
+ protected override IMovable MovementScheme { get; } = new PlayerMovement();
+ protected override CreatureType Type { get; } = CreatureType.FemalePeasant;
+ public override Faction Faction { get; } = Faction.Plebs;
+ public override ICreature IsAttacking { get; set; }
+ public override IBuilding IsAttackingBuilding { get; set; }
+
+
+ public FemalePeasant(ContentManager contentManager,
+ Pathfinder pathfinder, IOptionsManager optionsManager, AchievementsAndStatistics achievementsAndStatistics)
+ : base(contentManager, pathfinder, optionsManager, achievementsAndStatistics)
+ {
+ }
+
+ public void RemoveArmor()
+ {
+ if (Sprite[0] is NudeFemaleSprite)
+ {
+ ChangeEquipment(EquipmentType.Body, new ClothFemaleSprite());
+ }
+ else if (Sprite[0] is ClothFemaleSprite)
+ {
+ ChangeEquipment(EquipmentType.Body, new NudeFemaleSprite());
+ }
+ }
+ }
+}
diff --git a/V3/Objects/Forge.cs b/V3/Objects/Forge.cs
new file mode 100644
index 0000000..1da3970
--- /dev/null
+++ b/V3/Objects/Forge.cs
@@ -0,0 +1,19 @@
+using Microsoft.Xna.Framework;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// A Forge which can be attacked.
+ /// </summary>
+ public sealed class Forge : AbstractBuilding
+ {
+ public override string Name { get; } = "Schmiede";
+ protected override int MaxRobustness { get; } = 200;
+ public override int Robustness { get; protected set; } = 200;
+ public override int MaxGivesWeapons { get; protected set; } = 10;
+
+ public Forge(Vector2 position, Rectangle size, string textureName, BuildingFace facing) : base(position, size, textureName, facing)
+ {
+ }
+ }
+}
diff --git a/V3/Objects/IBasicCreatureFactory.cs b/V3/Objects/IBasicCreatureFactory.cs
new file mode 100644
index 0000000..9b7a88b
--- /dev/null
+++ b/V3/Objects/IBasicCreatureFactory.cs
@@ -0,0 +1,28 @@
+namespace V3.Objects
+{
+ public interface IBasicCreatureFactory
+ {
+ MalePeasant CreateMalePeasant();
+
+ FemalePeasant CreateFemalePeasant();
+
+ Necromancer CreateNecromancer();
+
+ Skeleton CreateSkeleton();
+ SkeletonElite CreateSkeletonElite();
+
+ Zombie CreateZombie();
+
+ Knight CreateKnight();
+
+ SkeletonHorse CreateSkeletonHorse();
+
+ Meatball CreateMeatball();
+
+ Prince CreatePrince();
+
+ King CreateKing();
+
+ KingsGuard CreateKingsGuard();
+ }
+}
diff --git a/V3/Objects/IBuilding.cs b/V3/Objects/IBuilding.cs
new file mode 100644
index 0000000..0fdd0aa
--- /dev/null
+++ b/V3/Objects/IBuilding.cs
@@ -0,0 +1,20 @@
+namespace V3.Objects
+{
+ public interface IBuilding : IGameObject
+ {
+ int Robustness { get; }
+ string Name { get; }
+ int MaxGivesWeapons { get; }
+
+ /// <summary>
+ /// Building takes specific amount of damage. Substracted from Robustness.
+ /// </summary>
+ /// <param name="damage">TakeDamage taken</param>
+ void TakeDamage(int damage);
+
+ /// <summary>
+ /// Building can give a fixed amount of upgrades.
+ /// </summary>
+ void UpgradeCounter();
+ }
+}
diff --git a/V3/Objects/ICreature.cs b/V3/Objects/ICreature.cs
new file mode 100644
index 0000000..891ada7
--- /dev/null
+++ b/V3/Objects/ICreature.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using V3.Camera;
+using V3.Data;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// A moving game object.
+ /// </summary>
+ public interface ICreature : IGameObject
+ {
+ string Name { get; }
+ Vector2 InitialPosition { set; }
+ int Life { get; }
+ int MaxLife { get; }
+ int Speed { get; }
+ int Attack { get; }
+ int AttackRadius { get; }
+ int SightRadius { get; }
+ TimeSpan TotalRecovery { get; }
+ TimeSpan Recovery { get; set; }
+ /// <summary>
+ /// Area where the creature is standing. Used for collisions.
+ /// </summary>
+ /// <summary>
+ /// Where you can click to select the creature.
+ /// </summary>
+ Rectangle SelectionRectangle { get; }
+ bool IsSelected { get; set; }
+ ICreature IsAttacking { get; set; }
+ IBuilding IsAttackingBuilding { get; set; }
+ MovementDirection MovementDirection { get; set; }
+ MovementState MovementState { get; set; }
+ Faction Faction { get; }
+ bool IsDead { get; }
+ bool IsUpgraded { get; set; }
+
+ /// <summary>
+ /// Creature takes specific amount of damage. Substracted from Life.
+ /// </summary>
+ /// <param name="damage">TakeDamage taken.</param>
+ void TakeDamage(int damage);
+
+ /// <summary>
+ /// Give command to move to desired destination. Not instant movement.
+ /// </summary>
+ /// <param name="destination">Destination in pixels.</param>
+ void Move (Vector2 destination);
+
+ //void ImportentMove(IGameObject creature);
+
+ /// <summary>
+ /// Draws a static non-animated sprite (for HUD) at specified position.
+ /// </summary>
+ /// <param name="spriteBatch">Sprite batch used for drawing.</param>
+ /// <param name="position">Position where to draw the sprite.</param>
+ void DrawStatic(SpriteBatch spriteBatch, Point position);
+
+ /// <summary>
+ /// Update the creature behaviour and animation.
+ /// </summary>
+ void Update(GameTime gameTime, ICreature playerCharacter,
+ bool rightButtonPressed, Vector2 rightButtonPosition, Quadtree quadtree, ICamera camera);
+
+ /// <summary>
+ /// Change the equipment/sprite of the creature to something other.
+ /// If in debug mode the function throws an exception if the creature does not have the specified equipment slots.
+ /// </summary>
+ /// <param name="equipmentType">Which part of the equipment should be changed.</param>
+ /// <param name="sprite">Which sprite to use instead.</param>
+ void ChangeEquipment(EquipmentType equipmentType, ISpriteCreature sprite);
+
+ /// <summary>
+ /// Sets back the position of the creature to its state when created.
+ /// </summary>
+ void ResetPosition();
+
+ /// <summary>
+ /// Plays the specified animation fully, but only once.
+ /// </summary>
+ /// <param name="animation">For which movement state the animation should be played.</param>
+ /// <param name="duration">How long (or how slow) should the animation be?</param>
+ void PlayAnimationOnce(MovementState animation, TimeSpan duration);
+
+ /// <summary>
+ /// Heals the creature. Can not go over MaxLife.
+ /// </summary>
+ /// <param name="amount">How much HP the creature gains.</param>
+ void Heal(int amount);
+
+ /// <summary>
+ /// Creature gets more life and maxlife. Used for testing in Techdemo.
+ /// </summary>
+ /// <param name="modifier">Multiplyier for Life.</param>
+ void Empower(int modifier);
+
+ /// <summary>
+ /// Save this creature’s data to a CreatureData object.
+ /// </summary>
+ /// <returns>the CreatureData object with the status of this creature</returns>
+ CreatureData SaveData();
+
+ /// <summary>
+ /// Restore the creature's state from the given data.
+ /// </summary>
+ /// <param name="data">the state of the creature to restore</param>
+ void LoadData(CreatureData data);
+
+ /// <summary>
+ /// Restore the creature's references to other creatures from the given data.
+ /// </summary>
+ /// <param name="data">the state of the creature to restore</param>
+ /// <param name="creatures">the list of all creatures by ID</param>
+ void LoadReferences(CreatureData data, Dictionary<int, ICreature> creatures);
+ }
+}
diff --git a/V3/Objects/IGameObject.cs b/V3/Objects/IGameObject.cs
new file mode 100644
index 0000000..556e918
--- /dev/null
+++ b/V3/Objects/IGameObject.cs
@@ -0,0 +1,43 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// A game object which is placed on the map.
+ /// </summary>
+ public interface IGameObject
+ {
+ Vector2 Position { get; set; }
+
+ /// <summary>
+ /// A unique ID for this game object, with which it can be identified.
+ /// All implementations should use the IdGenerator to generate this ID.
+ /// </summary>
+ int Id { get; }
+
+ /// <summary>
+ /// Draws the game object on the screen.
+ /// </summary>
+ /// <param name="spriteBatch">Sprite batch used for drawing.</param>
+ void Draw(SpriteBatch spriteBatch);
+
+ /// <summary>
+ /// The size of the object.
+ /// </summary>
+ Rectangle BoundaryRectangle { get; }
+
+ /// <summary>
+ /// Loads needed graphics.
+ /// </summary>
+ /// <param name="contentManager">Content manager used. </param>
+ void LoadContent(ContentManager contentManager);
+
+ /// <summary>
+ /// Returns the object instance without modifications.
+ /// </summary>
+ /// <returns>This object.</returns>
+ IGameObject GetSelf();
+ }
+}
diff --git a/V3/Objects/IObjectsManager.cs b/V3/Objects/IObjectsManager.cs
new file mode 100644
index 0000000..e8fa17b
--- /dev/null
+++ b/V3/Objects/IObjectsManager.cs
@@ -0,0 +1,131 @@
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using V3.Camera;
+using V3.Map;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// Objects manager for all game objects, be is creatures or buildings or even simple landscape objects.
+ /// </summary>
+ public interface IObjectsManager
+ {
+ //***** FOR TESTING PURPOSES!
+ List<ICreature> AddToSelectables { get; }
+ List<ICreature> CreatureList { get; }
+ List<ICreature> UndeadCreatures { get; }
+ //List<ICreature> KingdromCreatures { get; }
+ //List<ICreature> PlebCreatures { get; }
+ //***** NOT FOR TESTING PURPOSES ANYMORE!
+
+ /// <summary>
+ /// Gets the current player character. Usually the necromancer.
+ /// Do not set directly! Use CreatePlayerCharacter() instead!
+ /// </summary>
+ ICreature PlayerCharacter { get; }
+
+ ICreature Boss { get; }
+
+ ICreature Prince { get; }
+
+ Castle Castle { get; }
+
+ /// <summary>
+ /// If you load a new map with new objects you need to initialize the objects manager again.
+ /// (Or else you have all the current objects on the new map.)
+ /// </summary>
+ /// <param name="mapManager"></param>
+ void Initialize(IMapManager mapManager);
+
+ /// <summary>
+ /// Removes all objects from the object manager.
+ /// </summary>
+ void Clear();
+
+ /// <summary>
+ /// Creates the player character. This should be the first thing you do
+ /// after you created or initialized the objects manager.
+ /// </summary>
+ /// <param name="necromancer"></param>
+ void CreatePlayerCharacter(Necromancer necromancer);
+
+ /// <summary>
+ /// Creates the boss of the level. Game is won if the boss is killed.
+ /// </summary>
+ /// <param name="boss">Some ICreature to kill for winning.</param>
+ void CreateBoss(ICreature boss);
+
+ /// <summary>
+ /// Create the prince, a small boss.
+ /// </summary>
+ /// <param name="prince">Some hard ICreature to kill.</param>
+ void CreatePrince(ICreature prince);
+
+ /// <summary>
+ /// Creates a creature for the game and inserts it in the appropriate data structures.
+ /// </summary>
+ /// <param name="creature"></param>
+ void CreateCreature(ICreature creature);
+
+ /// <summary>
+ /// Removes specified creature from the game.
+ /// </summary>
+ /// <param name="creature">The creature to be removed.</param>
+ void RemoveCreature(ICreature creature);
+
+ /// <summary>
+ /// Draws all currently shown objects on the map.
+ /// </summary>
+ /// <param name="spriteBatch">The sprite batch used.</param>
+ /// <param name="camera">Camera for calculating which objects need to be drawn.</param>
+ void Draw(SpriteBatch spriteBatch, ICamera camera);
+
+ /// <summary>
+ /// Draws a visual representation of the Quadtree. For debugging purposes.
+ /// </summary>
+ /// <param name="spriteBatch"></param>
+ void DrawQuadtree(SpriteBatch spriteBatch);
+
+ /// <summary>
+ /// Update the behaviour of all creatures on the map.
+ /// </summary>
+ /// <param name="gameTime">Current game time.</param>
+ /// <param name="rightButtonPressed">Did the player press the right mouse button?</param>
+ /// <param name="rightButtonPosition">Where is the mouse currently?</param>
+ /// <param name="camera">Camera for checking where to do important updates.</param>
+ void Update(GameTime gameTime, bool rightButtonPressed, Vector2 rightButtonPosition, ICamera camera);
+
+ /// <summary>
+ /// Imports the TextureObjects from the objects map layer for drawing things in the right order.
+ /// </summary>
+ /// <param name="textureObjects"></param>
+ void ImportMapObjects(List<IGameObject> textureObjects);
+
+ /// <summary>
+ /// Get all objects in the given rectangles.
+ /// </summary>
+ /// <param name="rectangle">The rectangle which defines the area of the returnes objects.</param>
+ /// <returns>Game objects in the rectangle.</returns>
+ List<IGameObject> GetObjectsInRectangle(Rectangle rectangle);
+
+ /// <summary>
+ /// Gets all creatures which are in the given ellipse area.
+ /// </summary>
+ /// <param name="ellipse">To check if creature is in ellipse area.</param>
+ /// <returns></returns>
+ List<ICreature> GetCreaturesInEllipse(Ellipse ellipse);
+
+ /// <summary>
+ /// Playing around with some cheating codes.
+ /// </summary>
+ void ExposeTheLiving();
+
+ /// <summary>
+ /// Checks if a creature is standing in a graveyard area.
+ /// </summary>
+ /// <param name="creature">Check for this creature.</param>
+ /// <returns>True when standing in graveyard area.</returns>
+ bool InGraveyardArea(ICreature creature);
+ }
+}
diff --git a/V3/Objects/IdGenerator.cs b/V3/Objects/IdGenerator.cs
new file mode 100644
index 0000000..08976ca
--- /dev/null
+++ b/V3/Objects/IdGenerator.cs
@@ -0,0 +1,59 @@
+using System;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// Static helper class that generates unique IDs for game objects.
+ /// </summary>
+ // ReSharper disable once ClassNeverInstantiated.Global
+ public sealed class IdGenerator
+ {
+ private static int sCurrentId;
+
+ private static int? sIdOnce;
+
+ private IdGenerator()
+ {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Get an ID for a new game object.
+ /// </summary>
+ /// <returns>the ID to use for a new game object</returns>
+ public static int GetNextId()
+ {
+ int id;
+ if (sIdOnce.HasValue)
+ {
+ id = sIdOnce.Value;
+ ClearIdOnce();
+ }
+ else
+ {
+ id = sCurrentId;
+ sCurrentId++;
+ }
+
+ return id;
+ }
+
+ /// <summary>
+ /// Sets the ID to use only for the next object that is created.
+ /// </summary>
+ /// <param name="id">the id for the next object</param>
+ public static void SetIdOnce(int id)
+ {
+ sIdOnce = id;
+ }
+
+ /// <summary>
+ /// Clear the ID stored by SetIdOnce that is used only for the
+ /// next object.
+ /// </summary>
+ public static void ClearIdOnce()
+ {
+ sIdOnce = null;
+ }
+ }
+}
diff --git a/V3/Objects/King.cs b/V3/Objects/King.cs
new file mode 100644
index 0000000..b1c9713
--- /dev/null
+++ b/V3/Objects/King.cs
@@ -0,0 +1,33 @@
+using System;
+using Microsoft.Xna.Framework.Content;
+using V3.Data;
+using V3.Map;
+using V3.Objects.Movement;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ // ReSharper disable once ClassNeverInstantiated.Global
+ public class King : AbstractCreature
+ {
+ public override string Name { get; protected set; } = "König Harry";
+ public override int Life { get; protected set; } = 10000;
+ public override int MaxLife { get; protected set; } = 10000;
+ public override int Speed { get; } = 10;
+ public override int Attack { get; protected set; } = 45;
+ public override int AttackRadius { get; protected set; } = 48;
+ public override int SightRadius { get; protected set; } = 500;
+ public override TimeSpan TotalRecovery { get; } = TimeSpan.FromSeconds(0.8);
+ public override TimeSpan Recovery { get; set; }
+ protected override ISpriteCreature[] Sprite { get; } = { new KingSprite() };
+ protected override IMovable MovementScheme { get; } = new PlayerMovement();
+ protected override CreatureType Type { get; } = CreatureType.King;
+ public override Faction Faction { get; } = Faction.Kingdom;
+ public override ICreature IsAttacking { get; set; }
+ public override IBuilding IsAttackingBuilding { get; set; }
+
+ public King(ContentManager contentManager, Pathfinder pathfinder, IOptionsManager optionsManager, AchievementsAndStatistics achievementsAndStatistics) : base(contentManager, pathfinder, optionsManager, achievementsAndStatistics)
+ {
+ }
+ }
+}
diff --git a/V3/Objects/KingsGuard.cs b/V3/Objects/KingsGuard.cs
new file mode 100644
index 0000000..a0f3bc5
--- /dev/null
+++ b/V3/Objects/KingsGuard.cs
@@ -0,0 +1,62 @@
+using System;
+using Microsoft.Xna.Framework.Content;
+using V3.Data;
+using V3.Map;
+using V3.Objects.Movement;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ public sealed class KingsGuard : AbstractCreature
+ {
+ public override string Name { get; protected set; } = "Königliche Garde";
+ public override int Life { get; protected set; } = 200;
+ public override int MaxLife { get; protected set; } = 200;
+ public override int Speed { get; } = 7;
+ public override int Attack { get; protected set; } = 25;
+ public override int AttackRadius { get; protected set; } = 48;
+ public override int SightRadius { get; protected set; } = 200;
+ public override TimeSpan TotalRecovery { get; } = TimeSpan.FromSeconds(0.5);
+ public override TimeSpan Recovery { get; set; }
+ protected override ISpriteCreature[] Sprite { get; } = { new PlateSprite(), new HeadPlateSprite(), new LongswordSprite(), new ShieldSprite() };
+ protected override IMovable MovementScheme { get; } = new PlayerMovement();
+ protected override CreatureType Type { get; } = CreatureType.KingsGuard;
+ public override Faction Faction { get; } = Faction.Kingdom;
+ public override ICreature IsAttacking { get; set; }
+ public override IBuilding IsAttackingBuilding { get; set; }
+
+ public KingsGuard(ContentManager contentManager,
+ Pathfinder pathfinder, IOptionsManager optionsManager, AchievementsAndStatistics achievementsAndStatistics)
+ : base(contentManager, pathfinder, optionsManager,achievementsAndStatistics)
+ {
+ }
+
+ public void MakeFemale()
+ {
+ ChangeEquipment(EquipmentType.Body, new PlateFemaleSprite());
+ ChangeEquipment(EquipmentType.Head, new HeadPlateFemaleSprite());
+ ChangeEquipment(EquipmentType.Weapon, new LongswordFemaleSprite());
+ ChangeEquipment(EquipmentType.Offhand, new ShieldFemaleSprite());
+ }
+
+ public void RemoveArmor()
+ {
+ if (Sprite[(int)EquipmentType.Body] is PlateSprite)
+ {
+ ChangeEquipment(EquipmentType.Body, new NudeSprite());
+ }
+ else if (Sprite[(int)EquipmentType.Body] is PlateFemaleSprite)
+ {
+ ChangeEquipment(EquipmentType.Body, new NudeFemaleSprite());
+ }
+ else if (Sprite[(int)EquipmentType.Body] is NudeSprite)
+ {
+ ChangeEquipment(EquipmentType.Body, new PlateSprite());
+ }
+ else if (Sprite[(int)EquipmentType.Body] is NudeFemaleSprite)
+ {
+ ChangeEquipment(EquipmentType.Body, new PlateFemaleSprite());
+ }
+ }
+ }
+}
diff --git a/V3/Objects/Knight.cs b/V3/Objects/Knight.cs
new file mode 100644
index 0000000..ef31f2a
--- /dev/null
+++ b/V3/Objects/Knight.cs
@@ -0,0 +1,65 @@
+using System;
+using Microsoft.Xna.Framework.Content;
+using V3.Data;
+using V3.Map;
+using V3.Objects.Movement;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// A knight which fights against the necromancer.
+ /// </summary>
+ public sealed class Knight : AbstractCreature
+ {
+ public override string Name { get; protected set; } = "Ritter";
+ public override int Life { get; protected set; } = 120;
+ public override int MaxLife { get; protected set; } = 120;
+ public override int Speed { get; } = 8;
+ public override int Attack { get; protected set; } = 15;
+ public override int AttackRadius { get; protected set; } = 48;
+ public override int SightRadius { get; protected set; } = 200;
+ public override TimeSpan TotalRecovery { get; } = TimeSpan.FromSeconds(0.5);
+ public override TimeSpan Recovery { get; set; }
+ protected override ISpriteCreature[] Sprite { get; } = {new ChainSprite(), new HeadChainSprite(), new ShortswordSprite(), new BucklerSprite()};
+ protected override IMovable MovementScheme { get; } = new PlayerMovement();
+ protected override CreatureType Type { get; } = CreatureType.Knight;
+ public override Faction Faction { get; } = Faction.Kingdom;
+ public override ICreature IsAttacking { get; set; }
+ public override IBuilding IsAttackingBuilding { get; set; }
+
+ public Knight(ContentManager contentManager,
+ Pathfinder pathfinder, IOptionsManager optionsManager, AchievementsAndStatistics achievementsAndStatistics)
+ : base(contentManager, pathfinder, optionsManager, achievementsAndStatistics)
+ {
+ }
+
+ public void MakeFemale()
+ {
+ ChangeEquipment(EquipmentType.Body, new ChainFemaleSprite());
+ ChangeEquipment(EquipmentType.Head, new HeadChainFemaleSprite());
+ ChangeEquipment(EquipmentType.Weapon, new ShortswordFemaleSprite());
+ ChangeEquipment(EquipmentType.Offhand, new BucklerFemaleSprite());
+ }
+
+ public void RemoveArmor()
+ {
+ if (Sprite[(int) EquipmentType.Body] is ChainSprite)
+ {
+ ChangeEquipment(EquipmentType.Body, new NudeSprite());
+ }
+ else if (Sprite[(int) EquipmentType.Body] is ChainFemaleSprite)
+ {
+ ChangeEquipment(EquipmentType.Body, new NudeFemaleSprite());
+ }
+ else if (Sprite[(int)EquipmentType.Body] is NudeSprite)
+ {
+ ChangeEquipment(EquipmentType.Body, new ChainSprite());
+ }
+ else if (Sprite[(int)EquipmentType.Body] is NudeFemaleSprite)
+ {
+ ChangeEquipment(EquipmentType.Body, new ChainFemaleSprite());
+ }
+ }
+ }
+}
diff --git a/V3/Objects/MalePeasant.cs b/V3/Objects/MalePeasant.cs
new file mode 100644
index 0000000..1250412
--- /dev/null
+++ b/V3/Objects/MalePeasant.cs
@@ -0,0 +1,49 @@
+using System;
+using Microsoft.Xna.Framework.Content;
+using V3.Data;
+using V3.Map;
+using V3.Objects.Movement;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// Class for simple peasants which will be transformed into zombies.
+ /// </summary>
+ public sealed class MalePeasant : AbstractCreature
+ {
+ public override string Name { get; protected set; } = "Dorfbewohner";
+ public override int Life { get; protected set; } = 24;
+ public override int MaxLife { get; protected set; } = 24;
+ public override int Speed { get; } = 10;
+ public override int Attack { get; protected set; } = 5;
+ public override int AttackRadius { get; protected set; } = 0;
+ public override int SightRadius { get; protected set; } = 20;
+ public override TimeSpan TotalRecovery { get; } = TimeSpan.FromSeconds(0.3);
+ public override TimeSpan Recovery { get; set; }
+ protected override ISpriteCreature[] Sprite { get; } = {new ClothSprite(), new HeadSprite(), null};
+ protected override IMovable MovementScheme { get; } = new PlayerMovement();
+ protected override CreatureType Type { get; } = CreatureType.MalePeasant;
+ public override Faction Faction { get; } = Faction.Plebs;
+ public override ICreature IsAttacking { get; set; }
+ public override IBuilding IsAttackingBuilding { get; set; }
+
+ public MalePeasant(ContentManager contentManager,
+ Pathfinder pathfinder, IOptionsManager optionsManager, AchievementsAndStatistics achievementsAndStatistics)
+ : base(contentManager, pathfinder, optionsManager, achievementsAndStatistics)
+ {
+ }
+
+ public void RemoveArmor()
+ {
+ if (Sprite[0] is NudeSprite)
+ {
+ ChangeEquipment(EquipmentType.Body, new ClothSprite());
+ }
+ else if (Sprite[0] is ClothSprite)
+ {
+ ChangeEquipment(EquipmentType.Body, new NudeSprite());
+ }
+ }
+ }
+}
diff --git a/V3/Objects/Meatball.cs b/V3/Objects/Meatball.cs
new file mode 100644
index 0000000..144ef90
--- /dev/null
+++ b/V3/Objects/Meatball.cs
@@ -0,0 +1,36 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using V3.Data;
+using V3.Map;
+using V3.Objects.Movement;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ public sealed class Meatball : AbstractCreature
+ {
+ public Meatball(ContentManager contentManager, Pathfinder pathfinder, IOptionsManager optionsManager, AchievementsAndStatistics achievementsAndStatistics) : base(contentManager, pathfinder, optionsManager, achievementsAndStatistics)
+ {
+ }
+
+ protected override ISpriteCreature[] Sprite { get; } = { new MeatballSprite() };
+ protected override IMovable MovementScheme { get; } = new PlayerMovement();
+ protected override CreatureType Type { get; } = CreatureType.Meatball;
+ public override string Name { get; protected set; } = "Fleischball";
+ public override int Life { get; protected set; } = 200;
+ public override int MaxLife { get; protected set; } = 200;
+ public override int Speed { get; } = 5;
+ public override int Attack { get; protected set; } = 70;
+ public override int AttackRadius { get; protected set; } = 60;
+ public override int SightRadius { get; protected set; } = 100;
+ public override TimeSpan TotalRecovery { get; } = TimeSpan.FromSeconds(1);
+ public override TimeSpan Recovery { get; set; }
+ public override Faction Faction { get; } = Faction.Undead;
+ public override ICreature IsAttacking { get; set; }
+ public override IBuilding IsAttackingBuilding { get; set; }
+
+ protected override Point CreatureSize { get; } = new Point(36);
+ protected override Point BoundaryShift { get; } = new Point(-18, -24);
+ }
+}
diff --git a/V3/Objects/Movement.cs b/V3/Objects/Movement.cs
new file mode 100644
index 0000000..3dfa01b
--- /dev/null
+++ b/V3/Objects/Movement.cs
@@ -0,0 +1,20 @@
+// ReSharper disable InconsistentNaming
+namespace V3.Objects
+{
+ /// <summary>
+ /// Cardinal directions to show where the specific creature is facing.
+ /// Because of the isometric viewpoint N(orth) is the upper right.
+ /// </summary>
+ public enum MovementDirection
+ {
+ W, NW, N, NO, O, SO, S, SW
+ }
+
+ /// <summary>
+ /// The basic movement states a creature must hold.
+ /// </summary>
+ public enum MovementState
+ {
+ Idle, Moving, Attacking, Dying, Special
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Movement/CountStepsMovement.cs b/V3/Objects/Movement/CountStepsMovement.cs
new file mode 100644
index 0000000..e8c24c4
--- /dev/null
+++ b/V3/Objects/Movement/CountStepsMovement.cs
@@ -0,0 +1,16 @@
+using Microsoft.Xna.Framework;
+
+namespace V3.Objects.Movement
+{
+ public class CountStepsMovement : PlayerMovement
+ {
+ public float WalkedPixels { get; private set; }
+
+ public override Vector2 GiveNewPosition(Vector2 currentPosition, int speed)
+ {
+ var movedDistance = base.GiveNewPosition(currentPosition, speed);
+ WalkedPixels += Vector2.Distance(currentPosition, currentPosition + movedDistance);
+ return movedDistance;
+ }
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Movement/IMovable.cs b/V3/Objects/Movement/IMovable.cs
new file mode 100644
index 0000000..659708b
--- /dev/null
+++ b/V3/Objects/Movement/IMovable.cs
@@ -0,0 +1,43 @@
+using Microsoft.Xna.Framework;
+using V3.Data;
+using V3.Map;
+
+namespace V3.Objects.Movement
+{
+ public interface IMovable
+ {
+ /// <summary>
+ /// Calculates a path without collisions to desired destination.
+ /// </summary>
+ /// <param name="pathfinder">Pathfinder to use.</param>
+ /// <param name="position">Current position in pixel.</param>
+ /// <param name="destination">Destination in pixel.</param>
+ void FindPath(Pathfinder pathfinder, Vector2 position, Vector2 destination);
+
+ /// <summary>
+ /// Uses pathfinder to for steady movement to new transition.
+ /// </summary>
+ /// <param name="currentPosition">Current position in pixel.</param>
+ /// <param name="speed">Movement speed of the creature.</param>
+ /// <returns>Normalized vector * speed which represents a small step in the direction of desired destination.(</returns>
+ Vector2 GiveNewPosition(Vector2 currentPosition, int speed);
+
+ /// <summary>
+ /// Calculates the direction the creature is looking when moving.
+ /// </summary>
+ MovementDirection GiveMovementDirection();
+ bool IsMoving { get; }
+
+ /// <summary>
+ /// Save the movement data to a MovementData object.
+ /// </summary>
+ /// <returns>the MovementData object with the current status</returns>
+ MovementData SaveData();
+
+ /// <summary>
+ /// Restore the movement state from the given data.
+ /// </summary>
+ /// <param name="movementData">the state of the movement to restore</param>
+ void LoadData(MovementData movementData);
+ }
+}
diff --git a/V3/Objects/Movement/PlayerMovement.cs b/V3/Objects/Movement/PlayerMovement.cs
new file mode 100644
index 0000000..326503f
--- /dev/null
+++ b/V3/Objects/Movement/PlayerMovement.cs
@@ -0,0 +1,155 @@
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using V3.Data;
+using V3.Map;
+
+namespace V3.Objects.Movement
+{
+ // TODO: Rename the class. Current name is unfitting. (But what is fitting?)
+ /// <summary>
+ /// Movement scheme for moving an object with the pathfinder.
+ /// </summary>
+ public class PlayerMovement : IMovable
+ {
+ private const int CellHeight = Constants.CellHeight;
+ private const int CellWidth = Constants.CellWidth;
+ private const float SpeedModifier = 0.25f;
+
+ private List<Vector2> mPath;
+ private int mStep;
+ private Vector2 mLastMovement;
+
+ public bool IsMoving { get; private set; }
+
+ /// <summary>
+ /// Calculates a path without collisions to desired destination.
+ /// </summary>
+ /// <param name="pathfinder">Pathfinder to use.</param>
+ /// <param name="position">Current position in pixel.</param>
+ /// <param name="destination">Destination in pixel.</param>
+ public void FindPath(Pathfinder pathfinder, Vector2 position, Vector2 destination)
+ {
+ mStep = 0;
+ mPath = pathfinder.FindPath(new Vector2((int)(position.X / CellWidth), (int)(position.Y / CellHeight)),
+ new Vector2((int)(destination.X / CellWidth), (int)(destination.Y / CellHeight)));
+ IsMoving = mPath.Count > 0;
+ }
+
+ /// <summary>
+ /// Uses pathfinder to for steady movement to new transition.
+ /// </summary>
+ /// <param name="currentPosition">Current position in pixel.</param>
+ /// <param name="speed">Movement speed of the creature.</param>
+ /// <returns>Normalized vector * speed which represents a small step in the direction of desired destination.(</returns>
+ public virtual Vector2 GiveNewPosition(Vector2 currentPosition, int speed)
+ {
+ Vector2 nextPosition = mPath[mStep];
+ Vector2 newPosition = nextPosition - currentPosition;
+ newPosition.Normalize();
+ float distanceToDestination = Vector2.Distance(nextPosition, currentPosition);
+ if (distanceToDestination < SpeedModifier * speed)
+ {
+ if (mStep == mPath.Count - 1)
+ {
+ IsMoving = false;
+ }
+ else
+ {
+ mStep++;
+ }
+ }
+ mLastMovement = newPosition;
+ return newPosition * SpeedModifier * speed;
+ }
+
+ /// <summary>
+ /// Calculates the direction the creature is looking when moving.
+ /// </summary>
+ public MovementDirection GiveMovementDirection()
+ {
+ // |\
+ // |α\ α == 22.5°
+ // b| \ 1 β == 67.5°
+ // | β\
+ // ──────
+ // a
+ const float b = 0.92f; // b == sin β
+ const float a = 0.38f; // a == sin α
+ Vector2 direction = mLastMovement;
+ MovementDirection movementDirection;
+ if (direction.X < -b)
+ {
+ movementDirection = MovementDirection.W;
+ }
+ else if (direction.X > b)
+ {
+ movementDirection = MovementDirection.O;
+ }
+ else if (direction.Y > 0)
+ {
+ if (direction.X < -a)
+ {
+ movementDirection = MovementDirection.SW;
+ }
+ else if (direction.X > a)
+ {
+ movementDirection = MovementDirection.SO;
+ }
+ else
+ {
+ movementDirection = MovementDirection.S;
+ }
+ }
+ else
+ {
+ if (direction.X < -a)
+ {
+ movementDirection = MovementDirection.NW;
+ }
+ else if (direction.X > a)
+ {
+ movementDirection = MovementDirection.NO;
+ }
+ else
+ {
+ movementDirection = MovementDirection.N;
+ }
+ }
+ return movementDirection;
+ }
+ /// <summary>
+ /// Save the movement data to a MovementData object.
+ /// </summary>
+ /// <returns>the MovementData object with the current status</returns>
+ public MovementData SaveData()
+ {
+ var data = new MovementData();
+ data.IsMoving = IsMoving;
+ if (IsMoving)
+ {
+ data.Path = mPath;
+ data.Step = mStep;
+ data.LastMovement = mLastMovement;
+ }
+ return data;
+ }
+
+ /// <summary>
+ /// Restore the movement state from the given data.
+ /// </summary>
+ /// <param name="movementData">the state of the movement to restore</param>
+ public void LoadData(MovementData movementData)
+ {
+ if (movementData == null)
+ return;
+
+ IsMoving = movementData.IsMoving;
+ if (IsMoving)
+ {
+ mPath = movementData.Path;
+ mStep = movementData.Step;
+ mLastMovement = movementData.LastMovement;
+ }
+ }
+ }
+}
diff --git a/V3/Objects/Necromancer.cs b/V3/Objects/Necromancer.cs
new file mode 100644
index 0000000..b13650d
--- /dev/null
+++ b/V3/Objects/Necromancer.cs
@@ -0,0 +1,61 @@
+using System;
+using Microsoft.Xna.Framework.Content;
+using V3.Data;
+using V3.Map;
+using V3.Objects.Movement;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// A class for the player character: The Necromancer.
+ /// </summary>
+ // ReSharper disable once ClassNeverInstantiated.Global
+ public sealed class Necromancer : AbstractCreature
+ {
+ public override string Name { get; protected set; } = "Vagram der Grausame";
+ public override int Life { get; protected set; } = 500;
+ public override int MaxLife { get; protected set; } = 500;
+ public override int Speed { get; } = 12;
+ public override int Attack { get; protected set; } = 0;
+ public override int AttackRadius { get; protected set; } = 500;
+ public override int SightRadius { get; protected set; } = 0;
+ public override TimeSpan TotalRecovery { get; } = TimeSpan.FromSeconds(1);
+ public override TimeSpan Recovery { get; set; }
+ protected override ISpriteCreature[] Sprite { get; } = {new NecromancerSprite(), new StaffSprite()};
+ protected override IMovable MovementScheme { get; } = new CountStepsMovement();
+ protected override CreatureType Type { get; } = CreatureType.Necromancer;
+ public override Faction Faction { get; } = Faction.Undead;
+ public override ICreature IsAttacking { get; set; }
+ public override IBuilding IsAttackingBuilding { get; set; }
+ public float WalkedPixels => ((CountStepsMovement) MovementScheme).WalkedPixels;
+
+ public Necromancer(ContentManager contentManager,
+ Pathfinder pathfinder, IOptionsManager optionsManager, AchievementsAndStatistics achievementsAndStatistics)
+ : base(contentManager, pathfinder, optionsManager, achievementsAndStatistics)
+ {
+ }
+
+ public void ChangeSex()
+ {
+ ISpriteCreature sprite0;
+ ISpriteCreature sprite1;
+ if (Sprite[0] is NecromancerSprite)
+ {
+ sprite0 = new NecromancerFemaleSprite();
+ sprite1 = new StaffFemaleSprite();
+ }
+ else
+ {
+ sprite0 = new NecromancerSprite();
+ sprite1 = new StaffSprite();
+ }
+
+ ChangeEquipment(EquipmentType.Body, sprite0);
+ if (Sprite.Length >= 2 && Sprite[(int) EquipmentType.Head] != null)
+ {
+ ChangeEquipment(EquipmentType.Head, sprite1);
+ }
+ }
+ }
+}
diff --git a/V3/Objects/ObjectsManager.cs b/V3/Objects/ObjectsManager.cs
new file mode 100644
index 0000000..e3ef568
--- /dev/null
+++ b/V3/Objects/ObjectsManager.cs
@@ -0,0 +1,314 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using V3.Camera;
+using V3.Effects;
+using V3.Map;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// Objects manager for all game objects, be is creatures or buildings or even simple landscape objects.
+ /// </summary>
+ // ReSharper disable once ClassNeverInstantiated.Global
+ public class ObjectsManager : IObjectsManager
+ {
+ private List<List<ICreature>> mCreaturesByFactions;
+ private List<ICreature> mCreatureList;
+ private List<ICreature> mAddToSelectables;
+ private Quadtree mTextureQuadtree;
+ private Quadtree mInteractableQuadtree;
+ private readonly ContentManager mContentManager;
+ private readonly CreatureFactory mCreatureFactory;
+ private readonly IEffectsManager mEffectsManager;
+ private Rectangle mMapRectangle;
+ private List<Area> mAreas;
+ private readonly UpdatesPerSecond mFewerUpdates = new UpdatesPerSecond(15);
+ private readonly UpdatesPerSecond mEffectsCounter = new UpdatesPerSecond(0.2);
+
+ public List<ICreature> AddToSelectables => mAddToSelectables;
+ public List<ICreature> CreatureList => mCreatureList;
+ public List<ICreature> UndeadCreatures => mCreaturesByFactions[(int) Faction.Undead];
+ //public List<ICreature> KingdromCreatures => mCreaturesByFactions[(int) Faction.Kingdom];
+ //public List<ICreature> PlebCreatures => mCreaturesByFactions[(int) Faction.Plebs];
+ public ICreature PlayerCharacter { get; private set; }
+ public ICreature Boss { get; private set; }
+ public ICreature Prince { get; private set; }
+ public Castle Castle { get; private set; }
+
+ public ObjectsManager(ContentManager contentManager, CreatureFactory creatureFactory,
+ IEffectsManager effectsManager)
+ {
+ mContentManager = contentManager;
+ mCreatureFactory = creatureFactory;
+ mEffectsManager = effectsManager;
+ }
+
+ public void Initialize(IMapManager mapManager)
+ {
+ Clear();
+ mAddToSelectables = new List<ICreature>();
+ mCreatureList = new List<ICreature>();
+ mCreaturesByFactions = new List<List<ICreature>>();
+ // Make three lists because we have three factions.
+ for (int i = 0; i < 3; i++)
+ {
+ mCreaturesByFactions.Add(new List<ICreature>());
+ }
+ // Gets the initial values for the Quad Tree from the current map.
+ mInteractableQuadtree = new Quadtree(mapManager.SizeInPixel + new Point(128, 128));
+ mTextureQuadtree = new Quadtree(mapManager.SizeInPixel + new Point(128, 128));
+ mInteractableQuadtree.LoadContent(mContentManager);
+ mTextureQuadtree.LoadContent(mContentManager);
+
+ // Import map objects for drawing.
+ ImportMapObjects(mapManager.GetObjects());
+
+ // Save Map size in ObjectsManager.
+ mMapRectangle = new Rectangle(new Point(0), mapManager.SizeInPixel);
+
+ mAreas = mapManager.Areas;
+ }
+
+ public void Clear()
+ {
+ CreatureList?.Clear();
+ mCreaturesByFactions?.Clear();
+ for (int i = 0; i < 3; i++)
+ {
+ mCreaturesByFactions?.Add(new List<ICreature>());
+ }
+ PlayerCharacter = null;
+ Boss = null;
+ Prince = null;
+ Castle = null;
+ mInteractableQuadtree?.Clear();
+ mTextureQuadtree?.Clear();
+ }
+
+ public void CreatePlayerCharacter(Necromancer necromancer)
+ {
+ PlayerCharacter = necromancer;
+ AddCreature(necromancer);
+ }
+
+ public void CreateBoss(ICreature boss)
+ {
+ Boss = boss;
+ AddCreature(boss);
+ }
+
+ public void CreatePrince(ICreature prince)
+ {
+ Prince = prince;
+ AddCreature(prince);
+ }
+
+ public void CreateCreature(ICreature creature)
+ {
+ AddCreature(creature);
+ }
+
+ public void RemoveCreature(ICreature creature)
+ {
+ mCreatureList.Remove(creature);
+ mCreaturesByFactions[(int) creature.Faction].Remove(creature);
+ mInteractableQuadtree.RemoveItem(creature);
+ }
+
+ public void Draw(SpriteBatch spriteBatch, ICamera camera)
+ {
+ var objectsToDraw = mInteractableQuadtree.GetObjectsInRectangle(camera.ScreenRectangle);
+ var texturesToDraw = mTextureQuadtree.GetObjectsInRectangle(camera.ScreenRectangle);
+ IEnumerable<IGameObject> ordered = from obj in objectsToDraw.Concat(texturesToDraw) orderby obj.Position.Y select obj;
+ foreach (var obj in ordered)
+ {
+ obj.Draw(spriteBatch);
+ }
+ }
+
+ public void DrawQuadtree(SpriteBatch spriteBatch)
+ {
+ mInteractableQuadtree.Draw(spriteBatch);
+ mTextureQuadtree.Draw(spriteBatch);
+ }
+
+ public void Update(GameTime gameTime, bool rightButtonPressed, Vector2 rightButtonPosition, ICamera camera)
+ {
+ foreach (var creature in mCreatureList)
+ {
+ if (creature.GetSelf() == null) continue;
+ creature.Update(gameTime, PlayerCharacter, rightButtonPressed, rightButtonPosition, mInteractableQuadtree, camera);
+
+
+ // Create a boss if castle is down
+ if (creature.IsAttackingBuilding != null && creature.IsAttackingBuilding.Name == "Burg")
+ {
+ if (creature.IsAttackingBuilding.Robustness <= 50 && Boss == null)
+ {
+ var king = mCreatureFactory.CreateKing(new Vector2(creature.IsAttackingBuilding.Position.X - 300, creature.IsAttackingBuilding.Position.Y + 130), MovementDirection.S); //
+ var knight1 = mCreatureFactory.CreateKnight(new Vector2(creature.IsAttackingBuilding.Position.X - 320, creature.IsAttackingBuilding.Position.Y + 110), MovementDirection.S); //
+ var knight2 = mCreatureFactory.CreateKnight(new Vector2(creature.IsAttackingBuilding.Position.X - 280, creature.IsAttackingBuilding.Position.Y + 150), MovementDirection.S); //
+ CreateBoss(king);
+ AddCreature(knight1);
+ AddCreature(knight2);
+ break;
+ }
+ //break;
+ }
+ // Checks if the creature moved out of the map and resets its position as appropriate.
+ if (!mMapRectangle.Contains(creature.Position))
+ {
+ creature.ResetPosition();
+ }
+ }
+ if (mFewerUpdates.IsItTime(gameTime))
+ {
+ mInteractableQuadtree.Update();
+ }
+
+ #region Boss Special Attack
+ // Play some cool effects when boss is spawned so he looks cooler.
+ // Added a small special attack which does at bit AoE damage.
+ if (mEffectsCounter.IsItTime(gameTime))
+ {
+ if (Boss != null && camera.ScreenRectangle.Contains(Boss.Position))
+ {
+ mEffectsManager.PlayOnce(new Quake(), Boss.Position.ToPoint(), new Point(256, 128));
+ var aoeRadius = new Rectangle(Boss.Position.ToPoint() - new Point(128), new Point(256));
+ var effectedCreatures = mInteractableQuadtree.GetObjectsInRectangle(aoeRadius);
+ foreach (var obj in effectedCreatures)
+ {
+ var creature = obj as ICreature;
+ if (creature != null && creature.Faction == Faction.Undead && creature.BoundaryRectangle.Intersects(aoeRadius))
+ {
+ creature.TakeDamage(10);
+ }
+ }
+ }
+ }
+ #endregion
+
+ #region Necromancer stuff
+ if (PlayerCharacter.IsSelected && rightButtonPressed)
+ {
+ var objectsUnderMouse =
+ mInteractableQuadtree.GetObjectsInRectangle(new Rectangle(rightButtonPosition.ToPoint(), new Point(1, 1)));
+ foreach (var obj in objectsUnderMouse)
+ {
+ var creature = obj as ICreature;
+ if (creature == null || !creature.SelectionRectangle.Contains(rightButtonPosition) || !creature.IsDead || creature.Faction == Faction.Undead) continue;
+ if (Vector2.Distance(PlayerCharacter.Position, creature.Position) > PlayerCharacter.AttackRadius) continue;
+ RemoveCreature(creature);
+ if (creature is KingsGuard || creature is King || creature is Prince)
+ {
+ CreateCreature(mCreatureFactory.CreateSkeletonElite(creature.Position, creature.MovementDirection));
+ }
+ else
+ {
+ CreateCreature(mCreatureFactory.CreateZombie(creature.Position, creature.MovementDirection));
+ }
+ PlayerCharacter.PlayAnimationOnce(MovementState.Special, TimeSpan.FromSeconds(0.5d));
+ mEffectsManager.PlayOnce(new BloodBang(), creature.Position.ToPoint(), new Point(128));
+ break;
+ }
+ }
+ #endregion Necromancer stuff
+
+ #region SkeletonRider
+
+ var recMouse = new Rectangle(rightButtonPosition.ToPoint(), new Point(1, 1));
+ foreach (var creature in UndeadCreatures)
+ {
+ Skeleton skeleton = creature as Skeleton;
+ if (skeleton != null)
+ {
+ if(skeleton.IsSelected && rightButtonPressed)
+ {
+ var atMousPosition = mInteractableQuadtree.GetObjectsInRectangle(recMouse);
+ foreach (var wannabehorse in atMousPosition)
+ {
+ var horse = wannabehorse as SkeletonHorse;
+ if (horse != null && Vector2.Distance(skeleton.Position, horse.Position) < 32)
+ {
+ horse.Mount(skeleton);
+ horse.IsSelected = true;
+ mAddToSelectables.Add(horse);
+ }
+ }
+ }
+ }
+ }
+ #endregion SkeletonRider
+ }
+
+ private void AddCreature(ICreature creature)
+ {
+ if (!mMapRectangle.Contains(creature.BoundaryRectangle)) return;
+ mCreatureList.Add(creature);
+ mCreaturesByFactions[(int) creature.Faction].Add(creature);
+ mInteractableQuadtree.Insert(creature);
+ }
+
+ public void ImportMapObjects(List<IGameObject> textureObjects)
+ {
+ foreach (var obj in textureObjects)
+ {
+ if (obj is IBuilding)
+ {
+ mInteractableQuadtree.Insert(obj);
+ if (obj is Castle)
+ Castle = (Castle) obj;
+ }
+ else
+ {
+ mTextureQuadtree.Insert(obj);
+ }
+ }
+ }
+
+ public List<IGameObject> GetObjectsInRectangle(Rectangle rectangle)
+ {
+ return mInteractableQuadtree.GetObjectsInRectangle(rectangle);
+ }
+
+ public List<ICreature> GetCreaturesInEllipse(Ellipse ellipse)
+ {
+ var setToReturn = new List<ICreature>();
+ foreach (var obj in mInteractableQuadtree.GetObjectsInRectangle(ellipse.BoundaryRectangle))
+ {
+ ICreature creature = obj as ICreature;
+ if (creature != null && ellipse.Contains(creature.Position))
+ {
+ setToReturn.Add(creature);
+ }
+ }
+ return setToReturn;
+ }
+
+ public void ExposeTheLiving()
+ {
+ foreach (var creature in mCreatureList)
+ {
+ if (creature.Faction == Faction.Plebs)
+ {
+ (creature as MalePeasant)?.RemoveArmor();
+ (creature as FemalePeasant)?.RemoveArmor();
+ }
+ else if (creature.Faction == Faction.Kingdom)
+ {
+ (creature as Knight)?.RemoveArmor();
+ (creature as KingsGuard)?.RemoveArmor();
+ }
+ }
+ }
+
+ public bool InGraveyardArea(ICreature creature)
+ {
+ return mAreas.Where(area => area.Type == AreaType.Graveyard).Select(area => area.Contains(creature)).Any(contains => contains);
+ }
+ }
+}
diff --git a/V3/Objects/Prince.cs b/V3/Objects/Prince.cs
new file mode 100644
index 0000000..b806f41
--- /dev/null
+++ b/V3/Objects/Prince.cs
@@ -0,0 +1,33 @@
+using System;
+using Microsoft.Xna.Framework.Content;
+using V3.Data;
+using V3.Map;
+using V3.Objects.Movement;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ // ReSharper disable once ClassNeverInstantiated.Global
+ public sealed class Prince : AbstractCreature
+ {
+ public override string Name { get; protected set; } = "Prinz Erhard";
+ public override int Life { get; protected set; } = 6000;
+ public override int MaxLife { get; protected set; } = 6000;
+ public override int Speed { get; } = 10;
+ public override int Attack { get; protected set; } = 35;
+ public override int AttackRadius { get; protected set; } = 48;
+ public override int SightRadius { get; protected set; } = 500;
+ public override TimeSpan TotalRecovery { get; } = TimeSpan.FromSeconds(0.5);
+ public override TimeSpan Recovery { get; set; }
+ protected override ISpriteCreature[] Sprite { get; } = { new PrinceSprite() };
+ protected override IMovable MovementScheme { get; } = new PlayerMovement();
+ protected override CreatureType Type { get; } = CreatureType.Prince;
+ public override Faction Faction { get; } = Faction.Kingdom;
+ public override ICreature IsAttacking { get; set; }
+ public override IBuilding IsAttackingBuilding { get; set; }
+
+ public Prince(ContentManager contentManager, Pathfinder pathfinder, IOptionsManager optionsManager, AchievementsAndStatistics achievementsAndStatistics) : base(contentManager, pathfinder, optionsManager, achievementsAndStatistics)
+ {
+ }
+ }
+}
diff --git a/V3/Objects/Selection.cs b/V3/Objects/Selection.cs
new file mode 100644
index 0000000..4ec7ccc
--- /dev/null
+++ b/V3/Objects/Selection.cs
@@ -0,0 +1,483 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using V3.Data;
+using V3.Effects;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// Class for selecting creatures, holding the selection and drawing the selection rectangle on the screen.
+ /// </summary>
+ // ReSharper disable once ClassNeverInstantiated.Global
+ public sealed class Selection
+ {
+ private IObjectsManager mObjectsManager;
+ private IEffectsManager mEffectsManager;
+ private readonly Color mColor = Color.Black;
+ private Texture2D mTexture;
+ public List<ICreature> SelectedCreatures { get; private set; } = new List<ICreature>();
+ private List<ICreature> TransformList { get; } = new List<ICreature>();
+ private readonly CreatureFactory mCreatureFactory;
+ private int mDeadBodies;
+ private int mExplosionDeaths;
+ private Ellipse NecroArea { get; set; }
+ public Rectangle LastSelection { get; private set; }
+ private SoundEffect mSoundEffect;
+ private SoundEffectInstance mSoundEffectInstance;
+ private IOptionsManager mOptionsManager;
+ private AchievementsAndStatistics mAchievementsAndStatistics;
+
+ public Selection(ContentManager contentManager, CreatureFactory creatureFactory, IObjectsManager objectsManager,
+ IEffectsManager effectsManager, IOptionsManager optionsmanager, AchievementsAndStatistics achievementsAndStatistics)
+ {
+ mCreatureFactory = creatureFactory;
+ mObjectsManager = objectsManager;
+ mEffectsManager = effectsManager;
+ mOptionsManager = optionsmanager;
+ mAchievementsAndStatistics = achievementsAndStatistics;
+ LoadContent(contentManager);
+ }
+
+ private void LoadContent(ContentManager contentManager)
+ {
+ mTexture = contentManager.Load<Texture2D>("Sprites/WhiteRectangle");
+ mSoundEffect = contentManager.Load<SoundEffect>("Sounds/zonk2");
+ mSoundEffectInstance = mSoundEffect.CreateInstance();
+ }
+
+ /// <summary>
+ /// Select all objects in the area of the rectangle between origin and destination.
+ /// </summary>
+ /// <param name="origin">Position in pixels where the mouse button was pressed down.</param>
+ /// <param name="destination">Position in pixels where the mouse button was released.</param>
+ public void Select(Point origin, Point destination)
+ {
+ // Clean last selection
+ SelectedCreatures.ForEach(e => e.IsSelected=false);
+ SelectedCreatures.Clear();
+
+ // New selection
+ int x, y, width, height;
+ if (origin.X > destination.X)
+ {
+ x = destination.X;
+ width = origin.X - destination.X;
+ }
+ else
+ {
+ x = origin.X;
+ width = destination.X - origin.X;
+ }
+ if (origin.Y > destination.Y)
+ {
+ y = destination.Y;
+ height = origin.Y - destination.Y;
+ }
+ else
+ {
+ y = origin.Y;
+ height = destination.Y - origin.Y;
+ }
+
+ // Creates area for necromancer
+ var necroPos = mObjectsManager.PlayerCharacter;
+ NecroArea = new Ellipse(new Vector2((int)necroPos.Position.X, (int)necroPos.Position.Y), 1280, 640);
+
+ // List for all objects in the current selection
+ LastSelection = new Rectangle(x, y, width, height);
+ var selectedObjects = mObjectsManager.GetObjectsInRectangle(LastSelection);
+
+ // Lists to seperate undead from enemy creatures
+ var enemyCreatures = new List<ICreature>();
+ var undeadCreatures = new List<ICreature>();
+
+ foreach (var obj in selectedObjects)
+ {
+ var creature = obj as ICreature;
+ // Give priority when selecting undead creatures.
+ if (creature != null && !creature.IsDead && LastSelection.Intersects(creature.SelectionRectangle) && NecroArea.Contains(creature.Position))
+ {
+ if (creature.Faction != Faction.Undead)
+ {
+ enemyCreatures.Add(creature);
+ }
+ else
+ {
+ undeadCreatures.Add(creature);
+ }
+ }
+ }
+ SelectedCreatures = undeadCreatures.Count == 0 ? enemyCreatures : undeadCreatures;
+
+ // Make all selectable creatures selected
+ foreach (var selectedCreature in SelectedCreatures)
+ {
+ selectedCreature.IsSelected = true;
+ }
+ }
+
+ /// <summary>
+ /// Draw the selection rectangle on the screen.
+ /// </summary>
+ /// <param name="spriteBatch">Sprite batch used.</param>
+ /// <param name="origin">Position in pixels where the mouse button was pressed down.</param>
+ /// <param name="destination">Position in pixels where the mouse button was released.</param>
+ public void Draw(SpriteBatch spriteBatch, Point origin, Point destination)
+ {
+ if (origin.X > destination.X)
+ {
+ spriteBatch.Draw(mTexture, new Rectangle(destination.X, origin.Y, origin.X - destination.X, 2), mColor);
+ spriteBatch.Draw(mTexture, new Rectangle(destination.X, destination.Y, origin.X - destination.X, 2), mColor);
+ }
+ else
+ {
+ spriteBatch.Draw(mTexture, new Rectangle(origin.X, origin.Y, destination.X - origin.X, 2), mColor);
+ spriteBatch.Draw(mTexture, new Rectangle(origin.X, destination.Y, destination.X - origin.X, 2), mColor);
+ }
+ if (origin.Y > destination.Y)
+ {
+ spriteBatch.Draw(mTexture, new Rectangle(origin.X, destination.Y, 2, origin.Y - destination.Y), mColor);
+ spriteBatch.Draw(mTexture, new Rectangle(destination.X, destination.Y, 2, origin.Y - destination.Y), mColor);
+ }
+ else
+ {
+ spriteBatch.Draw(mTexture, new Rectangle(origin.X, origin.Y, 2, destination.Y - origin.Y), mColor);
+ spriteBatch.Draw(mTexture, new Rectangle(destination.X, origin.Y, 2, destination.Y - origin.Y), mColor);
+ }
+ }
+
+ /// <summary>
+ /// Press 1 to trigger the specialattack of the meatball
+ /// All non undead creatures at the distance of 200 pixels take damage
+ /// After the attack the meatball disappears and three zombies will be created
+ /// </summary>
+ public void Specialattack()
+ {
+ foreach (var creature in SelectedCreatures)
+ {
+ var meatball = creature as Meatball;
+ if (meatball != null)
+ {
+ // Creates radius for meatballs explosion
+ var explosionRadius = new Rectangle((int)meatball.Position.X - 200, (int)meatball.Position.Y - 200, 400, 400);
+ var objectsInMeatballArea = mObjectsManager.GetObjectsInRectangle(explosionRadius);
+
+ // Plays sounds and effects
+ mObjectsManager.PlayerCharacter.PlayAnimationOnce(MovementState.Special, TimeSpan.FromSeconds(0.5d));
+ mEffectsManager.PlayOnce(new Explosion(), meatball.Position.ToPoint(), explosionRadius.Size * new Point(3, 2));
+
+ // All attackable creatures take damage (own undead creatures won't take damage)
+ foreach (var attackable in objectsInMeatballArea)
+ {
+ var toAttack = attackable as ICreature;
+ if (toAttack != null && toAttack.Faction != Faction.Undead)
+ {
+ toAttack.TakeDamage(meatball.Attack);
+ if (toAttack.IsDead)
+ mExplosionDeaths += 1;
+ }
+ }
+
+ if (mExplosionDeaths >= 10)
+ mAchievementsAndStatistics.mKaboom = true;
+ mExplosionDeaths = 0;
+
+ // The new zombies after explosion
+ var zombie1 = mCreatureFactory.CreateZombie(meatball.Position, meatball.MovementDirection);
+ var zombie2 = mCreatureFactory.CreateZombie(new Vector2(meatball.Position.X + 50, meatball.Position.Y + 50), meatball.MovementDirection);
+ var zombie3 = mCreatureFactory.CreateZombie(new Vector2(meatball.Position.X - 50, meatball.Position.Y - 50), meatball.MovementDirection);
+
+ // Makes zombies be selected after explosion
+ zombie1.IsSelected = true;
+ zombie2.IsSelected = true;
+ zombie3.IsSelected = true;
+ meatball.IsSelected = false;
+ SelectedCreatures.Add(zombie1);
+ SelectedCreatures.Add(zombie2);
+ SelectedCreatures.Add(zombie3);
+ SelectedCreatures.Remove(meatball);
+
+ // Remove meatball from game
+ mObjectsManager.RemoveCreature(meatball);
+
+ // Add zombies to game
+ mObjectsManager.CreateCreature(zombie1);
+ mObjectsManager.CreateCreature(zombie2);
+ mObjectsManager.CreateCreature(zombie3);
+
+ // For every zombie the necromancer gets healed
+ var heal = mObjectsManager.PlayerCharacter.Life * 0.12;
+ mObjectsManager.PlayerCharacter.Heal((int)heal);
+
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Press 2 to create zombies from dead bodies
+ /// At least one dead body should be in necromancers area
+ /// </summary>
+ public void TransformZombie()
+ {
+ // Creates area for necromancer
+ var necroPos = mObjectsManager.PlayerCharacter;
+ NecroArea = new Ellipse(new Vector2((int) necroPos.Position.X, (int) necroPos.Position.Y), 1280, 640);
+
+ // Get all creatures in necromancers area
+ var necroArea = mObjectsManager.GetCreaturesInEllipse(NecroArea);
+
+ // Every dead body in necromancer area will be transformed to an zombie
+ // The prince will be transformed to an elite zombie
+ foreach (var creature in necroArea)
+ {
+ if (creature.Faction == Faction.Plebs || creature.Faction == Faction.Kingdom)
+ {
+ if (creature.IsDead)
+ {
+ ICreature zombie;
+ if (creature is KingsGuard || creature is King || creature is Prince)
+ zombie = mCreatureFactory.CreateSkeletonElite(creature.Position, creature.MovementDirection);
+ else
+ zombie = mCreatureFactory.CreateZombie(creature.Position, creature.MovementDirection);
+
+ // Add zombie to game
+ mObjectsManager.CreateCreature(zombie);
+
+ // Remove dead body from game
+ mObjectsManager.RemoveCreature(creature);
+
+ // Play sounds and effects
+ mEffectsManager.PlayOnce(new BloodBang(), zombie.Position.ToPoint(), new Point(128));
+ mObjectsManager.PlayerCharacter.PlayAnimationOnce(MovementState.Special,
+ TimeSpan.FromSeconds(0.5d));
+
+ // The number of dead bodies
+ mDeadBodies++;
+
+ // For every zombie the necromancer gets healed
+ var heal = mObjectsManager.PlayerCharacter.Life * 0.04;
+ mObjectsManager.PlayerCharacter.Heal((int)heal);
+ }
+ }
+ }
+
+ // Counts dead bodies in necromancers area and undead creatures
+ var set = mDeadBodies + mObjectsManager.UndeadCreatures.Count;
+ if (mObjectsManager.InGraveyardArea(mObjectsManager.PlayerCharacter) && set < 6)
+ {
+ mObjectsManager.PlayerCharacter.PlayAnimationOnce(MovementState.Special, TimeSpan.FromSeconds(0.5d));
+ for (int i = 1; i <= 6 - set; i++)
+ {
+ var positionX = mObjectsManager.PlayerCharacter.Position.X + (float)Math.Sin(i) * 75;
+ var positionY = mObjectsManager.PlayerCharacter.Position.Y + (float)Math.Cos(i) * 75;
+ var zombie = mCreatureFactory.CreateZombie(new Vector2(positionX, positionY), mObjectsManager.PlayerCharacter.MovementDirection);
+ mObjectsManager.CreateCreature(zombie);
+ zombie.PlayAnimationOnce(MovementState.Special, TimeSpan.FromSeconds(1));
+ }
+ }
+ mDeadBodies = 0;
+ }
+
+ /// <summary>
+ /// Press 3 to create a meatball in exchange for five zombie
+ /// At least five zombies should be selected
+ /// </summary>
+ public void TransformMeatball()
+ {
+ // The cost for this transformation
+ var cost = mObjectsManager.PlayerCharacter.MaxLife * 0.12;
+
+ // Transform only when necromancer life minus the transformations cost is not lower than 1
+ if (mObjectsManager.PlayerCharacter.Life - (int)cost >= 1)
+ {
+ // Puts all zombies in a seperate list called TransformList
+ foreach (var creature in SelectedCreatures)
+ {
+ var zombie = creature as Zombie;
+ if (zombie != null && TransformList.Count < 5)
+ {
+ TransformList.Add(zombie);
+ }
+ }
+
+ // Creates the meatball and makes him selected
+ if (TransformList.Count >= 5)
+ {
+ // Delete zombies from game and SelectedCratures
+ foreach (var zombie in TransformList)
+ {
+ SelectedCreatures.Remove(zombie);
+ mObjectsManager.RemoveCreature(zombie);
+ }
+
+ // Add meatball to game
+ // Position will be randomly chosen from one of the zombies
+ var randomNumber = new Random();
+ var positionMeatball = randomNumber.Next(5);
+ var meatball = mCreatureFactory.CreateMeatball(TransformList[positionMeatball].Position,
+ TransformList[positionMeatball].MovementDirection);
+ meatball.IsSelected = true;
+ SelectedCreatures.Add(meatball);
+ mObjectsManager.CreateCreature(meatball);
+
+ // Plays sounds and effects
+ mObjectsManager.PlayerCharacter.PlayAnimationOnce(MovementState.Special, TimeSpan.FromSeconds(0.5d));
+ mEffectsManager.PlayOnce(new BloodExplosion(), meatball.Position.ToPoint(), new Point(256));
+
+ // Necromancer takes damage
+ mObjectsManager.PlayerCharacter.TakeDamage((int)cost);
+ }
+ TransformList.Clear();
+ }
+ else
+ {
+ // Play sound when transformation not possible
+ mSoundEffectInstance.Volume = mOptionsManager.Options.GetEffectiveVolume();
+ mSoundEffectInstance.Play();
+ }
+ }
+
+
+ /// <summary>
+ /// Press 4 to create an skeleton in exchange for an zombie
+ /// At least one zombie should be selected
+ /// </summary>
+ public void TransformSkeleton()
+ {
+ // Iterates through SelectedCreatures and takes the first zombie
+ foreach (var creature in SelectedCreatures)
+ {
+ var zombie = creature as Zombie;
+ if (zombie != null)
+ {
+ // Delete zombie
+ SelectedCreatures.Remove(zombie);
+ mObjectsManager.RemoveCreature(zombie);
+
+ // Add skeleton
+ var skeleton = mCreatureFactory.CreateSkeleton(zombie.Position, zombie.MovementDirection);
+ skeleton.IsSelected = true;
+ SelectedCreatures.Add(skeleton);
+ mObjectsManager.CreateCreature(skeleton);
+
+ // Plays sounds and effects
+ mObjectsManager.PlayerCharacter.PlayAnimationOnce(MovementState.Special, TimeSpan.FromSeconds(0.5d));
+ mEffectsManager.PlayOnce(new BloodFountain(), zombie.Position.ToPoint() - new Point(0, 64), new Point(128));
+
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Press 5 to create an skeletonhorse in exchange for three skeletons
+ /// At least three skeletons should be selected
+ /// </summary>
+ public void TransformSkeletonHorse()
+ {
+ // The cost for this transformation
+ var cost = mObjectsManager.PlayerCharacter.MaxLife * 0.06;
+
+ // Transform only when necromancer life minus the transformations cost is not lower than 1
+ if (mObjectsManager.PlayerCharacter.Life - (int)cost >= 1)
+ {
+
+ // Puts all skeletons in a seperate list called TransformList
+ foreach (var creature in SelectedCreatures)
+ {
+ var skeleton = creature as Skeleton;
+ if (skeleton != null && TransformList.Count < 3)
+ {
+ TransformList.Add(skeleton);
+ }
+ }
+
+ // Creates the skeletonhorse and makes him selected
+ if (TransformList.Count >= 3)
+ {
+ // Delete zombies from game and SelectedCratures
+ foreach (var skeleton in TransformList)
+ {
+ SelectedCreatures.Remove(skeleton);
+ mObjectsManager.RemoveCreature(skeleton);
+ }
+
+ // Add skeletonhorse to game
+ // Position will be randomly chosen from one of the skeletons
+ var randomNumber = new Random();
+ var positionHorse = randomNumber.Next(3);
+ var skeletonHorse = mCreatureFactory.CreateSkeletonHorse(TransformList[positionHorse].Position,
+ TransformList[positionHorse].MovementDirection);
+ skeletonHorse.IsSelected = true;
+ SelectedCreatures.Add(skeletonHorse);
+ mObjectsManager.CreateCreature(skeletonHorse);
+
+ // Plays sounds and effects
+ mObjectsManager.PlayerCharacter.PlayAnimationOnce(MovementState.Special, TimeSpan.FromSeconds(0.5d));
+ mEffectsManager.PlayOnce(new HorseEffect(), skeletonHorse.Position.ToPoint() - new Point(0, 16), new Point(128));
+
+
+ // Necromancer takes damage
+ mObjectsManager.PlayerCharacter.TakeDamage((int)cost);
+ }
+ TransformList.Clear();
+ }
+ else
+ {
+ // Play sound when transformation not possible
+ mSoundEffectInstance.Volume = mOptionsManager.Options.GetEffectiveVolume();
+ mSoundEffectInstance.Play();
+ }
+ }
+
+ /// <summary>
+ /// If creature leaves area og the necromancer, he gets disselected
+ /// </summary>
+ public void UpdateSelection()
+ {
+ // Creates area for necromancer
+ var necroPos = mObjectsManager.PlayerCharacter;
+ NecroArea = new Ellipse(new Vector2((int)necroPos.Position.X, (int)necroPos.Position.Y), 1280, 640);
+
+ foreach (var creature in SelectedCreatures)
+ {
+ if (!NecroArea.Contains(creature.Position) || creature.IsDead)
+ creature.IsSelected = false;
+ }
+
+ if (mObjectsManager.AddToSelectables.Count > 0)
+ {
+ SelectedCreatures.Add(mObjectsManager.AddToSelectables[0]);
+ mObjectsManager.AddToSelectables.Clear();
+ }
+ }
+
+ /// <summary>
+ /// Give movement command to selected creatures.
+ /// </summary>
+ /// <param name="destination">Desired destination in pixels.</param>
+ public void Move(Vector2 destination)
+ {
+ if (SelectedCreatures.Count == 0) return;
+ var centreCreature = SelectedCreatures[0];
+ // You can not move enemy creatures.
+ if (centreCreature.Faction != Faction.Undead) return;
+ centreCreature.Move(destination);
+
+ for (int i = 1; i < SelectedCreatures.Count; i++)
+ {
+ Vector2 flockDestination = destination;
+ Vector2 distance = centreCreature.Position - SelectedCreatures[i].Position;
+ distance.Normalize();
+ distance *= 70;
+ flockDestination -= distance;
+ SelectedCreatures[i].Move(flockDestination);
+ }
+ }
+ }
+}
diff --git a/V3/Objects/Skeleton.cs b/V3/Objects/Skeleton.cs
new file mode 100644
index 0000000..58eda62
--- /dev/null
+++ b/V3/Objects/Skeleton.cs
@@ -0,0 +1,65 @@
+using System;
+using Microsoft.Xna.Framework.Content;
+using V3.Data;
+using V3.Map;
+using V3.Objects.Movement;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// A skeleton controlled by the necromancer.
+ /// </summary>
+ public class Skeleton : AbstractCreature
+ {
+ /// <summary>
+ /// If the skeleton is mounted, it is not updated and drawn anymore because
+ /// instead the horse sprite is moved.
+ /// </summary>
+ public bool Mounted { private get; set; }
+
+ public override string Name { get; protected set; } = "Skelett";
+ public override int Life { get; protected set; } = 100;
+ public override int MaxLife { get; protected set; } = 100;
+ public override int Speed { get; } = 10;
+ public override int Attack { get; protected set; } = 10;
+ public override int AttackRadius { get; protected set; } = 48;
+ public override int SightRadius { get; protected set; } = 150;
+ public override TimeSpan TotalRecovery { get; } = TimeSpan.FromSeconds(0.5);
+ public override TimeSpan Recovery { get; set; }
+ protected override ISpriteCreature[] Sprite { get; } = {new SkeletonSprite()};
+ protected override IMovable MovementScheme { get; } = new PlayerMovement();
+ protected override CreatureType Type { get; } = CreatureType.Skeleton;
+ public override Faction Faction { get; } = Faction.Undead;
+ public override ICreature IsAttacking { get; set; }
+ public override IBuilding IsAttackingBuilding { get; set; }
+
+ // ReSharper disable once MemberCanBeProtected.Global
+ public Skeleton(ContentManager contentManager,
+ Pathfinder pathfinder, IOptionsManager optionsManager, AchievementsAndStatistics achievementsAndStatistics)
+ : base(contentManager, pathfinder, optionsManager,achievementsAndStatistics)
+ {
+ }
+
+ public override IGameObject GetSelf()
+ {
+ return Mounted ? null : base.GetSelf();
+ }
+
+ public override CreatureData SaveData()
+ {
+ var data = base.SaveData();
+ data.Mounted = Mounted;
+ return data;
+ }
+
+ public override void LoadData(CreatureData data)
+ {
+ base.LoadData(data);
+ Mounted = data.Mounted;
+
+ if (IsUpgraded)
+ ChangeEquipment(EquipmentType.Body, new SkeletonArcherSprite());
+ }
+ }
+}
diff --git a/V3/Objects/SkeletonElite.cs b/V3/Objects/SkeletonElite.cs
new file mode 100644
index 0000000..ac3c6c4
--- /dev/null
+++ b/V3/Objects/SkeletonElite.cs
@@ -0,0 +1,19 @@
+using Microsoft.Xna.Framework.Content;
+using V3.Data;
+using V3.Map;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ public sealed class SkeletonElite : Skeleton
+ {
+ public override int Life { get; protected set; } = 200;
+ public override int MaxLife { get; protected set; } = 200;
+ public override int Attack { get; protected set; } = 30;
+ protected override ISpriteCreature[] Sprite { get; } = { new SkeletonEliteSprite() };
+
+ public SkeletonElite(ContentManager contentManager, Pathfinder pathfinder, IOptionsManager optionsManager, AchievementsAndStatistics achievementsAndStatistics) : base(contentManager, pathfinder, optionsManager, achievementsAndStatistics)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/SkeletonHorse.cs b/V3/Objects/SkeletonHorse.cs
new file mode 100644
index 0000000..6cd441d
--- /dev/null
+++ b/V3/Objects/SkeletonHorse.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using V3.Data;
+using V3.Map;
+using V3.Objects.Movement;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ public sealed class SkeletonHorse : AbstractCreature
+ {
+ public SkeletonHorse(ContentManager contentManager, Pathfinder pathfinder, IOptionsManager optionsManager, AchievementsAndStatistics achievementsAndStatistics) : base(contentManager, pathfinder, optionsManager, achievementsAndStatistics)
+ {
+ }
+
+ private Skeleton mSkeleton;
+
+ protected override ISpriteCreature[] Sprite { get; } = {new SkeletonHorseSprite()};
+ protected override IMovable MovementScheme { get; } = new PlayerMovement();
+ protected override CreatureType Type { get; } = CreatureType.SkeletonHorse;
+ public override string Name { get; protected set; } = "Totenpferd";
+ public override int Life { get; protected set; } = 150;
+ public override int MaxLife { get; protected set; } = 150;
+ public override int Speed { get; } = 20;
+ public override int Attack { get; protected set; }
+ public override int AttackRadius { get; protected set; }
+ public override int SightRadius { get; protected set; }
+ public override TimeSpan TotalRecovery { get; } = TimeSpan.FromSeconds(0.5);
+ public override TimeSpan Recovery { get; set; }
+ public override Faction Faction { get; } = Faction.Undead;
+ public override ICreature IsAttacking { get; set; }
+ public override IBuilding IsAttackingBuilding { get; set; }
+
+ protected override Point CreatureSize { get; } = new Point(36);
+ protected override Point BoundaryShift { get; } = new Point(-18, -24);
+
+ /// <summary>
+ /// A skeleton mounts a horse, becoming a skeleton rider.
+ /// </summary>
+ /// <param name="skeleton">The skeleton which mounts the horse.</param>
+ public void Mount(Skeleton skeleton)
+ {
+ if (IsDead || skeleton.IsDead || mSkeleton != null) return;
+ skeleton.Mounted = true;
+ mSkeleton = skeleton;
+ ChangeEquipment(EquipmentType.Body, new SkeletonRiderSprite());
+ Attack = skeleton.Attack;
+ AttackRadius = skeleton.AttackRadius * 2;
+ // Sight radius is higher because skeleton is mounted.
+ SightRadius = skeleton.SightRadius * 2;
+ Name = "Skelettreiter";
+ }
+
+ protected override void Die()
+ {
+ if (mSkeleton != null)
+ {
+ mSkeleton.Position = Position;
+ mSkeleton.Mounted = false;
+ mSkeleton = null;
+ ChangeEquipment(EquipmentType.Body, new SkeletonHorseSprite());
+ }
+ base.Die();
+ }
+
+ public override CreatureData SaveData()
+ {
+ var data = base.SaveData();
+ if (mSkeleton != null)
+ data.SkeletonId = mSkeleton.Id;
+ return data;
+ }
+
+ public override void LoadReferences(CreatureData data, Dictionary<int, ICreature> creatures)
+ {
+ base.LoadReferences(data, creatures);
+ if (creatures.ContainsKey(data.SkeletonId))
+ {
+ var creature = creatures[data.SkeletonId];
+ if (creature is Skeleton)
+ Mount((Skeleton) creature);
+ }
+ }
+ }
+}
diff --git a/V3/Objects/Sprite/AbstractSpriteCreature.cs b/V3/Objects/Sprite/AbstractSpriteCreature.cs
new file mode 100644
index 0000000..bde139e
--- /dev/null
+++ b/V3/Objects/Sprite/AbstractSpriteCreature.cs
@@ -0,0 +1,201 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace V3.Objects.Sprite
+{
+ public abstract class AbstractSpriteCreature : ISpriteCreature
+ {
+ // How many frames are there for each animation type?
+ protected virtual int IdleFrames { get; } = 4;
+ protected virtual int MovingFrames { get; } = 8;
+ protected virtual int AttackingFrames { get; } = 4;
+ protected virtual int DyingFrames { get; } = 6;
+ protected virtual int SpecialFrames { get; } = 4;
+
+ /// The row of the specific textures in the texture file.
+ protected virtual int IdleTextureIndex { get; } = 0;
+ protected virtual int MovingTextureIndex { get; } = 4;
+ protected virtual int AttackingTextureIndex { get; } = 12;
+ protected virtual int DyingTextureIndex { get; } = 18;
+ protected virtual int SpecialTextureIndex { get; } = 24;
+
+
+ // Specify the size of a single animation frame in pixel.
+ private const int SpriteWidth = 128;
+ private const int SpriteHeight = 128;
+ private readonly Point mSpriteShift = new Point(-SpriteWidth / 2, -SpriteHeight * 3 / 4);
+ private readonly Point mSpriteSize = new Point(SpriteWidth, SpriteHeight);
+
+ private double mTimeSinceUpdate;
+ private Texture2D mTexture;
+ // How much time until animation frame is changed (in milliseconds).
+ private const int UpdatesPerMSec = 125; // 8 FPS
+ private MovementState mLastMovementState = MovementState.Idle;
+ private MovementState mCurrentMovementState = MovementState.Idle;
+ private int mMaxAnimationSteps;
+ private int mMovementStateRow;
+ private bool mIdleBackwardsLoop;
+ private int mAnimationState;
+
+ // Fields for PlayOnce method.
+ private bool mPriorityAnimation;
+ private UpdatesPerSecond mUpS;
+
+ protected abstract string TextureFile { get; }
+
+ /// <summary>
+ /// Loads the texture file and prepares animations.
+ /// </summary>
+ /// <param name="contentManager"></param>
+ public void Load(ContentManager contentManager)
+ {
+ mTexture = contentManager.Load<Texture2D>("Sprites/" + TextureFile);
+ mMaxAnimationSteps = IdleFrames;
+ }
+
+ /// <summary>
+ /// Draws the sprite on the screen.
+ /// </summary>
+ /// <param name="spriteBatch">Sprite batch used for drawing.</param>
+ /// <param name="position">Position on the screen where sprite is drawn to.</param>
+ /// <param name="movementState">What moveset will be used? (Moving, Attacking...)</param>
+ /// <param name="movementDirection">Where does the sprite face?</param>
+ public void Draw(SpriteBatch spriteBatch, Vector2 position, MovementState movementState, MovementDirection movementDirection)
+ {
+ mCurrentMovementState = movementState;
+ Point sourceLocation = new Point((mMovementStateRow + mAnimationState) * SpriteWidth,
+ (int) movementDirection * SpriteHeight);
+ Rectangle sourceRectangle = new Rectangle(sourceLocation, mSpriteSize);
+ spriteBatch.Draw(mTexture, position + new Vector2(mSpriteShift.X, mSpriteShift.Y), sourceRectangle, Color.White);
+ }
+
+ public void DrawStatic(SpriteBatch spriteBatch, Point position, MovementState movementState, MovementDirection movementDirection)
+ {
+ Point sourceLocation = new Point((int)movementState * SpriteWidth, (int)movementDirection * SpriteHeight);
+ Rectangle destinationRectangle = new Rectangle(position + mSpriteShift, mSpriteSize);
+ Rectangle sourceRectangle = new Rectangle(sourceLocation, mSpriteSize);
+ spriteBatch.Draw(mTexture, destinationRectangle, sourceRectangle, Color.White);
+ }
+
+ /// <summary>
+ /// Change the sprite to show an animation.
+ /// </summary>
+ /// <param name="gameTime">Elapsed game time is used for calculating FPS.</param>
+ public void PlayAnimation(GameTime gameTime)
+ {
+ // Playing a single animation cycle just once with higher priority.
+ if (mPriorityAnimation)
+ {
+ if (mAnimationState < mMaxAnimationSteps - 1)
+ {
+ if (mUpS.IsItTime(gameTime))
+ {
+ mAnimationState++;
+ }
+ return;
+ }
+ mAnimationState = 0;
+ mCurrentMovementState = mLastMovementState;
+ SelectFrames(mCurrentMovementState);
+ mPriorityAnimation = false;
+ return;
+ }
+
+ // Change texture for showing animations according to elapsed game time.
+ mTimeSinceUpdate += gameTime.ElapsedGameTime.TotalMilliseconds;
+ if (mTimeSinceUpdate < UpdatesPerMSec)
+ {
+ return;
+ }
+ mTimeSinceUpdate = 0;
+
+ // Check which specific animation sprites need to be used.
+ // Only check if movement state is changed.
+ if (mCurrentMovementState != mLastMovementState)
+ {
+ SelectFrames(mCurrentMovementState);
+ mAnimationState = 0;
+ mLastMovementState = mCurrentMovementState;
+ }
+
+ // Idle animation is looped back and forth.
+ if (mIdleBackwardsLoop)
+ {
+ mAnimationState--;
+ if (mAnimationState <= 0)
+ {
+ mIdleBackwardsLoop = false;
+ }
+ return;
+ }
+
+ if (mAnimationState < mMaxAnimationSteps - 1)
+ {
+ mAnimationState++;
+ }
+ else
+ {
+ if (mLastMovementState == MovementState.Idle)
+ {
+ mIdleBackwardsLoop = true;
+ mAnimationState--;
+ }
+ else if (mLastMovementState == MovementState.Dying)
+ {
+ }
+ else
+ {
+ mAnimationState = 0;
+ }
+ }
+ }
+
+ private void SelectFrames(MovementState currentMovementState)
+ {
+ switch (currentMovementState)
+ {
+ case MovementState.Idle:
+ mMaxAnimationSteps = IdleFrames;
+ mMovementStateRow = IdleTextureIndex;
+ mIdleBackwardsLoop = false;
+ break;
+ case MovementState.Moving:
+ mMaxAnimationSteps = MovingFrames;
+ mMovementStateRow = MovingTextureIndex;
+ break;
+ case MovementState.Attacking:
+ mMaxAnimationSteps = AttackingFrames;
+ mMovementStateRow = AttackingTextureIndex;
+ break;
+ case MovementState.Dying:
+ mMaxAnimationSteps = DyingFrames;
+ mMovementStateRow = DyingTextureIndex;
+ break;
+ case MovementState.Special:
+ mMaxAnimationSteps = SpecialFrames;
+ mMovementStateRow = SpecialTextureIndex;
+ break;
+ default:
+ mMaxAnimationSteps = 1; // No Animation if default case is reached.
+ break;
+ }
+ }
+
+ /// <summary>
+ /// Plays the specified animation fully, but only once.
+ /// </summary>
+ /// <param name="animation">For which movement state the animation should be played.</param>
+ /// <param name="duration">How long (or how slow) should the animation be?</param>
+ public void PlayOnce(MovementState animation, TimeSpan duration)
+ {
+ mLastMovementState = mCurrentMovementState;
+ mCurrentMovementState = animation;
+ mPriorityAnimation = true;
+ SelectFrames(animation);
+ mAnimationState = 0;
+ mUpS = new UpdatesPerSecond(1d / (duration.TotalSeconds / mMaxAnimationSteps));
+ }
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/ArrowSprite.cs b/V3/Objects/Sprite/ArrowSprite.cs
new file mode 100644
index 0000000..76e34b7
--- /dev/null
+++ b/V3/Objects/Sprite/ArrowSprite.cs
@@ -0,0 +1,42 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace V3.Objects.Sprite
+{
+ /// <summary>
+ /// A simple arrow from different directions without animations.
+ /// </summary>
+ public sealed class ArrowSprite : ISpriteCreature
+ {
+ private Texture2D mTexture;
+ private const int Size = 64;
+
+ public void Load(ContentManager contentManager)
+ {
+ mTexture = contentManager.Load<Texture2D>("Sprites/arrows");
+ }
+
+ public void Draw(SpriteBatch spriteBatch, Vector2 position, MovementState movementState, MovementDirection movementDirection)
+ {
+ spriteBatch.Draw(mTexture, position - new Vector2(Size / 2f), new Rectangle(0, Size * (int) movementDirection, Size, Size), Color.White);
+ }
+
+ public void DrawStatic(SpriteBatch spriteBatch,
+ Point position,
+ MovementState movementState,
+ MovementDirection movementDirection)
+ {
+ Draw(spriteBatch, position.ToVector2(), movementState, movementDirection);
+ }
+
+ public void PlayAnimation(GameTime gameTime)
+ {
+ }
+
+ public void PlayOnce(MovementState animation, TimeSpan duration)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/BucklerFemaleSprite.cs b/V3/Objects/Sprite/BucklerFemaleSprite.cs
new file mode 100644
index 0000000..7d72869
--- /dev/null
+++ b/V3/Objects/Sprite/BucklerFemaleSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public class BucklerFemaleSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "buckler_female";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/BucklerSprite.cs b/V3/Objects/Sprite/BucklerSprite.cs
new file mode 100644
index 0000000..c55ed81
--- /dev/null
+++ b/V3/Objects/Sprite/BucklerSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public class BucklerSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "buckler";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/ChainFemaleSprite.cs b/V3/Objects/Sprite/ChainFemaleSprite.cs
new file mode 100644
index 0000000..eddfe9d
--- /dev/null
+++ b/V3/Objects/Sprite/ChainFemaleSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class ChainFemaleSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "chain_female";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/ChainSprite.cs b/V3/Objects/Sprite/ChainSprite.cs
new file mode 100644
index 0000000..33e7042
--- /dev/null
+++ b/V3/Objects/Sprite/ChainSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class ChainSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "chain";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/ClothFemaleSprite.cs b/V3/Objects/Sprite/ClothFemaleSprite.cs
new file mode 100644
index 0000000..3edaec1
--- /dev/null
+++ b/V3/Objects/Sprite/ClothFemaleSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class ClothFemaleSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "cloth_female";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/ClothSprite.cs b/V3/Objects/Sprite/ClothSprite.cs
new file mode 100644
index 0000000..b3aaff0
--- /dev/null
+++ b/V3/Objects/Sprite/ClothSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class ClothSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "cloth";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/EquipmentType.cs b/V3/Objects/Sprite/EquipmentType.cs
new file mode 100644
index 0000000..611c35d
--- /dev/null
+++ b/V3/Objects/Sprite/EquipmentType.cs
@@ -0,0 +1,10 @@
+namespace V3.Objects.Sprite
+{
+ /// <summary>
+ /// Different types of equipment slots.
+ /// </summary>
+ public enum EquipmentType
+ {
+ Body, Head, Weapon, Offhand
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/HeadBaldSprite.cs b/V3/Objects/Sprite/HeadBaldSprite.cs
new file mode 100644
index 0000000..7b01e16
--- /dev/null
+++ b/V3/Objects/Sprite/HeadBaldSprite.cs
@@ -0,0 +1,10 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace V3.Objects.Sprite
+{
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public sealed class HeadBaldSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "head_bald";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/HeadChainFemaleSprite.cs b/V3/Objects/Sprite/HeadChainFemaleSprite.cs
new file mode 100644
index 0000000..8d8ed90
--- /dev/null
+++ b/V3/Objects/Sprite/HeadChainFemaleSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public class HeadChainFemaleSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "head_chain_female";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/HeadChainSprite.cs b/V3/Objects/Sprite/HeadChainSprite.cs
new file mode 100644
index 0000000..31476f1
--- /dev/null
+++ b/V3/Objects/Sprite/HeadChainSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public class HeadChainSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "head_chain";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/HeadFemaleSprite.cs b/V3/Objects/Sprite/HeadFemaleSprite.cs
new file mode 100644
index 0000000..b8649aa
--- /dev/null
+++ b/V3/Objects/Sprite/HeadFemaleSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class HeadFemaleSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "head_female";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/HeadPlateFemaleSprite.cs b/V3/Objects/Sprite/HeadPlateFemaleSprite.cs
new file mode 100644
index 0000000..8ad7917
--- /dev/null
+++ b/V3/Objects/Sprite/HeadPlateFemaleSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class HeadPlateFemaleSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "head_plate_female";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/HeadPlateSprite.cs b/V3/Objects/Sprite/HeadPlateSprite.cs
new file mode 100644
index 0000000..fa237b3
--- /dev/null
+++ b/V3/Objects/Sprite/HeadPlateSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class HeadPlateSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "head_plate";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/HeadSprite.cs b/V3/Objects/Sprite/HeadSprite.cs
new file mode 100644
index 0000000..303bb8a
--- /dev/null
+++ b/V3/Objects/Sprite/HeadSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class HeadSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "head";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/ISpriteCreature.cs b/V3/Objects/Sprite/ISpriteCreature.cs
new file mode 100644
index 0000000..1339b2a
--- /dev/null
+++ b/V3/Objects/Sprite/ISpriteCreature.cs
@@ -0,0 +1,53 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace V3.Objects.Sprite
+{
+ /// <summary>
+ ///
+ /// </summary>
+ public interface ISpriteCreature
+ {
+ /// <summary>
+ /// Loads the texture file and prepares animations.
+ /// </summary>
+ /// <param name="contentManager">Content manager used.</param>
+ void Load(ContentManager contentManager);
+
+ /// <summary>
+ /// Draws the sprite on the screen.
+ /// </summary>
+ /// <param name="spriteBatch">Sprite batch used for drawing.</param>
+ /// <param name="position">Position on the screen in pixels where the sprite should stand.</param>
+ /// <param name="movementState">What moveset will be used? (Moving, Attacking...)</param>
+ /// <param name="movementDirection">Where does the sprite face to?</param>
+ void Draw(SpriteBatch spriteBatch, Vector2 position, MovementState movementState, MovementDirection movementDirection);
+
+ /// <summary>
+ /// Draws a static image of the sprite. No animations.
+ /// </summary>
+ /// <param name="spriteBatch">Sprite batch used for drawing.</param>
+ /// <param name="position">Position of the sprite in pixels. (Where are the feet of the sprite placed.</param>
+ /// <param name="movementState">What moveset will be used? (Moving, Attacking...)</param>
+ /// <param name="movementDirection">Where does the sprite face to?</param>
+ void DrawStatic(SpriteBatch spriteBatch,
+ Point position,
+ MovementState movementState,
+ MovementDirection movementDirection);
+
+ /// <summary>
+ /// Change the sprite to show an animation.
+ /// </summary>
+ /// <param name="gameTime">Elapsed game time is used for calculating FPS.</param>
+ void PlayAnimation(GameTime gameTime);
+
+ /// <summary>
+ /// Plays the specified animation fully, but only once.
+ /// </summary>
+ /// <param name="animation">For which movement state the animation should be played.</param>
+ /// <param name="duration">How long (or how slow) should the animation be?</param>
+ void PlayOnce(MovementState animation, TimeSpan duration);
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/KingSprite.cs b/V3/Objects/Sprite/KingSprite.cs
new file mode 100644
index 0000000..0640514
--- /dev/null
+++ b/V3/Objects/Sprite/KingSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public class KingSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "king";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/LongswordFemaleSprite.cs b/V3/Objects/Sprite/LongswordFemaleSprite.cs
new file mode 100644
index 0000000..fc10b7c
--- /dev/null
+++ b/V3/Objects/Sprite/LongswordFemaleSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class LongswordFemaleSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "longsword_female";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/LongswordSprite.cs b/V3/Objects/Sprite/LongswordSprite.cs
new file mode 100644
index 0000000..7ac1850
--- /dev/null
+++ b/V3/Objects/Sprite/LongswordSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class LongswordSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "longsword";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/MeatballSprite.cs b/V3/Objects/Sprite/MeatballSprite.cs
new file mode 100644
index 0000000..eeafbe9
--- /dev/null
+++ b/V3/Objects/Sprite/MeatballSprite.cs
@@ -0,0 +1,11 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class MeatballSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "fleischklops";
+ protected override int AttackingFrames { get; } = 12;
+ protected override int DyingFrames { get; } = 8;
+ protected override int AttackingTextureIndex { get; } = 12;
+ protected override int DyingTextureIndex { get; } = 24;
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/NecromancerFemaleSprite.cs b/V3/Objects/Sprite/NecromancerFemaleSprite.cs
new file mode 100644
index 0000000..ffecd74
--- /dev/null
+++ b/V3/Objects/Sprite/NecromancerFemaleSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public class NecromancerFemaleSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "necromancer_female";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/NecromancerSprite.cs b/V3/Objects/Sprite/NecromancerSprite.cs
new file mode 100644
index 0000000..494ba13
--- /dev/null
+++ b/V3/Objects/Sprite/NecromancerSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class NecromancerSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "necromancer";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/NudeFemaleSprite.cs b/V3/Objects/Sprite/NudeFemaleSprite.cs
new file mode 100644
index 0000000..e2aaa2f
--- /dev/null
+++ b/V3/Objects/Sprite/NudeFemaleSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class NudeFemaleSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "nude_female";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/NudeSprite.cs b/V3/Objects/Sprite/NudeSprite.cs
new file mode 100644
index 0000000..1056335
--- /dev/null
+++ b/V3/Objects/Sprite/NudeSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class NudeSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "nude";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/PlateFemaleSprite.cs b/V3/Objects/Sprite/PlateFemaleSprite.cs
new file mode 100644
index 0000000..af3bd79
--- /dev/null
+++ b/V3/Objects/Sprite/PlateFemaleSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class PlateFemaleSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "plate_female";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/PlateSprite.cs b/V3/Objects/Sprite/PlateSprite.cs
new file mode 100644
index 0000000..c8495a0
--- /dev/null
+++ b/V3/Objects/Sprite/PlateSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class PlateSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "plate";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/PrinceSprite.cs b/V3/Objects/Sprite/PrinceSprite.cs
new file mode 100644
index 0000000..efe9d18
--- /dev/null
+++ b/V3/Objects/Sprite/PrinceSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class PrinceSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "prince";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/ShieldFemaleSprite.cs b/V3/Objects/Sprite/ShieldFemaleSprite.cs
new file mode 100644
index 0000000..0e70545
--- /dev/null
+++ b/V3/Objects/Sprite/ShieldFemaleSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class ShieldFemaleSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "shield_female";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/ShieldSprite.cs b/V3/Objects/Sprite/ShieldSprite.cs
new file mode 100644
index 0000000..5830a45
--- /dev/null
+++ b/V3/Objects/Sprite/ShieldSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class ShieldSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "shield";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/ShortswordFemaleSprite.cs b/V3/Objects/Sprite/ShortswordFemaleSprite.cs
new file mode 100644
index 0000000..a046bb0
--- /dev/null
+++ b/V3/Objects/Sprite/ShortswordFemaleSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public class ShortswordFemaleSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "shortsword_female";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/ShortswordSprite.cs b/V3/Objects/Sprite/ShortswordSprite.cs
new file mode 100644
index 0000000..352631f
--- /dev/null
+++ b/V3/Objects/Sprite/ShortswordSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public class ShortswordSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "shortsword";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/SkeletonArcherSprite.cs b/V3/Objects/Sprite/SkeletonArcherSprite.cs
new file mode 100644
index 0000000..1891939
--- /dev/null
+++ b/V3/Objects/Sprite/SkeletonArcherSprite.cs
@@ -0,0 +1,10 @@
+namespace V3.Objects.Sprite
+{
+ public class SkeletonArcherSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "skeleton_archer";
+ protected override int AttackingFrames { get; } = 4;
+ protected override int AttackingTextureIndex { get; } = 28;
+ protected override int DyingTextureIndex { get; } = 22;
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/SkeletonEliteSprite.cs b/V3/Objects/Sprite/SkeletonEliteSprite.cs
new file mode 100644
index 0000000..99aa94b
--- /dev/null
+++ b/V3/Objects/Sprite/SkeletonEliteSprite.cs
@@ -0,0 +1,9 @@
+namespace V3.Objects.Sprite
+{
+ public class SkeletonEliteSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "skeleton_elite";
+ protected override int AttackingFrames { get; } = 4;
+ protected override int DyingTextureIndex { get; } = 22;
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/SkeletonHorseSprite.cs b/V3/Objects/Sprite/SkeletonHorseSprite.cs
new file mode 100644
index 0000000..f26b58b
--- /dev/null
+++ b/V3/Objects/Sprite/SkeletonHorseSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public class SkeletonHorseSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "skeleton_horse";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/SkeletonRiderSprite.cs b/V3/Objects/Sprite/SkeletonRiderSprite.cs
new file mode 100644
index 0000000..278d8c7
--- /dev/null
+++ b/V3/Objects/Sprite/SkeletonRiderSprite.cs
@@ -0,0 +1,10 @@
+namespace V3.Objects.Sprite
+{
+ public class SkeletonRiderSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "skeleton_rider";
+ protected override int AttackingFrames { get; } = 5;
+ protected override int DyingTextureIndex { get; } = 0;
+ protected override int DyingFrames { get; } = 0;
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/SkeletonSprite.cs b/V3/Objects/Sprite/SkeletonSprite.cs
new file mode 100644
index 0000000..15f7246
--- /dev/null
+++ b/V3/Objects/Sprite/SkeletonSprite.cs
@@ -0,0 +1,9 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class SkeletonSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "skeleton";
+ protected override int AttackingFrames { get; } = 4;
+ protected override int DyingTextureIndex { get; } = 22;
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/StaffFemaleSprite.cs b/V3/Objects/Sprite/StaffFemaleSprite.cs
new file mode 100644
index 0000000..5e4120c
--- /dev/null
+++ b/V3/Objects/Sprite/StaffFemaleSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public class StaffFemaleSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "staff_female";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/StaffSprite.cs b/V3/Objects/Sprite/StaffSprite.cs
new file mode 100644
index 0000000..aac7bed
--- /dev/null
+++ b/V3/Objects/Sprite/StaffSprite.cs
@@ -0,0 +1,7 @@
+namespace V3.Objects.Sprite
+{
+ public class StaffSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "staff";
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/ZombieSprite.cs b/V3/Objects/Sprite/ZombieSprite.cs
new file mode 100644
index 0000000..0cd1a19
--- /dev/null
+++ b/V3/Objects/Sprite/ZombieSprite.cs
@@ -0,0 +1,11 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class ZombieSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "zombie";
+ protected override int AttackingFrames { get; } = 8;
+ protected override int DyingTextureIndex { get; } = 22;
+ protected override int SpecialTextureIndex { get; } = 36;
+ protected override int SpecialFrames { get; } = 8;
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Sprite/ZombieWithClubSprite.cs b/V3/Objects/Sprite/ZombieWithClubSprite.cs
new file mode 100644
index 0000000..60abc24
--- /dev/null
+++ b/V3/Objects/Sprite/ZombieWithClubSprite.cs
@@ -0,0 +1,11 @@
+namespace V3.Objects.Sprite
+{
+ public sealed class ZombieWithClubSprite : AbstractSpriteCreature
+ {
+ protected override string TextureFile { get; } = "zombie_club";
+ protected override int AttackingFrames { get; } = 8;
+ protected override int DyingTextureIndex { get; } = 22;
+ protected override int SpecialTextureIndex { get; } = 36;
+ protected override int SpecialFrames { get; } = 8;
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/TextureObject.cs b/V3/Objects/TextureObject.cs
new file mode 100644
index 0000000..b7da6c0
--- /dev/null
+++ b/V3/Objects/TextureObject.cs
@@ -0,0 +1,76 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using System;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// Texture objects represent map data like tiles and map objects which are placed on the screen.
+ /// </summary>
+ public sealed class TextureObject : IGameObject
+ {
+ public Vector2 Position { get; set; }
+ public int Id { get; }
+ public Rectangle BoundaryRectangle => new Rectangle(mDrawPosition, mTextureSize);
+ private readonly string mTextureName;
+ private readonly Point mDrawPosition;
+ private readonly Point mTextureSize;
+ private Texture2D mTexture;
+ private readonly Point mTextureSource;
+
+ /// <summary>
+ /// Creates an empty TextureObject.
+ /// </summary>
+ public TextureObject()
+ {
+ Id = IdGenerator.GetNextId();
+ Position = Vector2.Zero;
+ mDrawPosition = Point.Zero;
+ mTextureSize = Point.Zero;
+ mTextureName = "EmptyPixel";
+ mTextureSource = Point.Zero;
+ }
+
+ public TextureObject(Point position, Point drawPosition, Point textureSize, Point textureSource, string textureName)
+ {
+ Position = position.ToVector2();
+ mDrawPosition = drawPosition;
+ mTextureSize = textureSize;
+ mTextureName = textureName;
+ mTextureSource = textureSource;
+ }
+
+ public void LoadContent(ContentManager contentManager)
+ {
+ mTexture = contentManager.Load<Texture2D>("Textures/" + mTextureName);
+ //mOnePixelTexture = contentManager.Load<Texture2D>("Sprites/WhiteRectangle");
+ }
+
+ public void Draw(SpriteBatch spriteBatch)
+ {
+ spriteBatch.Draw(mTexture, BoundaryRectangle, new Rectangle(mTextureSource, mTextureSize), Color.White);
+ }
+
+ public IGameObject GetSelf()
+ {
+ return this;
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/V3/Objects/Transformation.cs b/V3/Objects/Transformation.cs
new file mode 100644
index 0000000..1b1cf86
--- /dev/null
+++ b/V3/Objects/Transformation.cs
@@ -0,0 +1,102 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+namespace V3.Objects
+{
+ // ReSharper disable once ClassNeverInstantiated.Global
+ public sealed class Transformation
+ {
+ public IObjectsManager ObjectsManager { private get; set; }
+ private KeyboardState mOldStateSpecialattack;
+ private KeyboardState mOldStateZombie;
+ private KeyboardState mOldStateMeatball;
+ private KeyboardState mOldStateSkeleton;
+ private KeyboardState mOldStateHorse;
+ internal Selection mSelection;
+ private Texture2D mNecroArea;
+
+ /// <summary>
+ /// Call for transformations
+ /// </summary>
+ public void Transform()
+ {
+ KeyboardState newState = Keyboard.GetState();
+
+ // Specialattack for Meatball (press 1)
+ if (newState.IsKeyDown(Keys.D1))
+ {
+ if (!mOldStateSpecialattack.IsKeyDown(Keys.D1))
+ {
+ mSelection.Specialattack();
+ }
+ }
+ mOldStateSpecialattack = newState;
+
+ // Transform Zombie (press 2)
+ if (newState.IsKeyDown(Keys.D2))
+ {
+ if (!mOldStateZombie.IsKeyDown(Keys.D2))
+ {
+ mSelection.TransformZombie();
+ }
+ }
+ mOldStateZombie = newState;
+
+ // Transform Meatball (press 3)
+ // Nneed five zombies
+ if (newState.IsKeyDown(Keys.D3))
+ {
+ if (!mOldStateMeatball.IsKeyDown(Keys.D3))
+ {
+ mSelection.TransformMeatball();
+ }
+ }
+ mOldStateMeatball = newState;
+
+ // Transform Skeleton (press 4)
+ // Need one zombie
+ if (newState.IsKeyDown(Keys.D4))
+ {
+ if (!mOldStateSkeleton.IsKeyDown(Keys.D4))
+ {
+ mSelection.TransformSkeleton();
+ }
+ }
+ mOldStateSkeleton = newState;
+
+ // Transform SkeletonHorse (press 5)
+ // Nneed 3 skeletons
+ if (newState.IsKeyDown(Keys.D5))
+ {
+ if (!mOldStateHorse.IsKeyDown(Keys.D5))
+ {
+ mSelection.TransformSkeletonHorse();
+ }
+ }
+ mOldStateHorse = newState;
+ }
+
+ //Draw(SpriteBatch spriteBatch, Vector2 position, MovementState movementState, MovementDirection movementDirection)
+ /// <summary>
+ /// Load the selection and ellipse sprites for necromancers area
+ /// </summary>
+ /// <param name="contentManager">the content manager</param>
+ public void LoadArea(ContentManager contentManager)
+ {
+ //mNecroArea = contentManager.Load<Texture2D>("Sprites/selection");
+ mNecroArea = contentManager.Load<Texture2D>("Sprites/ellipse");
+ }
+
+ /// <summary>
+ /// Draw the area for necromancer
+ /// </summary>
+ /// <param name="spriteBatch">the sprite batch to use for drawing the object</param>
+ public void DrawNecroArea(SpriteBatch spriteBatch)
+ {
+ //spriteBatch.Draw(mNecroArea, ObjectsManager.PlayerCharacter.Position - new Vector2(800, 400), null, Color.Red*0.3f, 0, Vector2.Zero, 25, SpriteEffects.None, 0);
+ spriteBatch.Draw(mNecroArea, ObjectsManager.PlayerCharacter.Position - new Vector2(640, 320), null, Color.Red*0.5f, 0, Vector2.Zero, 2.5f, SpriteEffects.None, 0);
+ }
+ }
+} \ No newline at end of file
diff --git a/V3/Objects/Woodhouse.cs b/V3/Objects/Woodhouse.cs
new file mode 100644
index 0000000..82c961b
--- /dev/null
+++ b/V3/Objects/Woodhouse.cs
@@ -0,0 +1,16 @@
+using Microsoft.Xna.Framework;
+
+namespace V3.Objects
+{
+ public sealed class Woodhouse : AbstractBuilding
+ {
+ public override string Name { get; } = "Holzhaus";
+ protected override int MaxRobustness { get; } = 130;
+ public override int Robustness { get; protected set; } = 130;
+ public override int MaxGivesWeapons { get; protected set; } = 10;
+
+ public Woodhouse(Vector2 position, Rectangle size, string textureName, BuildingFace facing) : base(position, size, textureName, facing)
+ {
+ }
+ }
+}
diff --git a/V3/Objects/Zombie.cs b/V3/Objects/Zombie.cs
new file mode 100644
index 0000000..635634c
--- /dev/null
+++ b/V3/Objects/Zombie.cs
@@ -0,0 +1,45 @@
+using System;
+using Microsoft.Xna.Framework.Content;
+using V3.Data;
+using V3.Map;
+using V3.Objects.Movement;
+using V3.Objects.Sprite;
+
+namespace V3.Objects
+{
+ /// <summary>
+ /// A simple zombie controlled by the necromancer.
+ /// </summary>
+ public sealed class Zombie : AbstractCreature
+ {
+ public override string Name { get; protected set; } = "Zombie";
+ public override int Life { get; protected set; } = 150;
+ public override int MaxLife { get; protected set; } = 150;
+ public override int Speed { get; } = 5;
+ public override int Attack { get; protected set; } = 8;
+ public override int AttackRadius { get; protected set; } = 48;
+ public override int SightRadius { get; protected set; } = 150;
+ public override TimeSpan TotalRecovery { get; } = TimeSpan.FromSeconds(1);
+ public override TimeSpan Recovery { get; set; }
+ protected override ISpriteCreature[] Sprite { get; } = {new ZombieSprite()};
+ protected override IMovable MovementScheme { get; } = new PlayerMovement();
+ protected override CreatureType Type { get; } = CreatureType.Zombie;
+ public override Faction Faction { get; } = Faction.Undead;
+ public override ICreature IsAttacking { get; set; }
+ public override IBuilding IsAttackingBuilding { get; set; }
+
+ public Zombie(ContentManager contentManager,
+ Pathfinder pathfinder, IOptionsManager optionsManager, AchievementsAndStatistics achievementsAndStatistics)
+ : base(contentManager, pathfinder, optionsManager,achievementsAndStatistics)
+ {
+ }
+
+ public override void LoadData(CreatureData data)
+ {
+ base.LoadData(data);
+
+ if (IsUpgraded)
+ ChangeEquipment(EquipmentType.Body, new ZombieWithClubSprite());
+ }
+ }
+}