Fix remaining Scheme bugs.

- Implement IEquatable.
- Fix RoundTimeSeconds not properly being parsed.
- Uniquely throw ArgumentOutOfRangeException even for rounding properties.
- Fix FallDamage not being stored properly.
- Add tests for rounding properties and local scheme test files.
- Implement equality operations for SchemeWeapon.
This commit is contained in:
Ray Koopa 2020-06-27 16:30:49 +02:00
parent 378d2474b1
commit 88d07a1ff1
92 changed files with 389 additions and 257 deletions

View File

@ -5,7 +5,7 @@
<Description>.NET library for loading and modifying files of Worms Armageddon ProjectX.</Description>
<PackageReleaseNotes>Fix saving files. Uniquely leave open Stream instances when saving into them.</PackageReleaseNotes>
<PackageTags>$(PackageTags);project x;worms armageddon</PackageTags>
<Version>3.0.1</Version>
<Version>3.1.1</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms.Armageddon\Syroot.Worms.Armageddon.csproj" />

View File

@ -14,7 +14,7 @@ namespace Syroot.Worms.Armageddon
/// RubberWorm settings encoded in those.
/// Used by WA and WWP. S. https://worms2d.info/Game_scheme_file.
/// </summary>
public class Scheme : ILoadableFile, ISaveableFile
public class Scheme : IEquatable<Scheme>, ILoadableFile, ISaveableFile
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
@ -28,7 +28,7 @@ namespace Syroot.Worms.Armageddon
private static readonly byte[] _waterRiseRates;
private byte _fallDamage;
private int _fallDamage;
private byte _waterRiseIndex;
private byte _objectCount;
private byte _mineDelay;
@ -147,8 +147,8 @@ namespace Syroot.Worms.Armageddon
/// values lie between 0-508, at a granularity of 4. Note that changing this value rounds it down to a valid
/// granularity.
/// </summary>
/// <remarks>Valid settings can be enumerated through <see cref="ValidFallDamages"/>.</remarks>
/// <exception cref="ArgumentOutOfRangeException">The value is not between 0-508.</exception>
/// <remarks>Valid settings can be enumerated through <see cref="ValidFallDamages"/>.</remarks>
public int FallDamage
{
get => _fallDamage;
@ -156,7 +156,8 @@ namespace Syroot.Worms.Armageddon
{
if (value < 0 || value > 508)
throw new ArgumentOutOfRangeException(nameof(value), "Fall damage must be between 0-508.");
_fallDamage = (byte)(value >> 2 << 2);
_fallDamage = value >> 2 << 2;
}
}
@ -191,16 +192,20 @@ namespace Syroot.Worms.Armageddon
/// Gets or sets the amount in pixels which the water will rise between turns after Sudden Death was triggered.
/// Note that changing this value rounds it down to a valid setting.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">The value is not between 0-253.</exception>
/// <remarks>Valid settings can be enumerated through <see cref="ValidWateRiseRates"/>.</remarks>
public byte WaterRiseRate
{
get => _waterRiseRates[_waterRiseIndex];
set
{
if (value > 253)
throw new ArgumentOutOfRangeException(nameof(value), "Water rise rate must be between 0-253.");
int smallestDeviation = Int32.MaxValue;
for (int i = 0; i < _waterRiseRates.Length; i++)
{
int deviation = Math.Abs(value - _waterRiseRates[i]);
int deviation = value - _waterRiseRates[i];
if (deviation >= 0 && smallestDeviation > deviation)
{
smallestDeviation = deviation;
@ -247,12 +252,16 @@ namespace Syroot.Worms.Armageddon
/// Gets or sets the maximum number of objects (mines or oil drums) on the map. Note that changing this value
/// rounds it down to a valid setting.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">The value is not between 1-250.</exception>
/// <remarks>Valid settings can be enumerated through <see cref="ValidObjectCounts"/>.</remarks>
public byte ObjectCount
{
get => _objectCount;
set
{
if (value < 1 || value > 250)
throw new ArgumentOutOfRangeException(nameof(value), "Object count must be between 1-250.");
if (value > 100)
_objectCount = (byte)(value - value % 10);
else if (value > 30)
@ -651,10 +660,10 @@ namespace Syroot.Worms.Armageddon
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Loads the data from the given <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
/// <inheritdoc/>
public override bool Equals(object obj) => Equals(obj as Scheme);
/// <inheritdoc/>
public void Load(Stream stream)
{
using BinaryStream reader = new BinaryStream(stream, encoding: Encoding.ASCII, leaveOpen: true);
@ -714,20 +723,14 @@ namespace Syroot.Worms.Armageddon
LoadRubberWormSettings();
}
/// <summary>
/// Loads the data from the given file.
/// </summary>
/// <param name="fileName">The name of the file to load the data from.</param>
/// <inheritdoc/>
public void Load(string fileName)
{
using FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Load(stream);
}
/// <summary>
/// Saves the data into the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to save the data to.</param>
/// <inheritdoc/>
public void Save(Stream stream) => Save(stream, SchemeSaveFormat.ExtendedWithObjectCount);
/// <summary>
@ -790,10 +793,7 @@ namespace Syroot.Worms.Armageddon
writer.WriteStruct(weapon);
}
/// <summary>
/// Saves the data in the given file.
/// </summary>
/// <param name="fileName">The name of the file to save the data in.</param>
/// <inheritdoc/>
public void Save(string fileName) => Save(fileName, SchemeSaveFormat.ExtendedWithObjectCount);
/// <summary>
@ -809,6 +809,101 @@ namespace Syroot.Worms.Armageddon
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
/// <inheritdoc/>
public bool Equals(Scheme other)
=> other != null
&& Version == other.Version
&& HotSeatDelay == other.HotSeatDelay
&& RetreatTime == other.RetreatTime
&& RetreatTimeRope == other.RetreatTimeRope
&& ShowRoundTime == other.ShowRoundTime
&& AutomaticReplays == other.AutomaticReplays
&& FallDamage == other.FallDamage
&& ArtilleryMode == other.ArtilleryMode
&& SchemeEditor == other.SchemeEditor
&& StockpilingMode == other.StockpilingMode
&& WormSelectMode == other.WormSelectMode
&& SuddenDeathEvent == other.SuddenDeathEvent
&& WaterRiseRate == other.WaterRiseRate
&& WeaponCrateProbability == other.WeaponCrateProbability
&& DonorCards == other.DonorCards
&& HealthCrateProbability == other.HealthCrateProbability
&& HealthCrateEnergy == other.HealthCrateEnergy
&& UtilityCrateProbability == other.UtilityCrateProbability
&& ObjectTypes == other.ObjectTypes
&& ObjectCount == other.ObjectCount
&& MineDelay == other.MineDelay
&& MineDelayRandom == other.MineDelayRandom
&& DudMines == other.DudMines
&& ManualWormPlacement == other.ManualWormPlacement
&& WormEnergy == other.WormEnergy
&& TurnTime == other.TurnTime
&& TurnTimeInfinite == other.TurnTimeInfinite
&& RoundTimeMinutes == other.RoundTimeMinutes
&& RoundTimeSeconds == other.RoundTimeSeconds
&& NumberOfWins == other.NumberOfWins
&& Blood == other.Blood
&& AquaSheep == other.AquaSheep
&& SheepHeaven == other.SheepHeaven
&& GodWorms == other.GodWorms
&& IndestructibleLand == other.IndestructibleLand
&& UpgradedGrenade == other.UpgradedGrenade
&& UpgradedShotgun == other.UpgradedShotgun
&& UpgradedCluster == other.UpgradedCluster
&& UpgradedLongbow == other.UpgradedLongbow
&& EnableTeamWeapons == other.EnableTeamWeapons
&& EnableSuperWeapons == other.EnableSuperWeapons
&& Weapons.SequenceEqual(other.Weapons);
/// <inheritdoc/>
public override int GetHashCode()
{
HashCode hash = new HashCode();
hash.Add(Version);
hash.Add(HotSeatDelay);
hash.Add(RetreatTime);
hash.Add(RetreatTimeRope);
hash.Add(ShowRoundTime);
hash.Add(AutomaticReplays);
hash.Add(FallDamage);
hash.Add(ArtilleryMode);
hash.Add(SchemeEditor);
hash.Add(StockpilingMode);
hash.Add(WormSelectMode);
hash.Add(SuddenDeathEvent);
hash.Add(WaterRiseRate);
hash.Add(WeaponCrateProbability);
hash.Add(DonorCards);
hash.Add(HealthCrateProbability);
hash.Add(HealthCrateEnergy);
hash.Add(UtilityCrateProbability);
hash.Add(ObjectTypes);
hash.Add(ObjectCount);
hash.Add(MineDelay);
hash.Add(MineDelayRandom);
hash.Add(DudMines);
hash.Add(ManualWormPlacement);
hash.Add(WormEnergy);
hash.Add(TurnTime);
hash.Add(TurnTimeInfinite);
hash.Add(RoundTimeMinutes);
hash.Add(RoundTimeSeconds);
hash.Add(NumberOfWins);
hash.Add(Blood);
hash.Add(AquaSheep);
hash.Add(SheepHeaven);
hash.Add(GodWorms);
hash.Add(IndestructibleLand);
hash.Add(UpgradedGrenade);
hash.Add(UpgradedShotgun);
hash.Add(UpgradedCluster);
hash.Add(UpgradedLongbow);
hash.Add(EnableTeamWeapons);
hash.Add(EnableSuperWeapons);
hash.Add(Weapons);
return hash.ToHashCode();
}
private int GetWeaponCount() => Version == SchemeVersion.Extended ? 64 : 45;
private void LoadObjectTypesAndCount(BinaryStream reader)
@ -851,10 +946,10 @@ namespace Syroot.Worms.Armageddon
private void LoadRoundTimeConfig(BinaryStream reader)
{
byte raw = reader.Read1Byte();
if (raw > 0x7F)
if (raw > 127)
{
RoundTimeMinutes = 0;
RoundTimeSeconds = (byte)(raw - 0x7F);
RoundTimeSeconds = (byte)(256 - raw);
}
else
{
@ -941,7 +1036,7 @@ namespace Syroot.Worms.Armageddon
private void SaveTurnTimeConfig(BinaryStream writer) => writer.Write(TurnTimeInfinite ? (byte)0xFF : TurnTime);
private void SaveRoundTimeConfig(BinaryStream writer) => writer.Write(RoundTimeSeconds > 0
? (byte)(0xFF - (RoundTimeSeconds - 1))
? (byte)(256 - RoundTimeSeconds)
: RoundTimeMinutes);
private void SaveRubberWormSettings()

View File

@ -1,25 +1,47 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Syroot.Worms.Armageddon
{
/// <summary>
/// Represents the configuration of a weapon.
/// </summary>
[DebuggerDisplay("Ammo={Ammunition} Power={Power} Delay={Delay} Prob={Probability}")]
[StructLayout(LayoutKind.Sequential)]
public struct SchemeWeapon
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Syroot.Worms.Armageddon
{
/// <summary>
/// Represents the configuration of a weapon.
/// </summary>
[DebuggerDisplay("Ammo={Ammunition} Power={Power} Delay={Delay} Prob={Probability}")]
[StructLayout(LayoutKind.Sequential)]
public struct SchemeWeapon : IEquatable<SchemeWeapon>
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
/// <summary>Amount with which a team is equipped at game start. 10 and negative values represent infinity.</summary>
public sbyte Ammunition;
/// <summary>Power of this weapon.</summary>
public byte Power;
/// <summary>Amount with which a team is equipped at game start. 10 and negative values represent infinity.</summary>
public sbyte Ammunition;
/// <summary>Power of this weapon.</summary>
public byte Power;
/// <summary>Number of turns required to be taken by each team before this weapon becomes available. Negative
/// values represent infinity.</summary>
public sbyte Delay;
/// <summary>Percentual chance of this weapon to appear in crates. Has no effect for super weapons.</summary>
public sbyte Probability;
}
}
/// values represent infinity.</summary>
public sbyte Delay;
/// <summary>Percentual chance of this weapon to appear in crates. Has no effect for super weapons.</summary>
public sbyte Probability;
// ---- OPERATORS ----------------------------------------------------------------------------------------------
public static bool operator ==(SchemeWeapon left, SchemeWeapon right) => left.Equals(right);
public static bool operator !=(SchemeWeapon left, SchemeWeapon right) => !(left == right);
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <inheritdoc/>
public override bool Equals(object obj) => obj is SchemeWeapon weapon && Equals(weapon);
/// <inheritdoc/>
public bool Equals(SchemeWeapon other)
=> Ammunition == other.Ammunition
&& Power == other.Power
&& Delay == other.Delay
&& Probability == other.Probability;
/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine(Ammunition, Power, Delay, Probability);
}
}

View File

@ -5,7 +5,7 @@
<Description>.NET library for loading and modifying files of Team17's Worms Armageddon.</Description>
<PackageReleaseNotes>Simplify scheme fall damage and water rise usage.</PackageReleaseNotes>
<PackageTags>$(PackageTags);worms armageddon</PackageTags>
<Version>3.1.0</Version>
<Version>3.1.1</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

View File

@ -5,7 +5,7 @@
<Description>.NET library for loading and modifying files of Mgame Worms clients.</Description>
<PackageReleaseNotes>Fix saving files. Uniquely leave open Stream instances when saving into them.</PackageReleaseNotes>
<PackageTags>$(PackageTags);online worms;worms world party aqua</PackageTags>
<Version>3.0.1</Version>
<Version>3.1.1</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

View File

@ -5,7 +5,7 @@
<Description>.NET library for loading and modifying files of Team17's Worms World Party.</Description>
<PackageReleaseNotes>Fix saving files. Uniquely leave open Stream instances when saving into them.</PackageReleaseNotes>
<PackageTags>$(PackageTags);worms world party</PackageTags>
<Version>3.0.1</Version>
<Version>3.1.1</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

View File

@ -5,7 +5,7 @@
<Description>.NET library for loading and modifying files of Team17's Worms 2.</Description>
<PackageReleaseNotes>Fix saving files. Uniquely leave open Stream instances when saving into them.</PackageReleaseNotes>
<PackageTags>$(PackageTags);worms 2</PackageTags>
<Version>3.0.1</Version>
<Version>3.1.1</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />

View File

@ -5,7 +5,7 @@
<AssemblyName>Syroot.Worms</AssemblyName>
<Description>.NET library for loading and modifying files of Team17 Worms games.</Description>
<PackageReleaseNotes>Fix saving files. Uniquely leave open Stream instances when saving into them.</PackageReleaseNotes>
<Version>3.0.1</Version>
<Version>3.1.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Syroot.BinaryData.Serialization" Version="[5.2.0, 6)" />
@ -13,4 +13,7 @@
<PackageReference Include="System.Drawing.Common" Version="[4.7.0, 5)" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="[4.7.1, 5)" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
</ItemGroup>
</Project>

View File

@ -16,9 +16,6 @@ namespace Syroot.Worms.Test.Armageddon.ProjectX
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadLibraries()
{
FileTester.LoadFiles<Library>(Game.Armageddon, "*.pxl");
}
public void LoadLibraries() => FileTester.Run<Library>(Game.Armageddon, "*.pxl");
}
}

View File

@ -16,9 +16,6 @@ namespace Syroot.Worms.Test.Armageddon.ProjectX
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadSchemes()
{
FileTester.LoadFiles<Scheme>(Game.Armageddon, "*.pxs");
}
public void LoadSchemes() => FileTester.Run<Scheme>(Game.Armageddon, "*.pxs");
}
}

View File

@ -16,9 +16,6 @@ namespace Syroot.Worms.Test.Armageddon
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadGeneratedMaps()
{
FileTester.LoadFiles<GeneratedMap>(Game.Armageddon | Game.WorldParty, "*.lev");
}
public void LoadGeneratedMaps() => FileTester.Run<GeneratedMap>(Game.Armageddon | Game.WorldParty, "*.lev");
}
}

