diff --git a/GeometryDashAPI.Tests/LevelLengthTests.cs b/GeometryDashAPI.Tests/LevelLengthTests.cs new file mode 100644 index 0000000..ad0b5cb --- /dev/null +++ b/GeometryDashAPI.Tests/LevelLengthTests.cs @@ -0,0 +1,24 @@ +using System.IO; +using System.Net; +using FluentAssertions; +using GeometryDashAPI.Levels; +using GeometryDashAPI.Server; +using GeometryDashAPI.Server.Responses; +using NUnit.Framework; + +namespace GeometryDashAPI.Tests; + +[TestFixture] +public class LevelLengthTests +{ + [TestCase("12034598_Conclusion", 57)] + [TestCase("28755513_TheFinalLair", 154)] + [TestCase("116631_XmasParty", 87)] + public void Test(string fileName, int expectedSeconds) + { + var responseBody = File.ReadAllText(Path.Combine("data", "levels", fileName)); + var response = new ServerResponse(HttpStatusCode.OK, responseBody); + var level = new Level(response.GetResultOrDefault().Level.LevelString, compressed: true); + level.LevelLength.TotalSeconds.Should().BeApproximately(expectedSeconds, precision: 1); + } +} diff --git a/GeometryDashAPI/Levels/GameObjects/Specific/SpeedBlock.cs b/GeometryDashAPI/Levels/GameObjects/Specific/SpeedBlock.cs index 9fefb0a..b86ed87 100644 --- a/GeometryDashAPI/Levels/GameObjects/Specific/SpeedBlock.cs +++ b/GeometryDashAPI/Levels/GameObjects/Specific/SpeedBlock.cs @@ -1,9 +1,14 @@ -using GeometryDashAPI.Attributes; +using System; +using GeometryDashAPI.Attributes; using GeometryDashAPI.Levels.Enums; -using GeometryDashAPI.Levels.GameObjects.Default; namespace GeometryDashAPI.Levels.GameObjects.Specific { + /// + /// Represents the id of the speed block.

+ /// Not to be confused with .
+ /// Because it is responsible for a specific speed, instead of a specific block id + ///
public enum SpeedBlockId { Orange = 200, @@ -14,20 +19,23 @@ public enum SpeedBlockId } [GameBlock(200, 201, 202, 203, 1334)] - public class SpeedBlock : Block + public class SpeedBlock : Portal { [GameProperty("24", (short)Layer.B2)] protected override short zLayer { get; set; } = (short)Layer.B2; [GameProperty("25", -6)] public override int ZOrder { get; set; } = -6; - [GameProperty("13", true, true)] - public bool Using { get; set; } = true; - public SpeedBlockId BlockType { get => (SpeedBlockId)Id; set => Id = (int)value; } + public SpeedType SpeedType + { + get => FromBlockIdToSpeedType(BlockType); + set => BlockType = FromSpeedTypeToBlockId(value); + } + public SpeedBlock() : base(201) { } @@ -35,5 +43,35 @@ public SpeedBlock() : base(201) public SpeedBlock(SpeedBlockId type) : base((int)type) { } + + public SpeedBlock(SpeedType type) : base((int)FromSpeedTypeToBlockId(type)) + { + } + + public static SpeedType FromBlockIdToSpeedType(SpeedBlockId id) + { + return id switch + { + SpeedBlockId.Orange => SpeedType.Half, + SpeedBlockId.Default => SpeedType.Default, + SpeedBlockId.Green => SpeedType.X2, + SpeedBlockId.Purple => SpeedType.X3, + SpeedBlockId.Red => SpeedType.X4, + _ => throw new ArgumentOutOfRangeException(nameof(id), id, null) + }; + } + + public static SpeedBlockId FromSpeedTypeToBlockId(SpeedType speedType) + { + return speedType switch + { + SpeedType.Half => SpeedBlockId.Orange, + SpeedType.Default => SpeedBlockId.Default, + SpeedType.X2 => SpeedBlockId.Green, + SpeedType.X3 => SpeedBlockId.Purple, + SpeedType.X4 => SpeedBlockId.Red, + _ => throw new ArgumentOutOfRangeException(nameof(speedType), speedType, null) + }; + } } } diff --git a/GeometryDashAPI/Levels/GameObjects/Triggers/DisableBGEffectTrigger.cs b/GeometryDashAPI/Levels/GameObjects/Triggers/DisableBgEffectTrigger.cs similarity index 100% rename from GeometryDashAPI/Levels/GameObjects/Triggers/DisableBGEffectTrigger.cs rename to GeometryDashAPI/Levels/GameObjects/Triggers/DisableBgEffectTrigger.cs diff --git a/GeometryDashAPI/Levels/GameObjects/Triggers/EnableBGEffectTrigger.cs b/GeometryDashAPI/Levels/GameObjects/Triggers/EnableBgEffectTrigger.cs similarity index 100% rename from GeometryDashAPI/Levels/GameObjects/Triggers/EnableBGEffectTrigger.cs rename to GeometryDashAPI/Levels/GameObjects/Triggers/EnableBgEffectTrigger.cs diff --git a/GeometryDashAPI/Levels/Level.cs b/GeometryDashAPI/Levels/Level.cs index 64bca39..851692c 100644 --- a/GeometryDashAPI/Levels/Level.cs +++ b/GeometryDashAPI/Levels/Level.cs @@ -23,6 +23,8 @@ public Guidelines Guidelines set => Options.Guidelines = value; } + public TimeSpan LevelLength => Levels.LevelLength.Measure(this); + public int CountBlock => Blocks.Count; public int CountColor => Options.Colors.Count; diff --git a/GeometryDashAPI/Levels/LevelLength.cs b/GeometryDashAPI/Levels/LevelLength.cs new file mode 100644 index 0000000..555af5d --- /dev/null +++ b/GeometryDashAPI/Levels/LevelLength.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using GeometryDashAPI.Levels.GameObjects.Specific; + +namespace GeometryDashAPI.Levels +{ + public static class LevelLength + { + public static TimeSpan Measure(Level level) + { + var maxX = level.Blocks.Max(x => x.PositionX); + var portals = GetSpeedBlocks(level); + + var seconds = 0d; + var i = 1; + for (; i < portals.Length; i++) + seconds += (portals[i].PositionX - portals[i - 1].PositionX) / portals[i - 1].SpeedType.GetSpeed(); + + var final = Math.Min(i, portals.Length - 1); + var total = seconds + (maxX - portals[final].PositionX) / portals[final].SpeedType.GetSpeed(); + + return TimeSpan.FromSeconds(total); + } + + private static SpeedBlock[] GetSpeedBlocks(Level level) + { + return new[] { new SpeedBlock(level.Options.PlayerSpeed) } + .Concat(level.Blocks.OfType() + .Where(x => x.Checked) + .OrderBy(x => x.PositionX) + ).ToArray(); + } + } +} diff --git a/GeometryDashAPI/Levels/LevelSpeed.cs b/GeometryDashAPI/Levels/LevelSpeed.cs new file mode 100644 index 0000000..e51213e --- /dev/null +++ b/GeometryDashAPI/Levels/LevelSpeed.cs @@ -0,0 +1,25 @@ +using GeometryDashAPI.Levels.Enums; + +namespace GeometryDashAPI.Levels +{ + public static class LevelSpeed + { + public const double Half = 251.16; + public const double Default = 311.58; + public const double X2 = 387.42; + public const double X3 = 468; + public const double X4 = 576; + + public static double GetSpeed(this SpeedType speedType) + { + return speedType switch + { + SpeedType.Half => Half, + SpeedType.Default => Default, + SpeedType.X2 => X2, + SpeedType.X3 => X3, + SpeedType.X4 => X4 + }; + } + } +} diff --git a/GeometryDashAPI/Levels/Portal.cs b/GeometryDashAPI/Levels/Portal.cs new file mode 100644 index 0000000..24cfcb5 --- /dev/null +++ b/GeometryDashAPI/Levels/Portal.cs @@ -0,0 +1,18 @@ +using GeometryDashAPI.Attributes; +using GeometryDashAPI.Levels.GameObjects.Default; + +namespace GeometryDashAPI.Levels +{ + public class Portal : Block + { + [GameProperty("13", true, true)] public bool Checked { get; set; } + + public Portal() + { + } + + public Portal(int id) : base(id) + { + } + } +} \ No newline at end of file