diff options
Diffstat (limited to 'V3/Objects')
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()); + } + } +} |