View File

@ -16,9 +16,6 @@ namespace Syroot.Worms.Test.Armageddon
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadLandData()
{
FileTester.LoadFiles<LandData>(Game.Armageddon, "land.dat");
}
public void LoadLandData() => FileTester.Run<LandData>(Game.Armageddon, "land.dat");
}
}

View File

@ -1,24 +1,79 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Syroot.Worms.Armageddon;
using Syroot.Worms.Test.Core;
namespace Syroot.Worms.Test.Armageddon
{
/// <summary>
/// Represents a collection of tests for the <see cref="Scheme"/> class.
/// </summary>
[TestClass]
public class SchemeTests
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadSchemes()
{
FileTester.LoadFiles<Scheme>(Game.Armageddon | Game.WorldParty, "*.wsc");
}
}
}
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Syroot.Worms.Armageddon;
using Syroot.Worms.Test.Core;
namespace Syroot.Worms.Test.Armageddon
{
/// <summary>
/// Represents a collection of tests for the <see cref="Scheme"/> class.
/// </summary>
[TestClass]
public class SchemeTests
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Loads all files found in the test directory.
/// </summary>
[TestMethod]
public void TestFiles() => FileTester.Run<Scheme>(Game.Test, "*.wsc");
/// <summary>
/// Tests rounding down to the nearest valid fall damage value.
/// </summary>
[TestMethod]
public void RoundFallDamage()
{
Scheme scheme = new Scheme();
Assert.ThrowsException<ArgumentOutOfRangeException>(() => scheme.FallDamage = -1);
scheme.FallDamage = 0; Assert.AreEqual(0, scheme.FallDamage);
scheme.FallDamage = 1; Assert.AreEqual(0, scheme.FallDamage);
scheme.FallDamage = 3; Assert.AreEqual(0, scheme.FallDamage);
scheme.FallDamage = 4; Assert.AreEqual(4, scheme.FallDamage);
scheme.FallDamage = 507; Assert.AreEqual(504, scheme.FallDamage);
scheme.FallDamage = 508; Assert.AreEqual(508, scheme.FallDamage);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => scheme.FallDamage = 509);
}
/// <summary>
/// Tests rounding down to the nearest valid water rise rate value.
/// </summary>
[TestMethod]
public void RoundWaterRiseRate()
{
Scheme scheme = new Scheme();
scheme.WaterRiseRate = 0; Assert.AreEqual(0, scheme.WaterRiseRate);
scheme.WaterRiseRate = 1; Assert.AreEqual(0, scheme.WaterRiseRate);
scheme.WaterRiseRate = 4; Assert.AreEqual(0, scheme.WaterRiseRate);
scheme.WaterRiseRate = 5; Assert.AreEqual(5, scheme.WaterRiseRate);
scheme.WaterRiseRate = 252; Assert.AreEqual(245, scheme.WaterRiseRate);
scheme.WaterRiseRate = 253; Assert.AreEqual(253, scheme.WaterRiseRate);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => scheme.WaterRiseRate = 254);
}
/// <summary>
/// Tests rounding down to the nearest valid fall damage value.
/// </summary>
[TestMethod]
public void RoundObjectCount()
{
Scheme scheme = new Scheme();
Assert.ThrowsException<ArgumentOutOfRangeException>(() => scheme.ObjectCount = 0);
scheme.ObjectCount = 1; Assert.AreEqual(1, scheme.ObjectCount);
scheme.ObjectCount = 2; Assert.AreEqual(2, scheme.ObjectCount);
scheme.ObjectCount = 30; Assert.AreEqual(30, scheme.ObjectCount);
scheme.ObjectCount = 31; Assert.AreEqual(30, scheme.ObjectCount);
scheme.ObjectCount = 34; Assert.AreEqual(30, scheme.ObjectCount);
scheme.ObjectCount = 35; Assert.AreEqual(35, scheme.ObjectCount);
scheme.ObjectCount = 99; Assert.AreEqual(95, scheme.ObjectCount);
scheme.ObjectCount = 100; Assert.AreEqual(100, scheme.ObjectCount);
scheme.ObjectCount = 101; Assert.AreEqual(100, scheme.ObjectCount);
scheme.ObjectCount = 109; Assert.AreEqual(100, scheme.ObjectCount);
scheme.ObjectCount = 110; Assert.AreEqual(110, scheme.ObjectCount);
scheme.ObjectCount = 249; Assert.AreEqual(240, scheme.ObjectCount);
scheme.ObjectCount = 250; Assert.AreEqual(250, scheme.ObjectCount);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => scheme.ObjectCount = 251);
}
}
}

