diff --git a/Assets/Scripts/Logic/Data/GameOverview.cs b/Assets/Scripts/Logic/Data/GameOverview.cs index fdbfa175626982e81ca91d078f7f18024200fe56..85aa3eb5268be0549c1d220ccf11062fc9b1fb9c 100644 --- a/Assets/Scripts/Logic/Data/GameOverview.cs +++ b/Assets/Scripts/Logic/Data/GameOverview.cs @@ -94,7 +94,8 @@ public class GameOverview : IGameOverview { TimeLeftFromPhase = Math.Max(OverviewConfig.FightingPhaseDuration, World.GetTileObjectsOfType<Barrack>() .Max(barrack => - barrack.QueuedUnits.Count * World.Config.BarrackSpawnCooldownTime)); + barrack.QueuedUnits.Count * World.Config.BarrackSpawnCooldownTime + + barrack.Ordinal * World.Config.BarrackSpawnTimeOffset)); } else if (CurrentPhase == GamePhase.Fight) { if (Teams.Any(t => t.Castle.IsDestroyed)) { CurrentPhase = GamePhase.Finished; diff --git a/Assets/Scripts/Logic/Data/GameTeam.cs b/Assets/Scripts/Logic/Data/GameTeam.cs index ff6bf8fab4d9cb33bd2c5a7c6e42bf4042c7e33e..9cc9214ed179ac762e34c615ca995085da99caf2 100644 --- a/Assets/Scripts/Logic/Data/GameTeam.cs +++ b/Assets/Scripts/Logic/Data/GameTeam.cs @@ -55,7 +55,7 @@ public class GameTeam { Overview = overview; TeamColor = color; Castle = castle; - Barracks = new List<Barrack>(barracks); + Barracks = barracks.OrderBy(barrack => barrack.Ordinal).ToList(); Money = overview.EconomyConfig.StartingBalance; } @@ -122,7 +122,7 @@ public class GameTeam { _availableTowerPositionsCache.ExceptWith(world.Units.Select(u => u.TilePosition)); _availableTowerPositionsCache.RemoveWhere(position => { - ISet<TilePosition> blocked = new HashSet<TilePosition> { position }; + ISet<TilePosition> blocked = new HashSet<TilePosition> {position}; foreach (GameTeam sourceTeam in Overview.Teams) { TilePosition from = Overview.GetEnemyTeam(sourceTeam).Castle.Position; diff --git a/Assets/Scripts/Logic/Data/World/Barrack.cs b/Assets/Scripts/Logic/Data/World/Barrack.cs index 557ec35a5d2c39bf0c1eb9cb765d843242aa3348..713f6008e185ed0a9658f9985146f20986414cb6 100644 --- a/Assets/Scripts/Logic/Data/World/Barrack.cs +++ b/Assets/Scripts/Logic/Data/World/Barrack.cs @@ -5,7 +5,6 @@ using Logic.Event.World.Barrack; using Logic.Event.World.Unit; namespace Logic.Data.World { - public class Barrack : Building { #region Fields @@ -25,12 +24,16 @@ public class Barrack : Building { public IReadOnlyCollection<IUnitTypeData> QueuedUnits => new List<IUnitTypeData>(_queuedUnits); + public int Ordinal { get; } + #endregion #region Methods - internal Barrack(GameWorld world, TilePosition position, Color owner) - : base(world, position, owner) {} + internal Barrack(GameWorld world, TilePosition position, Color owner, int ordinal) + : base(world, position, owner) { + Ordinal = ordinal; + } internal void QueueUnit(IUnitTypeData type) { _queuedUnits.Add(type); @@ -62,7 +65,7 @@ public class Barrack : Building { } internal void ResetCooldown() { - RemainingCooldownTime = 0; + RemainingCooldownTime = Ordinal * World.Config.BarrackSpawnTimeOffset; } internal void Spawn() { @@ -98,5 +101,4 @@ public class Barrack : Building { #endregion } - } diff --git a/Assets/Scripts/Logic/Data/World/GameWorld.cs b/Assets/Scripts/Logic/Data/World/GameWorld.cs index ffd0340aeb9b2e283dd6e73242fa5ecb8dfeb35c..f32bcb543ab8df5b6a3775ba43b4201bd54476ab 100644 --- a/Assets/Scripts/Logic/Data/World/GameWorld.cs +++ b/Assets/Scripts/Logic/Data/World/GameWorld.cs @@ -95,6 +95,7 @@ public class GameWorld { #endregion private class TileObjectConstructors : WorldGenerator.ITileObjectConstructors { + private readonly IDictionary<Color, int> _barrackCounter = new Dictionary<Color, int>(); private readonly GameWorld _world; public TileObjectConstructors(GameWorld world) { @@ -106,7 +107,10 @@ public class GameWorld { } public Barrack CreateBarrack(TilePosition position, Color team) { - return new Barrack(_world, position, team); + _barrackCounter.TryGetValue(team, out int index); + Barrack barrack = new Barrack(_world, position, team, index); + _barrackCounter[team] = index + 1; + return barrack; } public Obstacle CreateObstacle(TilePosition position) { diff --git a/Assets/Scripts/Logic/Data/World/IGameWorldConfig.cs b/Assets/Scripts/Logic/Data/World/IGameWorldConfig.cs index 6f952f8d0058a97158baafc3c13fd774814476f3..10cf551690b1eb4d4df670fb8c71796619905a1c 100644 --- a/Assets/Scripts/Logic/Data/World/IGameWorldConfig.cs +++ b/Assets/Scripts/Logic/Data/World/IGameWorldConfig.cs @@ -1,5 +1,4 @@ ďťżnamespace Logic.Data.World { - public interface IGameWorldConfig { public int Width { get; } public int Height { get; } @@ -7,6 +6,6 @@ public interface IGameWorldConfig { public float CastleStartingHealth { get; } public int MaxBuildingDistance { get; } public bool GenerateObstacles { get; } + public float BarrackSpawnTimeOffset { get; } } - } diff --git a/Assets/Scripts/Logic/Data/World/WorldGenerator.cs b/Assets/Scripts/Logic/Data/World/WorldGenerator.cs index fa2f9a091766a2e322d371fea9a2eba0f50fbbe3..7e65c8f5ba4a3d3a9fc3fd0626969740e93810a1 100644 --- a/Assets/Scripts/Logic/Data/World/WorldGenerator.cs +++ b/Assets/Scripts/Logic/Data/World/WorldGenerator.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; namespace Logic.Data.World { - internal class WorldGenerator { public static TileObject[,] GenerateGrid(int seed, int width, int height, bool generateObstacles, ITileObjectConstructors constructors) { @@ -51,11 +50,11 @@ internal class WorldGenerator { occupiedList.Add(_constructors.CreateBarrack(new TilePosition(x, y), Color.Red)); - int obstacleCount = _generateObstacles ? (_random.Next() % ((_width * _height) / 60)) + 2 : 0; + int obstacleCount = _generateObstacles ? (_random.Next() % ((_width * _height) / 40)) + 2 : 0; int i = 0, rep = 0; while (i < obstacleCount && rep < 1000) { ++rep; - int obstacleSize = _random.Next() % 3 + 1; + int obstacleSize = _random.Next() % 2 + 1; x = _random.Next() % (_width - obstacleSize); y = _random.Next() % ((_height / 2) - obstacleSize); bool allGood = true; @@ -79,6 +78,21 @@ internal class WorldGenerator { } } + if (i == 0) { + List<int> goodIndices = new List<int>(); + for (int j = 0; j < _width; j++) { + tp = new TilePosition(_height / 2, _width); + if (!occupiedList.Where(building => !(building is Obstacle)) + .Any(occupied => occupied.Position.FirstNormDistance(tp) < 4)) { + goodIndices.Add(j); + } + } + + occupiedList.Add(_constructors.CreateObstacle(new TilePosition( + goodIndices[_random.Next() % goodIndices.Count], + _height / 2))); + } + List<TileObject> otherSide = new List<TileObject>(); foreach (TileObject tileObject in occupiedList) { TilePosition newPos = new TilePosition(_width, _height) @@ -109,5 +123,4 @@ internal class WorldGenerator { public Obstacle CreateObstacle(TilePosition position); } } - } diff --git a/Assets/Scripts/Logic/System/InvalidateCachesSystem.cs b/Assets/Scripts/Logic/System/InvalidateCachesSystem.cs index 54bf7766638b7d7af06c71bad7779d0299564359..a22e78a6faaea53a4be1b752c5776578da42ab06 100644 --- a/Assets/Scripts/Logic/System/InvalidateCachesSystem.cs +++ b/Assets/Scripts/Logic/System/InvalidateCachesSystem.cs @@ -5,7 +5,6 @@ using Logic.Event.World.Tower; using Logic.Event.World.Unit; namespace Logic.System { - /// <summary> /// System responsible for clearing/recalculating cached values. These include: /// <list type="bullet"> @@ -79,5 +78,4 @@ internal class InvalidateCachesSystem : BaseSystem { } } } - } diff --git a/Assets/Scripts/LogicTests/BarrackUnitIntegrationTest.cs b/Assets/Scripts/LogicTests/BarrackUnitIntegrationTest.cs index 532157923ae78888026670422a3f243e48e90e43..f91efddb169eb1eb5d178a33ff8100b07be34e27 100644 --- a/Assets/Scripts/LogicTests/BarrackUnitIntegrationTest.cs +++ b/Assets/Scripts/LogicTests/BarrackUnitIntegrationTest.cs @@ -6,7 +6,6 @@ using Logic.Data.World; using NUnit.Framework; namespace LogicTests { - /// <summary> /// Tests the interaction between the <see cref="Barrack"/> and the <see cref="Unit"/> classes: /// unit purchasing causing units to get queued and barracks spawning units. @@ -34,43 +33,43 @@ public class BarrackUnitIntegrationTest { public void TestBarracksSlowlySpawnUnits() { GameOverview overview = GameTestUtils.CreateOverview(); GameTeam team = overview.Teams.First(); - IUnitTypeData unitType = new GameTestUtils.UnitTypeData { Speed = 0 }; + IUnitTypeData unitType = new GameTestUtils.UnitTypeData {Speed = 0}; - //Purchase 3 units -> one of the barracks must have at least 2 - for (int i = 0; i < 3; i++) Assert.IsTrue(overview.Commands.Issue(new PurchaseUnitCommand(team, unitType))); - Barrack barrack = team.Barracks.OrderByDescending(b => b.QueuedUnits.Count).First(); + //Purchase 2 units for the first barrack + team.Barracks.First().QueueUnit(unitType); + team.Barracks.First().QueueUnit(unitType); //Enter fighting phase Assert.IsTrue(overview.Commands.Issue(new AdvancePhaseCommand(overview))); Assert.AreEqual(GamePhase.Fight, overview.CurrentPhase); Assert.AreEqual(0, overview.World.Units.Count); - Assert.AreEqual(3, team.Barracks.Sum(b => b.QueuedUnits.Count)); + Assert.AreEqual(2, team.Barracks.First().QueuedUnits.Count); - //Spawn the first unit(s) + //Spawn the first unit Assert.IsTrue(overview.Commands.Issue(new AdvanceTimeCommand(overview, float.Epsilon))); - Assert.AreEqual(3, overview.World.Units.Count + team.Barracks.Sum(b => b.QueuedUnits.Count)); - Assert.IsTrue(barrack.QueuedUnits.Count > 0); - Assert.IsTrue(barrack.IsOnCooldown); + Assert.AreEqual(2, overview.World.Units.Count + team.Barracks.Sum(b => b.QueuedUnits.Count)); + Assert.IsTrue(team.Barracks.First().QueuedUnits.Count > 0); + Assert.IsTrue(team.Barracks.First().IsOnCooldown); //Assert that cooldown lasts float deltaTime = overview.World.Config.BarrackSpawnCooldownTime / 10; for (int i = 0; i < 9; i++) Assert.IsTrue(overview.Commands.Issue(new AdvanceTimeCommand(overview, deltaTime))); - Assert.IsTrue(barrack.QueuedUnits.Count > 0); - Assert.IsTrue(barrack.IsOnCooldown); + Assert.IsTrue(team.Barracks.First().QueuedUnits.Count > 0); + Assert.IsTrue(team.Barracks.First().IsOnCooldown); //Assert that the barrack spawn another unit - int oldQueued = barrack.QueuedUnits.Count; + int oldQueued = team.Barracks.First().QueuedUnits.Count; for (int i = 0; i < 2; i++) Assert.IsTrue(overview.Commands.Issue(new AdvanceTimeCommand(overview, deltaTime))); - Assert.AreEqual(3, overview.World.Units.Count + team.Barracks.Sum(b => b.QueuedUnits.Count)); - Assert.AreEqual(oldQueued - 1, barrack.QueuedUnits.Count); - Assert.IsTrue(barrack.IsOnCooldown); + Assert.AreEqual(2, overview.World.Units.Count + team.Barracks.Sum(b => b.QueuedUnits.Count)); + Assert.AreEqual(oldQueued - 1, team.Barracks.First().QueuedUnits.Count); + Assert.IsTrue(team.Barracks.First().IsOnCooldown); //Assert that the barrack won't be on cooldown after spawning all units deltaTime = overview.World.Config.BarrackSpawnCooldownTime * 1.1f; for (int i = 0; i < 3; i++) Assert.IsTrue(overview.Commands.Issue(new AdvanceTimeCommand(overview, deltaTime))); - Assert.AreEqual(3, overview.World.Units.Count); + Assert.AreEqual(2, overview.World.Units.Count); Assert.AreEqual(0, team.Barracks.Sum(b => b.QueuedUnits.Count)); - Assert.IsFalse(barrack.IsOnCooldown); + Assert.IsFalse(team.Barracks.First().IsOnCooldown); } [Test] @@ -95,5 +94,4 @@ public class BarrackUnitIntegrationTest { Assert.AreEqual(expectedMoney, team.Money); } } - } diff --git a/Assets/Scripts/LogicTests/GameOverviewTest.cs b/Assets/Scripts/LogicTests/GameOverviewTest.cs index b56423088a8d05b37287705c2525062b7fd97892..44b07a610c1715dd15a4b7581c2447014a987776 100644 --- a/Assets/Scripts/LogicTests/GameOverviewTest.cs +++ b/Assets/Scripts/LogicTests/GameOverviewTest.cs @@ -6,7 +6,6 @@ using Logic.Data; using NUnit.Framework; namespace LogicTests { - /// <summary> /// Container of unit tests for the <see cref="GameOverview"/> class. /// This class encapsulates almost all other classes in the logic component, @@ -48,8 +47,8 @@ public class GameOverviewTest { GameOverview overview = GameTestUtils.CreateOverview(); Assert.AreEqual(GamePhase.Prepare, overview.CurrentPhase); - GameTestUtils.UnitTypeData unitType = new GameTestUtils.UnitTypeData { Speed = float.Epsilon }; - Assert.IsTrue(overview.Commands.Issue(new PurchaseUnitCommand(overview.Teams.First(), unitType))); + GameTestUtils.UnitTypeData unitType = new GameTestUtils.UnitTypeData {Speed = float.Epsilon}; + overview.Teams.First().Barracks.First().QueueUnit(unitType); overview.AdvancePhase(); Assert.AreEqual(GamePhase.Fight, overview.CurrentPhase); @@ -80,5 +79,4 @@ public class GameOverviewTest { Assert.AreEqual(GamePhase.Finished, overview.CurrentPhase); } } - } diff --git a/Assets/Scripts/LogicTests/GameTestUtils.cs b/Assets/Scripts/LogicTests/GameTestUtils.cs index abb554f8e9ea1800661de399bbb2f7652a14cd04..aba6a1dc98280c7c1133d2646d1ed3b6d2ec7510 100644 --- a/Assets/Scripts/LogicTests/GameTestUtils.cs +++ b/Assets/Scripts/LogicTests/GameTestUtils.cs @@ -5,7 +5,6 @@ using Logic.Data.World; using NUnit.Framework; namespace LogicTests { - /// <summary> /// Utility methods regarding the whole game (<see cref="GameOverview"/>). /// </summary> @@ -121,6 +120,8 @@ public static class GameTestUtils { public float CastleStartingHealth { get; set; } = 10; public int MaxBuildingDistance { get; set; } = 5; public bool GenerateObstacles { get; set; } = true; + + public float BarrackSpawnTimeOffset { get; set; } = 0.1f; } /// <summary> @@ -156,5 +157,4 @@ public static class GameTestUtils { public ITowerTypeData AfterUpgradeType { get; set; } = null; } } - } diff --git a/Assets/Scripts/LogicTests/RandomUtils.cs b/Assets/Scripts/LogicTests/RandomUtils.cs index 9ef69e2cbf88c401b80297f98fa9425d33b7dde6..1fea40814d40e50dc48799d251a061d3158ff39d 100644 --- a/Assets/Scripts/LogicTests/RandomUtils.cs +++ b/Assets/Scripts/LogicTests/RandomUtils.cs @@ -2,7 +2,6 @@ using NUnit.Framework; namespace LogicTests { - /// <summary> /// Utility methods regarding random number generation. /// </summary> @@ -27,5 +26,4 @@ public static class RandomUtils { } } } - } diff --git a/Assets/Scripts/LogicTests/UnitTowerCastleIntegrationTest.cs b/Assets/Scripts/LogicTests/UnitTowerCastleIntegrationTest.cs index beaf880a441337cd25bea783dd612584dcde283d..c1d0de961cba38c6cc73c41de1852f5f2836fd91 100644 --- a/Assets/Scripts/LogicTests/UnitTowerCastleIntegrationTest.cs +++ b/Assets/Scripts/LogicTests/UnitTowerCastleIntegrationTest.cs @@ -11,7 +11,6 @@ using Logic.Event.World.Unit; using NUnit.Framework; namespace LogicTests { - /// <summary> /// Tests the basic interactions between <see cref="Unit"/>, <see cref="Castle"/> /// and <see cref="Tower"/> instances: unit damaging castle, unit destroying castle, @@ -117,8 +116,8 @@ public class UnitTowerCastleIntegrationTest { GameTeam towerTeam = overview.GetEnemyTeam(unitTeam); //Purchase a unit with zero speed - GameTestUtils.UnitTypeData unitType = new GameTestUtils.UnitTypeData { Speed = 0 }; - Assert.IsTrue(overview.Commands.Issue(new PurchaseUnitCommand(unitTeam, unitType))); + GameTestUtils.UnitTypeData unitType = new GameTestUtils.UnitTypeData {Speed = 0}; + unitTeam.Barracks.First().QueueUnit(unitType); //Build a tower with zero damage and high range GameTestUtils.TowerTypeData towerType = new GameTestUtils.TowerTypeData { @@ -157,7 +156,7 @@ public class UnitTowerCastleIntegrationTest { [Test] public void TestTowerTargetReachedCastle() { GameOverview overview = CreateTowerTargetTestingGame(10, 10, - (towerTeam, _) => new[] { towerTeam.AvailableTowerPositions.First() }, 1, 0, float.PositiveInfinity, 1); + (towerTeam, _) => new[] {towerTeam.AvailableTowerPositions.First()}, 1, 0, float.PositiveInfinity, 1); //Update the tower's target Assert.IsTrue(overview.Commands.Issue(new AdvanceTimeCommand(overview, float.Epsilon))); @@ -194,8 +193,8 @@ public class UnitTowerCastleIntegrationTest { [Test] public void TestTowerTargetMovedOutOfRange() { IEnumerable<TilePosition> TowerPositionChooser(GameTeam towerTeam, GameWorld world) { - int[] dx = { -1, 0, 0, 1 }; - int[] dy = { 0, 1, -1, 0 }; + int[] dx = {-1, 0, 0, 1}; + int[] dy = {0, 1, -1, 0}; foreach (Barrack barrack in world.Overview.GetEnemyTeam(towerTeam).Barracks) { bool any = false; for (int i = 0; i < 4 && !any; i++) { @@ -244,8 +243,8 @@ public class UnitTowerCastleIntegrationTest { GameTeam towerTeam = overview.GetEnemyTeam(unitTeam); //Purchase a unit - GameTestUtils.UnitTypeData unitType = new GameTestUtils.UnitTypeData { Health = unitHealth }; - Assert.IsTrue(overview.Commands.Issue(new PurchaseUnitCommand(unitTeam, unitType))); + GameTestUtils.UnitTypeData unitType = new GameTestUtils.UnitTypeData {Health = unitHealth}; + overview.Teams.First().Barracks.First().QueueUnit(unitType); //Build a tower GameTestUtils.TowerTypeData towerType = new GameTestUtils.TowerTypeData { @@ -265,5 +264,4 @@ public class UnitTowerCastleIntegrationTest { return overview; } } - } diff --git a/Assets/Scripts/Presentation/World/Config/WorldConfig.cs b/Assets/Scripts/Presentation/World/Config/WorldConfig.cs index b335f127a47aa336e92edd40bb838849d5d56b1d..6468b1ea1a000076bcf86b7a80a6a38b905c16a6 100644 --- a/Assets/Scripts/Presentation/World/Config/WorldConfig.cs +++ b/Assets/Scripts/Presentation/World/Config/WorldConfig.cs @@ -41,6 +41,10 @@ public class WorldConfig : ScriptableObject, IGameWorldConfig { [Min(0)] private int maxBuildingDistance; + [SerializeField] + [Min(0.01f)] + private float barrackSpawnTimeOffset; + [NonSerialized] private int _height; @@ -65,5 +69,6 @@ public class WorldConfig : ScriptableObject, IGameWorldConfig { public float CastleStartingHealth => castleStartingHealth; public int MaxBuildingDistance => maxBuildingDistance; public bool GenerateObstacles => true; + public float BarrackSpawnTimeOffset => barrackSpawnTimeOffset; } } diff --git a/Assets/World/Config/WorldConfig.asset b/Assets/World/Config/WorldConfig.asset index 7651735645f268ccae500a512ff4d243cce3253c..27342230d020d9e80091f2d1cc8f7e21dc867768 100644 --- a/Assets/World/Config/WorldConfig.asset +++ b/Assets/World/Config/WorldConfig.asset @@ -19,3 +19,4 @@ MonoBehaviour: barrackSpawnCooldownTime: 0.6 castleStartingHealth: 10 maxBuildingDistance: 3 + barrackSpawnOffset: 0.1