View File

@ -1,13 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
<ProjectReference Include="..\..\library\Syroot.Worms.Armageddon\Syroot.Worms.Armageddon.csproj" />
<ProjectReference Include="..\Syroot.Worms.Test\Syroot.Worms.Test.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
<ProjectReference Include="..\..\library\Syroot.Worms.Armageddon\Syroot.Worms.Armageddon.csproj" />
<ProjectReference Include="..\Syroot.Worms.Test\Syroot.Worms.Test.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Files\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -16,9 +16,6 @@ namespace Syroot.Worms.Test.Armageddon
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadTeamContainers()
{
FileTester.LoadFiles<TeamContainer>(Game.Armageddon, "*.wgt");
}
public void LoadTeamContainers() => FileTester.Run<TeamContainer>(Game.Armageddon, "*.wgt");
}
}

View File

@ -15,9 +15,6 @@ namespace Syroot.Worms.Test
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadArchives()
{
FileTester.LoadFiles<Archive>(Game.Team17, "*.dir");
}
public void LoadArchives() => FileTester.Run<Archive>(Game.Team17, "*.dir");
}
}

View File

@ -1,87 +1,105 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Test.Core
{
/// <summary>
/// Represents methods helping in executing file-based tests.
/// </summary>
public static class FileTester
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private static readonly Dictionary<Game, string> _gamePaths = new Dictionary<Game, string>
{
[Game.Worms2] = @"C:\Games\Worms2",
[Game.Armageddon] = @"C:\Games\Worms Armageddon 3.6.31.0",
[Game.WorldParty] = @"C:\Games\Worms World Party",
[Game.OnlineWorms] = @"C:\Games\Online Worms",
[Game.WorldPartyAqua] = @"C:\Games\WWP Aqua"
};
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Loads the files found with the given <paramref name="wildcard"/>. Excludes file names specified in the
/// optional array <paramref name="excludedFiles"/>.
/// </summary>
/// <typeparam name="T">The type of the files to load.</typeparam>
/// <param name="games">The games to test.</param>
/// <param name="wildcard">The wildcard to match.</param>
/// <param name="excludedFiles">Optionally, the files to exclude.</param>
public static void LoadFiles<T>(Game games, string wildcard, string[] excludedFiles = null)
where T : ILoadableFile, new()
{
foreach (string fileName in FindFiles(games, wildcard))
{
if (excludedFiles?.Contains(Path.GetFileName(fileName), StringComparer.OrdinalIgnoreCase) == true)
{
Debug.WriteLine($"Skipping {fileName}");
continue;
}
Debug.Write($"Loading {fileName}...");
T instance = new T();
instance.Load(fileName);
Debug.WriteLine($" ok");
}
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static List<string> FindFiles(Game games, string wildcard)
{
List<string> files = new List<string>();
foreach (string path in GetGamePaths(games))
{
if (Directory.Exists(path))
files.AddRange(Directory.GetFiles(path, wildcard, SearchOption.AllDirectories));
}
if (files.Count == 0)
throw new InvalidOperationException("No files found to test.");
return files;
}
private static IEnumerable<string> GetGamePaths(Game game)
{
return _gamePaths.Where(x => game.HasFlag(x.Key)).Select(x => x.Value);
}
}
[Flags]
public enum Game
{
Worms2 = 1 << 0,
Armageddon = 1 << 1,
WorldParty = 1 << 2,
OnlineWorms = 1 << 3,
WorldPartyAqua = 1 << 4,
Team17 = Worms2 | Armageddon | WorldParty,
Mgame = OnlineWorms | WorldPartyAqua,
All = -1
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Syroot.Worms.Core.IO;
namespace Syroot.Worms.Test.Core
{
/// <summary>
/// Represents methods helping in executing file-based tests.
/// </summary>
public static class FileTester
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private static readonly Dictionary<Game, string> _gamePaths = new Dictionary<Game, string>
{
[Game.Worms2] = @"C:\Games\Worms2",
[Game.Armageddon] = @"C:\Games\Worms Armageddon 3.6.31.0",
[Game.WorldParty] = @"C:\Games\Worms World Party",
[Game.OnlineWorms] = @"C:\Games\Online Worms",
[Game.WorldPartyAqua] = @"C:\Games\WWP Aqua",
[Game.Test] = "Files"
};
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// Loads, saves, reloads, and compares the files found with the given <paramref name="wildcard"/>, as long as
/// each operation is supported. Excludes file names specified in the optional array
/// <paramref name="excludedFiles"/>.
/// </summary>
/// <typeparam name="T">The type of the files to load.</typeparam>
/// <param name="games">The games to test.</param>
/// <param name="wildcard">The wildcard to match.</param>
/// <param name="excludedFiles">Optionally, the files to exclude.</param>
public static void Run<T>(Game games, string wildcard, string[] excludedFiles = null)
where T : ILoadableFile, new()
{
foreach (string fileName in FindFiles(games, wildcard))
{
if (excludedFiles?.Contains(Path.GetFileName(fileName), StringComparer.OrdinalIgnoreCase) == true)
{
Debug.WriteLine($"Skipping {fileName}");
continue;
}
Debug.Write($"\"{fileName}\" load");
T instance = new T();
instance.Load(fileName);
if (instance is ISaveableFile saveable)
{
Debug.Write($" save");
using MemoryStream stream = new MemoryStream();
saveable.Save(stream);
Debug.Write($" reload");
stream.Position = 0;
T newInstance = new T();
newInstance.Load(stream);
Debug.Write($" compare");
Assert.IsTrue(newInstance.Equals(instance));
}
Debug.WriteLine($" OK");
}
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static List<string> FindFiles(Game games, string wildcard)
{
List<string> files = new List<string>();
foreach (string path in GetGamePaths(games))
if (Directory.Exists(path))
files.AddRange(Directory.GetFiles(path, wildcard, SearchOption.AllDirectories));
if (files.Count == 0)
throw new InvalidOperationException("No files found to test.");
return files;
}
private static IEnumerable<string> GetGamePaths(Game game)
{
return _gamePaths.Where(x => game.HasFlag(x.Key)).Select(x => x.Value);
}
}
[Flags]
public enum Game
{
Worms2 = 1 << 0,
Armageddon = 1 << 1,
WorldParty = 1 << 2,
OnlineWorms = 1 << 3,
WorldPartyAqua = 1 << 4,
Test = 1 << 32,
Team17 = Worms2 | Armageddon | WorldParty,
Mgame = OnlineWorms | WorldPartyAqua,
All = -1
}
}

View File

@ -15,9 +15,6 @@ namespace Syroot.Worms.Test
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadImages()
{
FileTester.LoadFiles<Img>(Game.All, "*.img");
}
public void LoadImages() => FileTester.Run<Img>(Game.All, "*.img");
}
}

View File

@ -17,7 +17,7 @@ namespace Syroot.Worms.Test
[TestMethod]
public void LoadPalettes()
{
FileTester.LoadFiles<RiffPalette>(Game.Team17, "*.pal", new string[]
FileTester.Run<RiffPalette>(Game.Team17, "*.pal", new string[]
{
"wwp.pal", // Contains 4 bytes of trash after the data chunk.
"wwpmaped.pal" // Contains 4 bytes of trash after the data chunk.

View File

@ -16,9 +16,6 @@ namespace Syroot.Worms.Test.WorldParty
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadLandData()
{
FileTester.LoadFiles<LandData>(Game.WorldParty, "land.dat");
}
public void LoadLandData() => FileTester.Run<LandData>(Game.WorldParty, "land.dat");
}
}

View File

@ -16,9 +16,6 @@ namespace Syroot.Worms.Test.WorldParty
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadTeamContainers()
{
FileTester.LoadFiles<TeamContainer>(Game.WorldParty, "*.wwp");
}
public void LoadTeamContainers() => FileTester.Run<TeamContainer>(Game.WorldParty, "*.wwp");
}
}

View File

@ -16,9 +16,6 @@ namespace Syroot.Worms.Test.Worms2
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadLandData()
{
FileTester.LoadFiles<LandData>(Game.Worms2, "land.dat");
}
public void LoadLandData() => FileTester.Run<LandData>(Game.Worms2, "land.dat");
}
}

View File

@ -16,9 +16,6 @@ namespace Syroot.Worms.Test.Worms2
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadSchemeOptions()
{
FileTester.LoadFiles<SchemeOptions>(Game.Worms2, "*.opt");
}
public void LoadSchemeOptions() => FileTester.Run<SchemeOptions>(Game.Worms2, "*.opt");
}
}

View File

@ -16,9 +16,6 @@ namespace Syroot.Worms.Test.Worms2
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadSchemeWeapons()
{
FileTester.LoadFiles<SchemeWeapons>(Game.Worms2, "*.wep");
}
public void LoadSchemeWeapons() => FileTester.Run<SchemeWeapons>(Game.Worms2, "*.wep");
}
}

View File

@ -16,9 +16,6 @@ namespace Syroot.Worms.Test.Worms2
/// Loads all files found in any game directories.
/// </summary>
[TestMethod]
public void LoadTeamContainers()
{
FileTester.LoadFiles<TeamContainer>(Game.Worms2, "*.st1");
}
public void LoadTeamContainers() => FileTester.Run<TeamContainer>(Game.Worms2, "*.st1");
}
}

View File

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.IO;
using Syroot.Worms.Armageddon;
using Syroot.Worms.Mgame;
namespace Syroot.Worms.Scratchpad
@ -11,31 +8,7 @@ namespace Syroot.Worms.Scratchpad
{
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static void Main()
{
IEnumerable<string> fileNames = Directory.EnumerateFiles(
@"C:\Games\Worms Armageddon 3.7.2.1\User\Schemes", "*.wsc", SearchOption.AllDirectories);
foreach (string fileName in fileNames)
{
string tempFile = Path.GetTempFileName();
using (Stream tempStream = new FileStream(tempFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
// Load the scheme and save it to temp file.
Scheme origScheme = new Scheme(fileName);
tempStream.Position = 0;
origScheme.Save(tempStream);
// Load the temp file.
tempStream.Position = 0;
Scheme newScheme = new Scheme(tempStream);
if (origScheme.ObjectCount != newScheme.ObjectCount || origScheme.ObjectTypes != newScheme.ObjectTypes)
throw new InvalidOperationException("mismatch");
}
File.Delete(tempFile);
}
}
private static void Main() { }
private static void ConvertIgdImages()
{